0

I have a bit of a challenge. I need to sort a List of objects, and I need to sort it from a string representing the path to the property in any sub class. I need to use the List.Sort() and not OrderBy().

Lest make a simple example. I have a list of persons represented by two sub classes for identification and name

public class NameParts
{
    public String FirstName { get; set; }
    public String LastName { get; set; }
}

public class Identification
{
    public String NiNumber { get; set; }
    public NameParts Name { get; set; }
}

public class Person
{
    public String Email { get; set; }
    public String Phone { get; set; }
    public Int16 Age { get; set; }

    public Identification Id { get; set; }
}

Now I need to sort the list by age. Very simple

 public static void SortByAge(List<Person> listToSort)
 {
     listToSort.Sort((x, y) => x.Age.CompareTo(y.Age));
 }

And even by NiNumber and FirstName it is fairly simple this way

 public static void SortByNiNumber(List<Person> listToSort)
 {
      listToSort.Sort((x, y) => x.Id.NiNumber.CompareTo(y.Id.NiNumber));
 }

 public static void SortByFirstName(List<Person> listToSort)
 {
      listToSort.Sort((x, y) => x.Id.Name.FirstName.CompareTo(y.Id.Name.FirstName));
 }

Now comes the tricky part. I need to perform all the above sorts giving a string that represents the path to theproperty to sort by. Like "Id.Name.FirstName"

So I need

 public static void SortByAny(List<Person> listToSort, String sortBy)
 {
   //??????
 }

That can be called with

 List<Person> theList = new List<Person>();
 SortByAny(theList, "Age");
 SortByAny(theList, "Id.NiNumber");
 SortByAny(theList, "Id.Name.FirstName");

I know I need to use reflection for this, and I have managed to do so but I cannot get further than properties in the Person Class itself, so I probably need to do something else, and this is where I'm stuck.

Does anyone have some brilliant ideas on how to solve this?

Thanks

Beaker
  • 227
  • 2
  • 14
  • You could probably use part of this solution: http://stackoverflow.com/questions/4473928/c-sharp-dynamic-string-property-path – E. Mourits Jul 01 '16 at 06:37
  • Can you explain why you need this? Maybe there is another solution to your problem – Maxim Kosov Jul 01 '16 at 07:27
  • I need it because I would prefer a single sort method over implementing sort method i each of 10-100 classes with Switches for all sort options. of course that would work well, but gives me a lot of code to maintain – Beaker Jul 01 '16 at 07:41
  • Why can't you work with the approach @E.Mourits linked? – thehennyy Jul 01 '16 at 07:42
  • I'm trying to see if I can get something out of that. But I'm not quite a master in reflection though :) – Beaker Jul 01 '16 at 07:51

3 Answers3

0

Because getting properties by reflection is quite hard to do, just use and modify this code:

public static void SortByAny(List<Person> listToSort, String sortBy)
{
    if (sortBy == "Email")
        listToSort.Sort((x, y) => x.Email.CompareTo(y.Email));
    else if (sortBy == "Phone")
        listToSort.Sort((x, y) => x.Phone.CompareTo(y.Phone));
    else if (sortBy == "Age")
        listToSort.Sort((x, y) => x.Age.CompareTo(y.Age));
    else if (sortBy == "Id.NiNumber")
        listToSort.Sort((x, y) => x.Id.NiNumber.CompareTo(y.Id.NiNumber));
    else if (sortBy == "Id.Name.FirstName")
        listToSort.Sort((x, y) => x.Id.Name.FirstName.CompareTo(y.Id.Name.FirstName));
    else if (sortBy == "Id.Name.LastName")
        listToSort.Sort((x, y) => x.Id.Name.LastName.CompareTo(y.Id.Name.LastName));
}
  • Thanks for the suggestion This was more or less what I came up with, but the problem is with drilling down deeper.. – Beaker Jul 01 '16 at 07:37
0

Do you really need to put reflection in place? If u need to sort the list by a specific and determined number of "paths" i would suggest you implementing it in a fixed way, going straight ahead with sorting. maybe a switch might help?

switch(sortByString){
    case: "Id.NiNumber":
    SortByNiNumber(List<Person> listToSort);
    break;
    ...
}

if you don't have too many options it would be faster. And maybe you can replace the switch with a dictionary of search paths and delegates or actions.

  • I do it like that now. But this will have to work on any class and not just Person, and I need it to not have to write all sort expressions for all classes... – Beaker Jul 01 '16 at 07:39
  • Mmh... Guess you should give a bit more of background then. Usually, when i have to do something so "universal" the answer is just changing the point of view and rethink at it. – Emanuele Andreoli Jul 01 '16 at 07:42
0

You can modify the approach @E.Mourits linked: C# dynamic. String property path. I included a bit of error checking, on error you have to check the InnerException of the InvalidOperationException the Sort method can throw.

static void SortByAny<T>(List<T> list, string path)
{
    list.Sort((x, y) => ReflectOnPath(x, path).CompareTo(ReflectOnPath(y, path)));
}

static IComparable ReflectOnPath(object o, string path)
{
    object value = o;
    var pathComponents = path.Split('.');
    foreach (var component in pathComponents)
    {
        if (value == null)
        {
            throw new NullReferenceException($"Path '{path}' can not be resolved at: {component}.");
        }
        var prop = value.GetType().GetProperty(component);
        if (prop == null)
        {
            throw new ArgumentException($"Path '{path}' can not be resolved at: {component}.", nameof(path));
        }
        value = prop.GetValue(value, null);
    }
    if (!(value is IComparable))
    {
        throw new ArgumentException($"Value at path '{path}' does not implement ICompareable.", nameof(path));
    }
    return (IComparable)value;
}

If some of the values you want to compare do not implement IComparable you have to add more details how you want to compare them in this case.

Community
  • 1
  • 1
thehennyy
  • 4,020
  • 1
  • 22
  • 31
  • Only issue with these kind of solutions is Performance. Dont believe there is much to optimize with this. – Beaker Jul 01 '16 at 09:59