对于像LINQ这样的自定义顺序,如何在不指定第二类泛型的情况下创建泛型方法?

时间:2015-05-17 21:37:43

标签: c# linq generics lambda func

拥有模特:

public class MyCustomObject
{
    public int Id { get; set; }
    public string Description { get; set; }
    //A lot of properties
}

我需要通过自定义属性订购的查询queryableObjectsToOrder。 属性名称存储为字符串,可以是"Id""Description"

queryableObjectsToOrder.OrderBy(customSortCreatedInRuntime);

然后我创建了一个通用方法来构建Func to Order。

public Func<TSource, TResult> CreateCustomSort<TSource, TResult>(Type t, string fieldName)
{
    var propertyField = t.GetProperty(fieldName);
    if (propertyField != null)
    {
        var parameterExp = Expression.Parameter(t, "sel");
        var fieldProp = Expression.PropertyOrField(parameterExp, fieldName);
        var lambda = Expression.Lambda<Func<TSource, TResult>>(fieldProp, parameterExp);
        return lambda.Compile();
    }
    else
        return (obj) => { return default(TResult); };
}

我称之为并且有效:

string propertyToOrder = "Id";

var customSortCreatedInRuntime = CreateCustomSort<MyCustomObject, int>(typeof(MyCustomObject), propertyToOrder);
var resultList = queryableObjectsToOrder.OrderBy(customSortCreatedInRuntime);

但是如果我将字符串propertyToOrder更改为&#34;描述&#34;,我需要将对通用方法的调用的签名更改为:

string propertyToOrder = "Description";

var customSortCreatedInRuntime = CreateCustomSort<MyCustomObject, string>(typeof(MyCustomObject), propertyToOrder);

这是一个问题,因为我不知道存储为字符串的属性名称的类型。 如何在不指定类型TResult的情况下重写CreateCustomSort方法?

2 个答案:

答案 0 :(得分:1)

编译器无法通过运行时可用的信息(在您的情况下,string参数)静态验证类型安全表达式(在您的情况下是通用返回类型)。

但如果适合您,可以将其设为dynamic

答案 1 :(得分:1)

当我看到这样的问题时,我总是想知道:&#34;为什么?&#34;和&#34;这闻起来像XY Problem&#34;。什么阻止你做常规的类型安全lambda?

无论如何,最简单的方法是不构建lambda:

public Func<TSource, object> CreateCustomSort<TSource>(string fieldName)
{
    var prop = TypeDescriptor.GetProperties(typeof(TSource)).Find(fieldName, false);
    if (prop != null)
        return (x) => prop.GetValue(x, null);
    else
        return (x) => null;
}

如果要使用与手动键入的lambda表达式完全等效,则需要执行更复杂的操作,例如构建包含IQueryable表达式的新OrderBy实例。以下扩展方法执行此操作:

public static class OrderByExtensions
{
    public static IQueryable<TSource> OrderByField<TSource>(this IQueryable<TSource> query, string fieldName, bool isAscending = true)
    {
        var prop = TypeDescriptor.GetProperties(typeof(TSource)).Find(fieldName, false);
        if (prop == null)
            return query;

        var sourceExpr = Expression.Parameter(typeof(TSource), "source");
        var propExpr = Expression.Property(sourceExpr, prop.Name);
        var selectorExpr = Expression.Lambda(propExpr, sourceExpr);
        string method = isAscending ? "OrderBy" : "OrderByDescending";
        Type[] types = new Type[] { query.ElementType, selectorExpr.Body.Type };
        var orderByCallExpr = Expression.Call(typeof(Queryable), method, types, query.Expression, selectorExpr);
        return query.Provider.CreateQuery<TSource>(orderByCallExpr);
    }
}    

用法示例:

public static void Main(params string[] args)
{
    var myCustomObjects = new[] {
        new MyCustomObject() { Id = 10, Description = "Hello" },
        new MyCustomObject() { Id = 2, Description = "SO" },
        new MyCustomObject() { Id = 42, Description = "Abcde" }
    };

    var result = myCustomObjects
        .AsQueryable()
        .OrderByField("Description");

    foreach (var r in result)
        Console.WriteLine("{0} - {1}", r.Id, r.Description);
}

输出:

42 - Abcde
10 - Hello
2 - SO

注意:

构建这些查询表达式是很昂贵的,如果你想看到这个更复杂的代码段的任何性能优势,你应该按照TSource(并且有很多调用)将构建的表达式缓存在缓存中。