Using "IndexOf" on a List<T> of objects - c#

In my Visual Studio2019 C# console program (named 'codeTester') I have this object:
public class ipData
{
private string ip;
private string region;
private string country;
public ipData(string ip, string region, string country)
{
this.ip = ip;
this.region = region;
this.country = country;
}
public string Ip
{
get { return ip; }
set { ip = value; }
}
public string Region
{
get { return region; }
set { region = value; }
}
public string Country
{
get { return country; }
set { country = value; }
}
}
and I created a List of this object and add some data:
List<ipData> ipInfo = new List<ipData>();
ipInfo.Add(new ipData("192.168.0.199", "UT", "USA"));
ipInfo.Add(new ipData("251.168.0.963", "NB", "CAN"));
Now I want to search the list on one of its fields so I ask the user for the data to search for:
Console.WriteLine("Enter searh criteria: ");
string searchparam = Console.ReadLine();
Next I want the index of the found item, if any:
int x = ipInfo.IndexOf(searchparam);
but this statement throws a design-time exception which says:
"Argument 1: cannot convert from 'string' to 'codeTester.Program.ipData'"
So I've been stuck at this point for hours and all my searches have not yielded anything pertinent. Where am I going wrong?

It does not work, because the search parameter is expected to be of the same type as the element type of the list. In this case ipData.
You could use FindIndex which accepts a lambda expression as parameter:
int x =
ipInfo.FindIndex(ip => ip.Region == searchparam || ip.Country == searchparam);
if (x >= 0) {
Console.WriteLine($"The IP address is {ipInfo[x]}");
} else {
Console.WriteLine("not found");
}
or you can use LINQ like this:
string ipAddress = ipInfo
.FirstOrDefault(ip => ip.Region == searchparam || ip.Country == searchparam)?.Ip;
This will return a null string if no entry was found.
You can also have it return the whole record instead:
ipData data = ipInfo
.FirstOrDefault(ip => ip.Region == searchparam || ip.Country == searchparam);
if (data != null) {
Console.WriteLine(
$"Country = {data.Country}, Region = {data.Region}, IP = {data.ip}");
}
LINQ also allows you to return more than one result. E.g. you can return all data corresponding to one country like this:
var result = ipInfo.Where(ip => ip.Country == "USA");
foreach (ipData data in result) {
Console.WriteLine(
$"Country = {data.Country}, Region = {data.Region}, IP = {data.ip}");
}
The C# naming conventions state the class names should be written in PascalCase. Another convention says that acronyms with up to two characters in length are written all upper case (IP). According to these conventions, the class name should be IPData.
See also:
Capitalization Conventions (Microsoft Docs)
C# Coding Standards and Naming Conventions

That is because IndexOf searches for the exact object in list. For it to work, you would have to pass ipData to it instead.
For this purpose, you should use Find or FindIndex.

Related

How do to create string ciphers

I have an error on this part foreach( string code in text ) the error is saying can not convert char to string. how do i convert this to string
my list
class MyCipher : ICipherDecipher
{
private List<Code> alphabet;
public MyCipher()
{
alphabet = new List<Code>();
alphabet.Add(new Code("Aca", " 1234"));
alphabet.Add(new Code("Bb", " 1234"));
alphabet.Add(new Code("C1", " 1234"));
}
this is where im gtting the error on the foreach part , its saying cant convert to string from char
private string Cipher( string text )
{
StringBuilder result = new StringBuilder();
foreach( string code in text )
{
Code element =
alphabet.Where(x => x.MyCode == code.ToString()).SingleOrDefault();
if ( element != null)
{
result.Append(element.MyDecoded);
}
}
return result.ToString();
}
Edited code
class MyCipher : ICipherDecipher
{
private List<Code> alphabet;
public MyCipher()
{
alphabet = new List<Code>();
alphabet.Add(new Code("4", " take 4"));
alphabet.Add(new Code(" ", " a"));
alphabet.Add(new Code("4d", " for 4 days"));
}
public string Cipher(params string[] codes)
{
StringBuilder result = new StringBuilder();
foreach (string code in codes)
{
Code element =
alphabet.Where(x => x.MyCode == code).SingleOrDefault();
if (element != null)
{
result.Append(element.MyDecoded);
}
}
return result.ToString();
}
class Code
{
public string MyCode;
public string MyDecoded;
public Code(string code, string decode)
{
MyCode = code;
MyDecoded = decode;
}
}
}
Button code
public partial class Form1 : Form
{
private ICipherDecipher myCipher;
public Form1()
{
myCipher = new MyCipher();
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
string textToBeCiphered = textBox1.Text;
string textCiphered = myCipher.Cipher(textToBeCiphered);
textBox2.Text = textCiphered;
}
}
Change this part of your code:
foreach( char code in text )
{
Code element =
alphabet.Where(x => x.MyCode == code.ToString()).SingleOrDefault();
if ( element != null)
{
result.Append(element.MyDecoded);
}
}
Then everywhere you use code you convert it to string by using code.ToString()
You're iterating over a string (text), so the code variable should be a char, not a string:
foreach( char code in text )
Alternatively, you can use var:
foreach( var code in text )
to make the compiler automatically assign the type it thinks it should be, but you should still be aware of the type it actually is because it impacts the operations the variable supports.
Going a bit deeper into it, some other languages (especially dynamic languages like JavaScript and Python) don't make this distinction but C# (like most other C-like languages) has a char datatype that holds a single element of the text. In the case of C#, that type holds a UTF-16 code unit. (Which is not always the same as a user-visible character, but that's a whole other story).
Based on the discussion in the comments, it seems you want to match the whole string instead of characters of the string. In that case you have two options, depending on what you want to do:
If you want the Cypher method to receive a single string and get the code that matches that string, just get rid of the loop and match against the parameter directly:
private string Cipher( string code )
{
StringBuilder result = new StringBuilder();
Code element =
alphabet.Where(x => x.MyCode == code).SingleOrDefault();
if ( element != null)
{
result.Append(element.MyDecoded);
}
return result.ToString();
}
Alternatively, if you want to pass multiple strings and process all of them, pass an IEnumerable<string> (from System.Collections.Generic) or a derived type such as string[]:
private string Cipher( IEnumerable<string> codes )
{
StringBuilder result = new StringBuilder();
foreach( string code in codes )
{
Code element =
alphabet.Where(x => x.MyCode == code).SingleOrDefault();
if ( element != null)
{
result.Append(element.MyDecoded);
}
}
return result.ToString();
}
// Usage examples:
private string CallSite() {
// (These are just some of the options, other enumerable types
// will work as well as long as they enumerate over strings)
return Cypher( new[] { "Aca", "Bb" });
return Cypher( new List<string> { "Aca", "Bb" });
}
Alternatively, if you replace IEnumerable<string> with params string[] you can call the function as if it had any number of string parameters:
private string Cipher( params string[] codes )
{
// ... (as above)
}
// Usage example:
private string CallSite() {
return Cypher( "Aca", "Bb" );
}
In any of these cases, you can get replace code.ToString() with simply code because it already is a string (and "string".ToString() just returns itself).

Optimizing processing of data stored in a flat file

At my office we use an old third-party tool to handle some data processing and export work. The output of this tool is unfortunately in a really clunky format, so for us to put it into a meaningful form and work with it, we have to have an intermediate processing step between the raw export of this data and our ability to act further on it.
This problem was one that I pretty concisely solved some time ago in Python with itertools, but for reasons, I need to relocate this work into an existing C# application.
I've super-generalized and simplified the example data that I've posted here (and the corresponding code), but it's representative of the way the real data is set up. The raw data spit out by the tool looks like this, with some caveats (which I'll explain):
Zip Code: 11111
First Name: Joe
Last Name: Smith
ID: 1
Phone Number: 555-555-1111
Zip Code: 11111
First Name: John
Last Name: Doe
ID: 2
Phone Number: 555-555-1112
Zip Code: 11111
First Name: Mike
Last Name: Jones
ID: 3
Phone Number: 555-555-1113
There are no unique separators between records. They're just listed one right after the other. A valid and actionable record contains all five items ("Zip Code", "First Name", "Last Name", "ID", "Phone Number").
We only need first/last name, ID, and phone number for our purposes. Each unique record always begins with Zip Code, but thanks to some quirks in the underlying process and the third-party tool, I have some things I need to account for:
Records missing a phone number are invalid, and will show up with a value of "(n/a)" in the "Phone Number" line. We need to ignore the whole record in this case.
Records (rarely) may be missing a line (such as "Last Name") if the record was not entered correctly prior to processing. We ignore these cases, too.
If there was an error with some linked information to the underlying data, the record will contain a line beginning with "Error". Its exact position among the other items in a record varies. If a record contains an error, we ignore it.
The way I solved this in C# is to start with the first line and check to see if it begins with "Zip Code". If so, I drop into a further loop where I build a dictionary of keys and values (splitting on the first ":") until I hit the next "Zip Code" line. It then repeats and rolls through the process again while current line < (line count - 5).
private void CrappilyHandleExportLines(List<string> RawExportLines)
{
int lineNumber = 0;
while (lineNumber < (RawExportLines.Count - 5))
{
// The lineGroup dict will represent the record we're currently processing
Dictionary<string, string> lineGroup = new Dictionary<string, string>();
// If the current line begins with "Zip Code", this means we've reached another record to process
if (RawExportLines[lineNumber++].StartsWith("Zip Code"))
{
// If the line does NOT begin with "Zip Code", we assume it's another part of the record we're already
// working on.
while (!RawExportLines[lineNumber].StartsWith("Zip Code"))
{
// Append everything except "Error" lines to the record we're working on, as stored in lineGroup
if (!RawExportLines[lineNumber].StartsWith("Error")
{
string[] splitLine = RawExportLines[lineNumber].Split(new[] { ":" }, 2, StringSplitOptions.None);
lineGroup[splitLine[0].Trim()] = splitLine[1].Trim();
}
lineNumber++;
}
}
// Validate the record before continuing. verifyAllKeys is just a method that does a check of the key list
// against a list of expected keys using Except to make sure all of the items that we require are present.
if (verifyAllKeys(new List<string>(lineGroup.Keys)) || (lineGroup["Phone Number"] != "(n/a)"))
{
// The record is good! Now we can do something with it:
WorkOnProcessedRecord(lineGroup);
}
}
}
This works (from my initial testing, at least). The problem is that I really dislike this code. I know there's a better way to do it, but I'm not as strong in C# as I'd like to be so I think I'm missing out on some ways that would allow me to more elegantly and safely get the desired result.
Can anyone lend a hand to point me in the right direction as to how I can implement a better solution? Thank you!
This may help you, the idea is grouping entries based on their id by dictionary, then you can validate enitries with appropriate conditions:
static void Main(string[] args)
{
string path = #"t.txt";
var text = File.ReadAllLines(path, Encoding.UTF8);
var dict = new Dictionary<string, Dictionary<string, string>>();
var id = "";
var rows = text
.Select(l => new { prop = l.Split(':')[0], val = l.Split(':')[1].Trim() })
.ToList();
foreach (var row in rows)
{
if (row.prop == "ID")
{
id = row.val;
}
else if (dict.ContainsKey(id))
{
dict[id].Add(row.prop, row.val);
}
else
{
dict[id] = new Dictionary<string, string>();
dict[id].Add(row.prop, row.val);
}
}
//get valid entries
var validEntries = dict.Where(e =>e.Value.Keys.Intersect(new List<string> { "Zip Code", "First Name", "Last Name", "Phone Number" }).Count()==4 && e.Value["Phone Number"] != "(n/a)").ToDictionary(x=>x.Key, x => x.Value);
}
In case ID is related to previous properties and emerges after them you can use below code as If block :
if (row.prop == "ID")
{
var values=dict[id];
dict.Remove(id);
dict.Add(row.val,values);
id = "";
}
I would try to solve the problem in a bit more of an object oriented manner using a factory-ish pattern.
//Define a class to hold all people we get, which might be empty or have problems in them.
public class PersonText
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string PhoneNumber { get; set; }
public string ID { get; set; }
public string ZipCode { get; set; }
public bool Error { get; set; }
public bool Anything { get; set; }
}
//A class to hold a key ("First Name"), and a way to set the respective item on the PersonText class correctly.
public class PersonItemGetSets
{
public string Key { get; }
public Func<PersonText, string> Getter { get; }
public Action<PersonText, string> Setter { get; }
public PersonItemGetSets(string key, Action<PersonText, string> setter, Func<PersonText, string> getter)
{
Getter = getter;
Key = key;
Setter = setter;
}
}
//This will get people from the lines of text
public static IEnumerable<PersonText> GetPeople(IEnumerable<string> lines)
{
var itemGetSets = new List<PersonItemGetSets>()
{
new PersonItemGetSets("First Name", (p, s) => p.FirstName = s, p => p.FirstName),
new PersonItemGetSets("Last Name", (p, s) => p.LastName = s, p => p.LastName),
new PersonItemGetSets("Phone Number", (p, s) => p.PhoneNumber = s, p => p.PhoneNumber),
new PersonItemGetSets("ID", (p, s) => p.ID = s, p => p.ID),
new PersonItemGetSets("Zip Code", (p, s) => p.ZipCode = s, p => p.ZipCode),
};
foreach (var person in GetRawPeople(lines, itemGetSets, "Error"))
{
if (IsValidPerson(person, itemGetSets))
yield return person;
}
}
//Used to determine if a PersonText is valid and if it is worth processing.
private static bool IsValidPerson(PersonText p, IReadOnlyList<PersonItemGetSets> itemGetSets)
{
if (itemGetSets.Any(x => x.Getter(p) == null))
return false;
if (p.Error)
return false;
if (!p.Anything)
return false;
if (p.PhoneNumber.Length != 12) // "555-555-5555".Length = 12
return false;
return true;
}
//Read through each line, and return all potential people, but don't validate whether they're correct at this time.
private static IEnumerable<PersonText> GetRawPeople(IEnumerable<string> lines, IReadOnlyList<PersonItemGetSets> itemGetSets, string errorToken)
{
var person = new PersonText();
foreach (var line in lines)
{
var parts = line.Split(':');
bool valid = false;
if (parts.Length == 2)
{
var left = parts[0];
var right = parts[1].Trim();
foreach (var igs in itemGetSets)
{
if (left.Equals(igs.Key, StringComparison.OrdinalIgnoreCase))
{
valid = true;
person.Anything = true;
if (igs.Getter(person) != null)
{
yield return person;
person = new PersonText();
}
igs.Setter(person, right);
}
}
}
else if (parts.Length == 1)
{
if (parts[0].Trim().Equals(errorToken, StringComparison.OrdinalIgnoreCase))
{
person.Error = true;
}
}
if (!valid)
{
if (person.Anything)
{
yield return person;
person = new PersonText();
}
continue;
}
}
if (person.Anything)
yield return person;
}
Have a look at the code working here: https://dotnetfiddle.net/xVnATX

How to Sort a Class List that contain both of double and string data

Person class:
class Person
{
public string ID;
public string Name;
public string PClass;
public string Age;
public string Sex;
public string Survived;
public string SexCode;
public Person(string id,string name,string pclass,string age,string sex,
string survived,string sexcode)
{
ID = id;
Name = name;
PClass = pclass;
Age = age;
Sex = sex;
Survived = survived;
SexCode = sexcode;
}
My program code:
class Program
{
static void Main(string[] args)
{
string[] data = File.ReadAllLines("titanic.csv");
data = data.Skip(1).ToArray();
List<Person> personList = new List<Person>();
List<Person> personList_name = new List<Person>();
List<Person> personList_pclass = new List<Person>();
List<Person> personList_age = new List<Person>();
List<Person> personList_sex = new List<Person>();
List<Person> personList_id = new List<Person>();
for (int i = 0; i < data.Length; i++)
{
string[] temp = Regex.Split(data[i], ",(?=(?:[^\"]*\"[^\"]*\")*[^\"]*$)");
Person person = new Person(temp[0], temp[1], temp[2],temp[3],
temp[4], temp[5], temp[6]);
personList.Add(person);
}
personList_name = personList.OrderBy(x => x.Name).ToList();
personList_pclass = personList.OrderBy(z => z.PClass).ToList();
personList_sex = personList.OrderBy(w => w.Sex).ToList();
int id_;
int age_;
personList_age = personList.OrderBy(y => int.TryParse(y.Age, out age_)).ToList();
//personList_id = personList.OrderByDescending(int.TryParse(ID, out number)).ToList();
personList_id = personList.OrderBy(o => int.TryParse(o.ID, out id_)).ToList();
while (true)
{
Console.WriteLine(" Please select your filtring method:\n" +
"1-By Name\n2-By Pclass\n3-By Age\n4-By Sex\n5-By ID\n Press -1 to quit.");
string selection = Console.ReadLine();
if (selection == "-1")
{
break;
}
Console.WriteLine(("{0,-10}{1,70}{2,20}{3,20}{4,20}{5,20}{6,20}"), item.ID.Trim('"'), item.Name.Trim('"'), item.PClass.Trim('"')
, item.Age, item.Sex.Trim('"'), item.Survived.Trim('"')
, item.SexCode.Trim('"'));
}
}
if (selection == "3")
{
Console.WriteLine(("{0,-10}{1,70}{2,20}{3,20}{4,20}{5,20}{6,20}"), "ID", "NAME", "PCLASS", "AGE", "SEX", "SURVIVED", "SEXCODE");
foreach (var item in personList_age)
{
Console.WriteLine(("{0,-10}{1,70}{2,20}{3,20}{4,20}{5,20}{6,20}"), item.ID.Trim('"'), item.Name.Trim('"'), item.PClass.Trim('"')
, item.Age, item.Sex.Trim('"'), item.Survived.Trim('"')
, item.SexCode.Trim('"'));
}
}
}
}
}
I am able to sort string data from CSV file such as Name, but for age and ID I cannot obtain the correct order (either ascending or descending)
I have searched a lot about this problem. Tried to use Icomparable and Tryparse
but without any positive result. The problem is that Orderby deals with age and ID as string. But if I define Age and ID as double or int I will have "cannot convert" while defining temp array. Any helpful suggestion or solution PLEASE?
This is what happens when I order according to Age for example. It seems still ordering according to ID!
(source: up-00.com)
Instead of an expression, provide a function:
personList_age = personList.OrderBy
(
y => {
int age;
bool ok = int.TryParse(y.Age, out age);
return ok ? age : default(int);
}
).ToList();
Or to keep things clean maybe write an extension method:
static void int ToInt(this string input)
{
int n;
bool ok = int.TryParse(input, out n);
return ok ? n : default(int);
}
And call like this:
personList_age = personList.OrderBy( y => t.Age.ToInt());
Have you tried
personList.OrderBy(x => Convert.ToInt32(x.id)).ToList();
?
This way it should sort them by the int-value
The problem is that TryParse returns a bool and not the parsed value. OrderBy then orders true vs false. In an ascending ordering, false comes before true.
If you are expecting these values to always be integers you have a few options.
The better (in my opinion) is to change those properties in your class to integers and do your parsing in the constructor, throwing an exception if a bad value is passed (not allowing a bad Person to be created).
The second option would be to switch to int.Parse within the OrderBy. This only works if all the values can definitely be parsed because otherwise an exception is thrown.
If you must use TryParse (for example it's not guaranteed to be integer data and you can't change your class definition), you can use a multi statement lambda:
personList.OrderBy(person => {
int value;
return int.TryParse(person.ID, out value) ? value : -1;
});
You may want to chose a different value for the failure case depending on where you want them to sort. In the above case all failures would come first--though that assumes that no ID could be negative. You could use int.MinValue instead. If you want them to come last you could use int.MaxValue--though if your IDs could legitimately be that large you'll have a problem (I think it is safe to assume at least that nobody will have an age that large).
Another option would be to filter out the failures by first using Select to create an anonymous type containing the data you need (successfully parsed, value if applicable and your original Person). Then use Where to filter our any values that failed to parse followed by an OrderBy on the parsed data. Finally use another Select to extract your Person from the anonymous type.
personList.Select(p => {
int value;
var ok = int.TryParse(p.ID, out value);
return new { Ok = ok, ID = value, Person = p };
})
.Where(result => result.Ok)
.OrderBy(result => result.ID)
.Select(result => result.Person);

Getting expression text

I want to pass the name of a property of a model to a method. Instead of using the name as string, I am using lambda expression as it is easy to make a typo, and also property names may be changed. Now if the property is a simple property (e.g: model.Name) I can get the name from the expression. But if it is a nested property (e.g: model.AnotherModel.Name) then how can I get full text ("AnotherModel.Name") from the expression. For example, I have the following classes:
public class BaseModel
{
public ChildModel Child { get; set; }
public List<ChildModel> ChildList { get; set; }
public BaseModel()
{
Child = new ChildModel();
ChildList = new List<ChildModel>();
}
}
public class ChildModel
{
public string Name { get;set; }
}
public void GetExpressionText<T>(Expression<Func<T, object>> expression)
{
string expText;
//what to do??
return expText;
}
GetExpressionText<BaseModel>(b => b.Child); //should return "Child"
GetExpressionText<BaseModel>(b => b.Child.Name); //should return "Child.Name"
GetExpressionText<BaseModel>(b => b.ChildList[0].Name); //should return "ChildList[0].Name"
My first thought was to use expression.Body.ToString() and tweak that a bit, but you would still need to deal with Unary (convert) etc. Assuming this is for logging and you want more control, the below can be used for formatting as wanted (e.g. if you want Child->Name for display purposes, string.Join("->",..) can be used). It may not be complete, but should you find any unsupported types, they should be easy to add.
PS: this post was generated before the question was closed. Just noticed it was reopend and submitting it now, but I haven't checked if particulars have been changed.
public string GetName(Expression e, out Expression parent)
{
if(e is MemberExpression m){ //property or field
parent = m.Expression;
return m.Member.Name;
}
else if(e is MethodCallExpression mc){
string args = string.Join(",", mc.Arguments.SelectMany(GetExpressionParts));
if(mc.Method.IsSpecialName){ //for indexers, not sure this is a safe check...
return $"{GetName(mc.Object, out parent)}[{args}]";
}
else{ //other method calls
parent = mc.Object;
return $"{mc.Method.Name}({args})";
}
}
else if(e is ConstantExpression c){ //constant value
parent = null;
return c.Value?.ToString() ?? "null";
}
else if(e is UnaryExpression u){ //convert
parent= u.Operand;
return null;
}
else{
parent =null;
return e.ToString();
}
}
public IEnumerable<string> GetExpressionParts(Expression e){
var list = new List<string>();
while(e!=null && !(e is ParameterExpression)){
var name = GetName(e,out e);
if(name!=null)list.Add(name);
}
list.Reverse();
return list;
}
public string GetExpressionText<T>(Expression<Func<T, object>> expression) => string.Join(".", GetExpressionParts(expression.Body));
You could use the C# 6.0 feature: nameof(b.Child) "Used to obtain the simple (unqualified) string name of a variable, type, or member."
which will also change on renaming. But this will only return the propertyname and not the complete path. Returning a complete path will be difficult, because only one instance is passed.
Closest i know right now is by simply using expression.Body.ToString() which would result in b.ChildList.get_Item(0).Name as a result.
You would still have to remove the first b. from the string if not wanted, and you could go even further to your intended output with Regex by replacing the get_Item(0) with the typical Index-Accessor.
(Also i had to make the ChildList and the Name-Property of ChildModel public to get it to work)
This Should get you most of the way there:
public static string GetFullPath<T>(Expression<Func<T>> action)
{
var removeBodyPath = new Regex(#"value\((.*)\).");
var result = action.Body.ToString();
var replaced = removeBodyPath.Replace(result, String.Empty);
var seperatedFiltered = replaced.Split('.').Skip(1).ToArray();
return string.Join(".", seperatedFiltered);
}
It gets ugly quite quickly...
public static string GetExpressionText<T>(Expression<Func<T, object>> expression)
{
bool needDot = false;
Expression exp = expression.Body;
string descr = string.Empty;
while (exp != null)
{
if (exp.NodeType == ExpressionType.MemberAccess)
{
// Property or field
var ma = (MemberExpression)exp;
descr = ma.Member.Name + (needDot ? "." : string.Empty) + descr;
exp = ma.Expression;
needDot = true;
}
else if (exp.NodeType == ExpressionType.ArrayIndex)
{
// Array indexer
var be = (BinaryExpression)exp;
descr = GetParameters(new ReadOnlyCollection<Expression>(new[] { be.Right })) + (needDot ? "." : string.Empty) + descr;
exp = be.Left;
needDot = false;
}
else if (exp.NodeType == ExpressionType.Index)
{
// Object indexer (not used by C#. See ExpressionType.Call)
var ie = (IndexExpression)exp;
descr = GetParameters(ie.Arguments) + (needDot ? "." : string.Empty) + descr;
exp = ie.Object;
needDot = false;
}
else if (exp.NodeType == ExpressionType.Parameter)
{
break;
}
else if (exp.NodeType == ExpressionType.Call)
{
var ca = (MethodCallExpression)exp;
if (ca.Method.IsSpecialName)
{
// Object indexer
bool isIndexer = ca.Method.DeclaringType.GetDefaultMembers().OfType<PropertyInfo>().Where(x => x.GetGetMethod() == ca.Method).Any();
if (!isIndexer)
{
throw new Exception();
}
}
else if (ca.Object.Type.IsArray && ca.Method.Name == "Get")
{
// Multidimensiona array indexer
}
else
{
throw new Exception();
}
descr = GetParameters(ca.Arguments) + (needDot ? "." : string.Empty) + descr;
exp = ca.Object;
needDot = false;
}
}
return descr;
}
private static string GetParameters(ReadOnlyCollection<Expression> exps)
{
var values = new string[exps.Count];
for (int i = 0; i < exps.Count; i++)
{
if (exps[i].NodeType != ExpressionType.Constant)
{
throw new Exception();
}
var ce = (ConstantExpression)exps[i];
// Quite wrong here... We should escape string values (\n written as \n and so on)
values[i] = ce.Value == null ? "null" :
ce.Type == typeof(string) ? "\"" + ce.Value + "\"" :
ce.Type == typeof(char) ? "'" + ce.Value + "\'" :
ce.Value.ToString();
}
return "[" + string.Join(", ", values) + "]";
}
The code is quite easy to read, but it is quite long... There are 4 main cases: MemberAccess, that is accessing a property/field, ArrayIndex that is using the indexer of a single-dimensional array, Index that is unused by the C# compiler, but that should be using the indexer of an object (like the [...] of the List<> you are using), and Call that is used by C# for using an indexer or for accessing multi-dimensional arrays (new int[5, 4]) (and for other method calls, but we disregard them).
I support multidimensional arrays, jagged array s(arrays of arrays, new int[5][]) or arrays of indexable objects (new List<int>[5]) or indexable objects of indexable objects (new List<List<int>>). There is even support for multi-property indexers (indexers that use more than one key value, like obj[1, 2]). Small problem: printing the "value" of the indexers: I support only null, integers of various types, chars and strings (but I don't escape them... ugly... if there is a \n then it won't be printed as \n). Other types are not really supported... They will print what they will print (see GetParameters() if you want)

Best practice for parsing and validating mobile number

I wonder what the best practice for parsing and validating a mobile number before sending a text is. I've got code that works, but I'd like to find out better ways of doing it (as my last question, this is part of my early new years resolution to write better quality code!).
At the moment we are very forgiving when the user enters the number on the form, they can enter things like "+44 123 4567890", "00441234567890", "0123456789", "+44(0)123456789", "012-345-6789" or even "haven't got a phone".
However, to send the text the format must be 44xxxxxxxxxx (this is for UK mobiles only), so we need to parse it and validate it before we can send. Below is the code that I have for now (C#, asp.net), it would be great if anyone had any ideas on how to improve it.
Thanks,
Annelie
private bool IsMobileNumberValid(string mobileNumber)
{
// parse the number
_mobileNumber = ParsedMobileNumber(mobileNumber);
// check if it's the right length
if (_mobileNumber.Length != 12)
{
return false;
}
// check if it contains non-numeric characters
if(!Regex.IsMatch(_mobileNumber, #"^[-+]?[0-9]*\.?[0-9]+$"))
{
return false;
}
return true;
}
private string ParsedMobileNumber(string number)
{
number = number.Replace("+", "");
number = number.Replace(".", "");
number = number.Replace(" ", "");
number = number.Replace("-", "");
number = number.Replace("/", "");
number = number.Replace("(", "");
number = number.Replace(")", "");
number = number.Trim(new char[] { '0' });
if (!number.StartsWith("44"))
{
number = "44" + number;
}
return number;
}
EDIT
Here's what I ended up with:
private bool IsMobileNumberValid(string mobileNumber)
{
// remove all non-numeric characters
_mobileNumber = CleanNumber(mobileNumber);
// trim any leading zeros
_mobileNumber = _mobileNumber.TrimStart(new char[] { '0' });
// check for this in case they've entered 44 (0)xxxxxxxxx or similar
if (_mobileNumber.StartsWith("440"))
{
_mobileNumber = _mobileNumber.Remove(2, 1);
}
// add country code if they haven't entered it
if (!_mobileNumber.StartsWith("44"))
{
_mobileNumber = "44" + _mobileNumber;
}
// check if it's the right length
if (_mobileNumber.Length != 12)
{
return false;
}
return true;
}
private string CleanNumber(string phone)
{
Regex digitsOnly = new Regex(#"[^\d]");
return digitsOnly.Replace(phone, "");
}
Use a regular expression to remove any non-numeric characters instead of trying to guess how a person will enter their number - this will remove all your Replace() and Trim() methods, unless you really need to trim a leading zero.
string CleanPhone(string phone)
{
Regex digitsOnly = new Regex(#"[^\d]");
return digitsOnly.Replace(phone, "");
}
Alternatively, I would recommend you use a masked textbox to collect the # (there are many options available) to allow only numeric input, and display the input with whatever format you'd like. This way you're guaranteeing that the value received will be all numeric characters.
Check out QAS, it's a commercial solution.
They have email, phone and address validations.
http://www.qas.com/phone-number-validation-web-service.htm
We use their services for Address and Email (not phone) and have been satisfied with it.
#annelie maybe you can update your regular expression to a more powerful one. Check out this site here. It contains many expressions but I think one of the top 2 expressions in the site should be suitable to you.
public class PhoneNumber
{
public PhoneNumber(string value)
{
if (String.IsNullOrEmpty(value))
throw new ArgumentNullException("numberString", Properties.Resources.PhoneNumberIsNullOrEmpty);
var match = new Regex(#"\+(\w+) \((\w+)\) (\w+)", RegexOptions.Compiled).Match(value);
if (match.Success)
{
ushort countryCode = 0;
ushort localCode = 0;
int number = 0;
if (UInt16.TryParse(match.Result("$1"), out countryCode) &&
UInt16.TryParse(match.Result("$2"), out localCode) &&
Int32.TryParse(match.Result("$3"), out number))
{
this.CountryCode = countryCode;
this.LocalCode = localCode;
this.Number = number;
}
}
else
{
throw new ArgumentNullException("numberString", Properties.Resources.PhoneNumberInvalid);
}
}
public PhoneNumber(int countryCode, int localCode, int number)
{
if (countryCode == 0)
throw new ArgumentOutOfRangeException("countryCode", Properties.Resources.PhoneNumberIsNullOrEmpty);
else if (localCode == 0)
throw new ArgumentOutOfRangeException("localCode", Properties.Resources.PhoneNumberIsNullOrEmpty);
else if (number == 0)
throw new ArgumentOutOfRangeException("number", Properties.Resources.PhoneNumberIsNullOrEmpty);
this.CountryCode = countryCode;
this.LocalCode = localCode;
this.Number = number;
}
public int CountryCode { get; set; }
public int LocalCode { get; set; }
public int Number { get; set; }
public override string ToString()
{
return String.Format(System.Globalization.CultureInfo.CurrentCulture, "+{0} ({1}) {2}", CountryCode, LocalCode, Number);
}
public static bool Validate(string value)
{
return new Regex(#"\+\w+ \(\w+\) \w+", RegexOptions.Compiled).IsMatch(value);
}
public static bool Validate(string countryCode, string localCode, string number, out PhoneNumber phoneNumber)
{
var valid = false;
phoneNumber = null;
try
{
ushort uCountryCode = 0;
ushort uLocalCode = 0;
int iNumber = 0;
// match only if all three numbers have been parsed successfully
valid = UInt16.TryParse(countryCode, out uCountryCode) &&
UInt16.TryParse(localCode, out uLocalCode) &&
Int32.TryParse(number, out iNumber);
if (valid)
phoneNumber = new PhoneNumber(uCountryCode, uLocalCode, iNumber);
}
catch (ArgumentException)
{
// still not match
}
return valid;
}
}

Categories