表达式树的可枚举选择

3
我正在学习“表达式树”,但我无法执行以下表达式:
// first case
someList.Select(p => p.SomeProperty);

并且

// second case
someList.Select(p => new OtherClass 
{
    SomeProperty = p.SomeProperty
})

对于“第一个案例”,我尝试做了以下操作:

var someList = new List<SomeClass>();
someList.Add(new SomeClass { SomeProperty = "Hello" });

var someParam = Expression.Parameter(typeof(SomeClass), "p");
var someProperty = Expression.Property(someParam, "SomeProperty");

Expression.Call(
    typeof(Enumerable),
    "Select",
    new Type[]
    {
        typeof(SomeClass),
        typeof(string)
    },
    Expression.Lambda(
        someProperty,
        someParam
    )
).Dump();

但是我遇到了这个错误: InvalidOperationException: 在类型“System.Linq.Enumerable”上没有泛型方法“Select”与提供的类型参数和参数兼容。如果该方法是非泛型方法,则不应提供任何类型参数。 关于“第二种情况”,我不知道该如何继续。
有人能在这里指导我吗?

我认为你忘记了在 Expression.Call 中的 new Type[]Expression.Lambda 之间提供 IEnumerable<SomeClass> 参数。 - Will
你不能直接使用表达式树与 Enumerable.* 方法。你必须 .Compile() 它们,或者使用 IQueryable<>Queryable.* 方法。 - xanatos
2个回答

3

以下是您可以做的一些示例:

给定:

public class SomeClass
{
    public string SomeProperty { get; set; }
}

并且

var someList = new List<SomeClass>();
someList.Add(new SomeClass { SomeProperty = "Hello" });

var someParam = Expression.Parameter(typeof(SomeClass), "p");
var someProperty = Expression.Property(someParam, "SomeProperty");

Expression<Func<SomeClass, string>> lambda = Expression.Lambda<Func<SomeClass, string>>(someProperty, someParam); // p => p.SomeProperty

使用一个 IEnumerable<SomeClass>… 注意 .Compile()
Func<SomeClass, string> compiled = lambda.Compile();
IEnumerable<string> q1 = someList.Select(compiled);

您不应该在实际开发中使用AsQueryable(),除非是在单元测试和“试验性”程序(例如此程序)中使用。为了让@Peter满意,我将添加另一个可能的条件:如果您真正知道它的作用(而不是认为自己知道它的作用,真正知道!),那么您可以使用它。但如果这是您第一次使用它,我仍然建议您在SO上询问是否正确使用它。

IQueryable<SomeClass> queryable = someList.AsQueryable();

直接使用 Queryable.Select()
IQueryable<string> q2 = queryable.Select(lambda);

构建一个 Select 并使用 CreateQuery(这与内部的 Queryable.Select 非常相似)将其“注入”到查询中。

MethodInfo select = (from x in typeof(Queryable).GetMethods()
                    where x.Name == "Select" && x.IsGenericMethod
                    let gens = x.GetGenericArguments()
                    where gens.Length == 2
                    let pars = x.GetParameters()
                    where pars.Length == 2 && 
                        pars[0].ParameterType == typeof(IQueryable<>).MakeGenericType(gens[0]) &&
                        pars[1].ParameterType == typeof(Expression<>).MakeGenericType(typeof(Func<,>).MakeGenericType(gens))
                    select x).Single().MakeGenericMethod(typeof(SomeClass), typeof(string));

MethodCallExpression select2 = Expression.Call(null, select, Expression.Constant(queryable), lambda);

IQueryProvider provider = queryable.Provider;
IQueryable<string> q3 = provider.CreateQuery<string>(select2);

1
为什么我们应该“永远不要在单元测试和实验程序之外使用AsQueryable()”?鉴于这样强烈的说法,如果您能用简短的解释来支持它,那就更好了。 - Peter Lillevold
@PeterLillevold 正确的说法应该是“除非你知道自己在做什么,否则不应该使用它”,但很少有人会承认他们一直在试错直到编译成功,所以使用绝对语气更容易...比如“除非你是游戏程序员或正在创建PDF,否则不要使用floatdouble”。 - xanatos
好的,因此我认为你的陈述非常主观,没有突出任何与OP问题相关的内容。使用绝对语言不会增加认识和理解,除非你用充分的理由来支持它。 - Peter Lillevold
再次强调,需要有一个“为什么”来支持你的观点。从技术上讲,使用AsQueryable()存在哪些风险?我理解你认为90%的C#程序员只是在向编译器抛代码,你有权利持有这种观点。但是这种观点与本问题几乎没有关系。 - Peter Lillevold
@PeterLillevold 没有风险风险是当您推迟可能为空的指针时发生的。这更像是“如果您想使用它,请阅读它的功能,我们不会谈论它”...我相信在SO上有关于它的问题。这个问题不是关于AsQueryable(),但只是因为我使用了它,有人会复制粘贴我的代码到他的代码中,并使用AsQueryable()。我使用了一个不好的模式,并明确表示这是一个不好的模式,并且仅仅因为我正在使用它来展示某些东西(使用IQueryable<>),而不是在生产读取代码中使用它。 - xanatos
显示剩余5条评论

3

大家冷静,经过一些研究我找到了代码中缺失的部分...

第一个问题:

Expression.Call(
    typeof(Enumerable),
    "Select",
    new Type[]
    {
        typeof(SomeClass),
        typeof(string)
    },
    Expression.Constant(someList), // <---------------- HERE IT IS
    Expression.Lambda(
        someProperty,
        someParam
    )
);

对于第二种情况,我通过以下代码创建了“new”表达式:
var bind = Expression.Bind(typeof(OtherClass).GetProperty("SomeProperty"), someProperty);
var otherClassNew = Expression.New(typeof(OtherClass));
var otherClassInit = Expression.MemberInit(otherClassNew, bind);

无论如何,感谢大家的帮助!

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