5
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Net;
using System.Text;
using System.Text.RegularExpressions;
using System.Web.UI.WebControls;
using dynamic = System.Linq.Dynamic;
using System.Linq.Expressions;

namespace Project.Lib.Extensions
{
    public static partial class Utils
    {
        public static List<T> SortForMe<T>(this List<T> list, string propertyName,SortDirection sortDirection)
        {
            string exp1 = string.Format("model.{0}", propertyName);
            var p1 = Expression.Parameter(typeof(T), "model");
            var e1 = dynamic.DynamicExpression.ParseLambda(new[] { p1 }, null, exp1);

            if (e1 != null)
            {
                if (sortDirection==SortDirection.Ascending)
                {
                    var result = list.OrderBy((Func<T, object>)e1.Compile()).ToList();
                    return result;
                }
                else
                {
                    var result = list.OrderByDescending((Func<T, object>)e1.Compile()).ToList();
                    return result;
                }
            }
            return list;
        }
    }
}

I am using this code for sorting my Generic List by propertyName. When the property type is string, this code runs successfully, but when the type is long or int, I am getting this exception:

Unable to cast object of type 'System.Func`2[Project.Lib.Model.UserQueryCount,System.Int64]' to type 'System.Func`2[Project.Lib.Model.UserQueryCount,System.Object]'.


var result = list.OrderBy((Func<T, dyamic>)e1.Compile()).ToList();

In the line above, I decided using dynamic, but got the exception again. What should I do?


I changed my method like this:

public static List<TModel> SortForMe<TModel>(this List<TModel> list, string propertyName,SortDirection sortDirection) where TModel:class
    {
        var ins = Activator.CreateInstance<TModel>();
        var prop= ins.GetType().GetProperty(propertyName);
        var propertyType =  prop.PropertyType;

    string exp1 = string.Format("model.{0}", propertyName);
    var p1 = System.Linq.Expressions.Expression.Parameter(typeof(TModel), "model");
    var e1 = System.Linq.Dynamic.DynamicExpression.ParseLambda(new[] { p1 }, null, exp1);

    if (e1 != null)
    {
        if (sortDirection==SortDirection.Ascending)
        {
            return list.OrderBy((Func<TModel, propertyType>)e1.Compile()).ToList();
        }

        return list.OrderByDescending((Func<TModel, propertyType>)e1.Compile()).ToList();
    }
        return list;
}

I got propertyType using reflection but in Func I couldn't use it like this: "Func<TModel, propertyType>" Is there any way to resolve this problem

Thanks for the help.

Müslüm ÖZTÜRK
  • 961
  • 1
  • 11
  • 30
  • haven't got time to go bug hunting, but you should be able to steal most of the lambda code from here: http://stackoverflow.com/questions/41244/dynamic-linq-orderby - very similar question – Marc Gravell Aug 03 '12 at 12:59
  • 2
    also, `using dynamic = System.Linq.Dynamic;` is really confusing; `dynamic` is contextual keyword; strongly suggest you pick another alias... or just don't use the alias at all – Marc Gravell Aug 03 '12 at 13:01
  • "using dynamic = System.Linq.Dynamic;" I deleted this. And I changed "var e1 = System.Linq.Dynamic.DynamicExpression.ParseLambda(new[] { p1 }, null, exp1);" Thanks for your suggestions – Müslüm ÖZTÜRK Aug 03 '12 at 13:14

2 Answers2

2

The reason it doesn't work is indeed boxing, as mentioned in another answer.

class S
{
  public int f;
  public S s;
}

{
  Func<S, S> sGetter = s => s.s; // okay
  Func<S, object> objectGetter = s => s.s; // okay
  objectGetter = sGetter; // also okay
}

{
  Func<S, int> intGetter = s => s.f; // okay
  Func<S, object> objectGetter = s => s.f; // still okay
  objectGetter = intGetter; // not okay
}

The reason that Func<S, object> objectGetter = s => s.f works but the last assignment cannot work is that it actually gets interpreted as Func<S, object> objectGetter = s => (object)s.f, and that cast performs a real operation. The intGetter and objectGetter don't do the same thing, so one cannot be reinterpreted as the other. Realising this, you should be okay if you include that cast yourself. As far as I can tell, DQL (that is what you're using, right?) will do that for you if you simply specify that your desired return type is object:

var e1 = DynamicExpression.ParseLambda(new[] { p1 }, typeof(object), exp1);
0

I suspect this has something to do with the boxing of value types, but I cannot be really sure. However, there is a workaround that involves no boxing and both solves your issue and makes your method a bit easier to use.

Instead of string that identifies a property to sort by, you can actually use the lambda expression returning the property. That can be done with following method:

private static string GetPropertyName<TObject, TProperty>(Expression<Func<TObject,     TProperty>> property)
{
    MemberExpression memberExpression = (MemberExpression)property.Body;
    PropertyInfo propertyInfo = (PropertyInfo)memberExpression.Member;

    return propertyInfo.Name;
}

Notice that I've wrapped the Func delegate into the Expression type (you may want to read more about it). That way, I can interrogate the expression and find out which members does it use. Of course, this will fail for all but simple x => x.Property lambdas, so you may want to do some error checking.

To use it, you will have to change your extension method a little:

public static List<TObject> SortForMe<TObject, TProperty>(
    this List<TObject> list, 
    Expression<Func<TObject, TProperty>> property, 
    SortDirection sortDirection)
{
     string propertyName = GetPropertyName(property);
     ...
     ...          
     // when casting compiled expression to Func, instead of object, use TProperty
     // that way, you will avoid boxing
     var result = list.OrderBy((Func<T, TProperty>)e1.Compile()).ToList();
     ...
     ...
}

The actual usage could look like the following:

List<Foo> foos = ... ;
foos.SortForMe(x => x.IntegerProperty, SortDirection.Ascending);
foos.SortForMe(x => x.StringProperty, SortDirection.Ascending);

The IntegerProperty and StringProperty are actual properties defined on Foo type. There is no need to specify the actual type parameters when using the method - C#'s type inference comes into play here really nicely.

Nikola Anusev
  • 6,940
  • 1
  • 30
  • 46