动态声明 Func<in T, out Result>

6

考虑以下内容:

var propertyinfo = typeof(Customer).GetProperty(sortExpressionStr);
Type orderType = propertyinfo.PropertyType;

现在我想声明

Func<int,orderType>

我知道直接操作 ordertype 是不可能的,因为它是在运行时确定的,但有没有什么变通方法呢?

这正是我想要做的:

var propertyinfo = typeof(T).GetProperty(sortExpressionStr);
Type orderType = propertyinfo.PropertyType;

var param = Expression.Parameter(typeof(T), "x");
var sortExpression = (Expression.Lambda<Func<T, orderType>>
   (Expression.Convert(Expression.Property(param, sortExpressionStr), typeof(orderType)), param));

我需要进行转换的原因是:

Expression<Func<T,object>> to Expression<Func<T,orderType>>

如果不可能,那么我想从一开始就使用正确的类型创建它,情况如下:
我在一个方法中,该方法有一个类型为“Customer”的属性名,我想按照它排序,我想创建一个排序表达式树将其传递给此处的“Orderby”。

你不能在编译时声明 sortExpression 是类型为 Expression<Func<T,orderType>> 的,因为 orderType 只有在运行时才能确定,而不是在编译时。你打算如何在编译后使用你的 lambda 表达式呢? - dtb
好的,那就不考虑 sortexpression,是否有可能通过属性名的字符串来排序? - Stacker
我已经扩展了我的回答。 - dtb
这似乎是相同的问题,已经在这里回答了。这种方法保持类型安全。 - Leon van der Walt
6个回答

8
您可以通过使用开放式泛型类型定义,然后从中创建特定类型来实现此操作:
typeof(Func<,>).MakeGenericType(typeof(int), orderType);

然而,您正在尝试的操作(调用 Lambda<TDelegate>)是不直接可能的。您必须不带类型参数调用 Lambda

var propertyinfo = typeof(T).GetProperty(sortExpressionStr);
Type orderType = propertyinfo.PropertyType;

var param = Expression.Parameter(typeof(T), "x");
var sortExpression = Expression.Lambda(
        Expression.Convert(Expression.Property(param, sortExpressionStr),
                           orderType), 
        param));

这将在幕后为您创建适当的 Func<,>。如果您想编译表达式并使用委托,您只能通过动态方式执行此操作。
sortExpression.Compile().DynamicInvoke(param);

如果您想在Queryable上调用OrderBy扩展方法,事情会变得有些复杂:
var propertyInfo = typeof(T).GetProperty(sortExpressionStr);
Type orderType = propertyInfo.PropertyType;

// first find the OrderBy method with no types specified
MethodInfo method = typeof(Queryable).GetMethods()
  .Where(m => m.Name == "OrderBy" && m.GetParameters().Length == 2)
  .Single();
// then make the right version by supplying the right types
MethodInfo concreteMethod = method.MakeGenericMethod(typeof(T), orderType);

var param = Expression.Parameter(typeof(T), "x");

// the key selector for the OrderBy method
Expression orderBy =
    Expression.Lambda(
        Expression.Property(orderParam, propertyInfo),
        orderParam);

// how to use:
var sequence = new T[0].AsQueryable(); // sample IQueryable

// because no types are known in advance, we need to call Invoke 
// through relection here
IQueryable result = (IQueryable) concreteMethod.Invoke(
                                   null, // = static
                                   new object[] { sequence, orderBy });

不好意思,typeof(orderType)是编译不通过的,我已经尝试过了。 - Stacker
typeof(orderType) 不合适,因为你试图在编译时 typeof,而 ordertype 只能在运行时确定。 - Stacker
只是好奇:为什么需要将类型为 orderType 的属性转换为 orderType 类型,使用 Expression.Convert? - dtb
@dtb 当你正在创建动态排序并且不知道将要传递哪个属性以及它的类型时,当它作为字符串传递给你时。 - Stacker
因为 DynamicInvoke 的返回值是一个无法推断其类型的对象。 - Stacker
显示剩余5条评论

7
您可以使用 Type.MakeGenericType 方法
Type result = typeof(Func<,>).MakeGenericType(typeof(int), orderType);

这应该可以工作:
public static IQueryable<T> OrderByField<T>(
    IQueryable<T> q, string sortfield, bool ascending)
{
    var p = Expression.Parameter(typeof(T), "p");
    var x = Expression.Lambda(Expression.Property(p, sortfield), p);

    return q.Provider.CreateQuery<T>(
               Expression.Call(typeof(Queryable),
                               ascending ? "OrderBy" : "OrderByDescending",
                               new Type[] { q.ElementType, x.Body.Type },
                               q.Expression,
                               x));
}

源自这里


我不确定这是否符合我的情况,并且我不知道如何测试它,无论如何,我已经解决了问题,还是谢谢您的时间和努力。 - Stacker
@Stacker:那么,你是如何解决这个问题的?其他人可能会遇到同样的问题。 - dtb
我已经发布了解决方案并说明了我的代码的问题。谢谢,伙计。 - Stacker

1
linqClass.OrderBy(GetSortExpression(sortstr));


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

这个方法有效,我的问题在于我传递了额外的参数Typeof(Object),而orderby告诉我它不能按照Object类型排序。感谢大家。

感谢dtb,我会检查你的答案是否有效,如果有效,我会接受它,如果无效,我会接受这个答案。


这对我不起作用。 List<Employee> list = new List<Employee>(); /填充列表/ 当我像这样调用时:list.OrderBy(GetSortExpression("MyColName")) - Prerak K

1

看看我的解决方案是否足够动态。

public class Product
{
    public long ID { get; set; }
    public string Name { get; set; }
    public DateTime Date { get; set; }
}


static void Main(string[] args)
{
    List<Product> products = (from i in Enumerable.Range(1, 10)
                          select new Product { ID = i, Name = "product " + i, Date = DateTime.Now.AddDays(-i) }).ToList();  //the test case

    const string SortBy = "Date";  // to test you can change to "ID"/"Name"

    Type sortType = typeof(Product).GetProperty(SortBy).PropertyType;     // DateTime
    ParameterExpression sortParamExp = Expression.Parameter(typeof(Product), "p");    // {p}
    Expression sortBodyExp = Expression.PropertyOrField(sortParamExp, SortBy);   // {p.DateTime}
    LambdaExpression sortExp = Expression.Lambda(sortBodyExp, sortParamExp);   //   {p=>p.DateTime}
    var OrderByMethod = typeof(Enumerable).GetMethods().Where(m => m.Name.Equals("OrderBy") && m.GetParameters().Count() == 2).FirstOrDefault().MakeGenericMethod(typeof(Product), sortType);
    var result = OrderByMethod.Invoke(products, new object[] { products, sortExp.Compile() });
}

基于上述内容,将 Product 更改为 T 以使其成为通用的并不困难。


如果我使用的是LINQ to Enumerable,那么我可以使用它,但如果我使用的是LINQ to SQL,那么这将导致获取记录并进行排序,因此如果您只需要取前10个并跳过其余的记录,这将会带来麻烦,因为您需要在SQL中使用OrderBy方法进行排序以检索记录! - Stacker

1

是的,这个方法可以工作,但我不想包含动态 Linq DLL。 - Stacker
它作为源文件分发,而不是程序集。您可以将其编译到您的程序集中,以避免任何额外的引用。请查看C:\ Program Files(x86)\ Microsoft Visual Studio 10.0 \ Samples \ 1033 \ CSharpSamples.zip,并查看压缩文件LinqSamples \ DynamicQuery \ DynamicQuery \ Dynamic.cs。 - sisve

0

如果您想将其传递到CreateDelegate中,您可以获取与Func<int,orderType>相关联的Type

但是您最终想要做什么?可能有更直接的方法。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接