Generic Func <T, K> for sorting collections of different types

I have many collections for many types. I want to sort each collection by different properties. For example, it IEnumerable<Employee>

will be sorted by properties Name

and Age

, and IEnumerable<Department>

will be sorted by properties NumberOfEmployees

and DepartmentName

. I am using PaginatedList

pagination after sorting.

public class PaginatedList<T> : List<T>
{
  public PaginatedList(IEnumerable<T> source, Int32 pageIndex, Int32 pageSize , Func<T,Object> orderBy)
  {
    this.AddRange(source.OrderBy(orderBy).Skip((PageIndex - 1) * PageSize).Take(PageSize));
  }
}

      

Notice the 4th parameter, which is the sort delegate that will be passed to the OrderBy extension method.

I am using generic method to create this 4th item

public Func<T, Object> SortingFactory<T>(String sortby) 
{
  switch (typeof(T).ToString())
  {
    case "Employee":
      switch(sortby)
      {
        case "Name":
           return new Func<Employee,String>(delegate(Employee e) { return e.Name; });
           break;                            
        case "Age":
           return new Func<Employee,Int32>(delegate(Employee e) { return e.Age; });
           break;
      }
      break;
    case "Department":
      switch(sortby)
      {
        case "NumberOfEmployees":
           return new Func<Department,Int32>(delegate(Department d) { return d.NumberOfEmployees; });
           break;                            
        case "DepartmentName":
           return new Func<Department,String>(delegate(Department d) { return d.DepartmentName; });
           break;
      }
      break;
  }
}

      

but it gives me Error compilation Cannot implicitly convert type 'System.Func<Employee,String>' to 'System.Func<T,object>'

I also tried to decalre the output like Func<Object,Object>

, but I got the same error.

What mistake did I make and how to do it.

+3


source to share


4 answers


Let's say I understood well

public class PaginatedList<T> : List<T>
{
  public PaginatedList(IEnumerable<T> source, Int32 pageIndex, Int32 pageSize )
  {
    this.AddRange(GetOrderFor<T>().Skip((PageIndex - 1) * PageSize).Take(PageSize));
  }
}

public static class Helpers
    {

        public static Func<T, object> GetSortExpression<T>(string sortExpressionStr)
        {
            var param = Expression.Parameter(typeof (T), "x");
            var sortExpression = Expression.Lambda<Func<T, object>>(Expression.Convert(Expression.Property(param, sortExpressionStr), typeof(object)), param);
            return sortExpression.Compile();
        }

        public static IOrderedEnumerable<T> GetOrderFor<T>(this IEnumerable<T> list)
        {
            switch (typeof (T).Name)
            {
                case "Employee":
                    return list.OrderBy(GetSortExpression<T>("Name")).ThenBy(GetSortExpression<T>("Age"));
                case "Category":
                    return list.OrderBy(GetSortExpression<T>("Name")).ThenBy(GetSortExpression <T> ("Id"));
            }
            return null;
        }
    }

      



And if I misunderstood, I think that simply using the GetSortExpression method will help you avoid the error

case "Employee":
      switch(sortby)
      {
        case "Name":
           return Helpers.GetSortExpression<T>("Name");                           
        case "Age":
           return Helpers.GetSortExpression<T>("Age");
      }

      

+1


source


The main problem with your code has to do with trying to convert between typical types. The solution given here is to avoid this.

You must use the class Comparer<T>

. Below is an example of sorting Employee

s:

class EmployeeComparer : Comparer<Employee>
{
    string property;

    public EmployeeComparer(string Property)
    {
        this.property = Property;
    }

    public override int Compare(Employee x, Employee y)
    {
        switch (this.property)
        {
            case "Name":
                return Comparer<string>.Default.Compare(x.Name, y.Name);
            case "Age":
                return Comparer<int>.Default.Compare(x.Age, y.Age);
            default:
                return 0;
        }
    }
}

      

DepartmentComparer

will be very similar.

EDIT: field property

abstracted by base class with modified EmployeeComparer

:

abstract class PropertyComparer<T> : Comparer<T>
{
    protected string property;

    public PropertyComparer(string Property)
    {
        this.property = Property;
    }
}

class EmployeeComparer : PropertyComparer<Employee>
{
    public EmployeeComparer(string Property) : base(Property)
    {

    }

    public override int Compare(Employee x, Employee y)
    ...
}

      



Then you can write a function to get the mapping for the type you want:

Comparer<T> GetComparer(string Property)
{
    // Sadly, you cannot switch on a Type
    if (typeof(T) == typeof(Employee))
    {
        return new EmployeeComparer(Property) as Comparer<T>;
    }
    else if (typeof(T) == typeof(Department))
    {
        return new DepartmentComparer(Property) as Comparer<T>;
    }
    else
    {
        return Comparer<T>.Default;
    }
}

      

This will most likely belong to the class PaginatedList

:

public class PaginatedList<T> : List<T>
{
    Comparer<T> GetComparer(string Property)
    ...

    public PaginatedList(IEnumerable<T> source, int pageIndex, int pageSize, string orderBy)
    {
        Comparer<T> comparer = GetComparer(orderBy);
        this.AddRange(source.OrderBy(x => x, comparer).Skip((PageIndex - 1) * PageSize).Take(PageSize));
    }
}

      

NTN. I haven't tested it, but if you find a bug, just comment.

+1


source


You want a more general version SortingFactory

that will return any type of lambda. This basically converts the string to a strongly typed expression suitable for sorting:

public Expression<Func<T, To>> SortingFactory<T, To>( String sortby )
{
    // Entity type
    System.Type dataType = typeof( T );

    // Entity - main parameter (x =>
    ParameterExpression rootExp = Expression.Parameter(dataType, "x" );

    // property (x => x.Property
    PropertyInfo pi = dataType.GetProperty( sortby );

    // put together
    Expression expr = Expression.Property( rootExp, pi );
    return Expression.Lambda<Func<T, To>>( expr, rootExp );
}

      

I don't have it, but you can check that pi

it is not null. It also assumes that the input string is a scalar property and not an entity or collection, which gets a little more complicated.

+1


source


Cannot implicitly convert type 'System.Func<Employee,String>' to 'System.Func<T,object>'

      

This error tells you that it Func<X, string>

doesn't inherit from Func<X, object>

(even if the string inherits from the object). This is a very common Generics mistake! List<Customer>

does not inherit from List<object>

. If it did, you could get away with this:

List<Customer> c = new List<Customer>();
List<object> x = (List<object>) c;
x.Add(x)
//List<object> is-a object, so the statement is valid,
//  but a List<Customer> is not a Customer, breaks the instance referenced by c
// instead of breaking c instance, you get a runtime exception on line 2 - invalid cast.

      


Many of the answers refer to expression manifest materials. I feel like the hammer is too heavy to swing in this problem ...

What you need to do is remove / hide this second general setting as I did here .

public interface IOrderer<T>
{
  IOrderedEnumerable<T> ApplyOrderBy(IEnumerable<T> source);
  IOrderedEnumerable<T> ApplyOrderByDescending(IEnumerable<T> source);
  IOrderedEnumerable<T> ApplyThenBy(IOrderedEnumerable<T> source);
  IOrderedEnumerable<T> ApplyThenByDescending(IOrderedEnumerable<T> source);
} 

public class Orderer<T, U> : IOrderer<T>
{
  private Func<T, U> _orderFunc;
  public Orderer(Func<T, U> orderFunc)
  { _orderFunc = orderFunc; }
  public IOrderedEnumerable<T> ApplyOrderBy(IEnumerable<T> source)
  { return source.OrderBy(_orderFunc); }
  public IOrderedEnumerable<T> ApplyOrderByDescending(IEnumerable<T> source)
  { return source.OrderByDescending(_orderFunc); }
  public IOrderedEnumerable<T> ApplyThenBy(IOrderedEnumerable<T> source)
  { return source.ThenBy(_orderFunc); }
  public IOrderedEnumerable<T> ApplyThenByDescending(IOrderedEnumerable<T> source)
  { return source.ThenByDescending(_orderFunc); }
}  

      

Then your method:

public class PaginatedList<T> : List<T>
{
  public PaginatedList(
    IEnumerable<T> source,
    Int32 pageIndex, Int32 pageSize,
    IOrderer<T> orderer)
  {
    IEnumerable<T> query = orderer.ApplyOrderBy(source)
      .Skip((PageIndex - 1) * PageSize)
      .Take(PageSize)  
    this.AddRange(query);
  }
} 

      

Or, if you need multiple sorting (which will inevitably happen):

public class PaginatedList<T> : List<T>
{
  public PaginatedList(
    IEnumerable<T> source,
    Int32 pageIndex, Int32 pageSize,
    List<IOrderer<T>> orderers)
  {
    IEnumerable<T> query = source;

    if (orderers.Any())
    {
      IOrderer<T> firstOrder = orderers.First();
      IOrderedEnumerable<T> orderedQuery = firstOrder.ApplyOrderBy(source);
      foreach(IOrderer<T> nextOrder in orderers.Skip(1))
      {
        orderedQuery = nextOrder.ApplyThenBy(orderedQuery);
      }
      query = orderedQuery;
    }

    this.AddRange(query.Skip((PageIndex - 1) * PageSize).Take(PageSize));
  }
} 

      

After that you just need to add some kind of property to the IOrderer and Orderer so that it knows if it should be asc / desc and you should order everything wrapped.


(more fun, your sorting factory)

public IOrderer<T> SortingFactory<T>(String sortby)
{
  switch (typeof(T).ToString())
  {
    case "Employee":
       switch(sortby)
       {
         case "Name":
            return new Orderer<Employee, string>(e => e.Name); //hmm, not sure this will work.
            break;  

      

+1


source







All Articles