在另一个lambda表达式中使用lambda表达式

6
我需要将两个lambda表达式结合起来,使第二个表达式“包含”第一个表达式。 我搜索了很多,但没有找到任何明确的答案...
我试图做的是: 将第一个表达式“expression1”作为参数传递给一个方法,并且仅用于定义第二个lambda表达式必须操作的字段或属性。
简单地说,我正在尝试做以下事情:
// simple field selector :
Expression<Func<T, string>> expression1 = obj => obj.field; 

// trying to use the field selector in 2nd expression :
Expression<Func<T, bool>> e2 = obj => [[Result of expression1]].Equals("myValue");

换句话说,我想要得到:
Expression<Func<T, bool>> e2 = obj => obj.field.Equals("myValue"); 

我需要这样做,因为不总是会调用Equals()方法,而是会调用许多不同的方法。
我试图将expression1编译为Func<T, string>,以便在expression2中调用此函数,但由于我正在使用LinQ,所以我会得到一个异常,因为LinQ不支持Invoke节点类型。
所以我的问题是:是否有一种方法可以仅组合两个表达式的主体,如何实现?
感谢您的提前帮助!
2个回答

5

好的,这应该可以解决问题:

void Main()
{
    var execute = Create<TestClass>(
            first => first.MyStringField, // field selector
            second => second.Equals("1234") // method selector
        );

    Console.WriteLine(execute(new TestClass("1234"))); // true
    Console.WriteLine(execute(new TestClass("4321"))); // false
}

class TestClass
{
    public string MyStringField;

    public TestClass(string val){
        MyStringField = val;
    }

}

static Func<TSource, bool> Create<TSource>(
                    Expression<Func<TSource, string>> fieldSelector,
                    Expression<Func<string, bool>> methodSelector
                )
{
    // todo: classical validation of fieldSelector, if necessary.
    // todo: classical validation of methodSelector, if necessary.

    var compiledFieldSelector = fieldSelector.Compile();
    var compiledMethodSelector = methodSelector.Compile();

    return T => compiledMethodSelector(compiledFieldSelector(T));
}

请注意,由于Lambda表达式的性质,您需要验证字段选择器和方法选择器,否则可能会做一些非常奇怪的事情。

或者,下一种方法创建的Lambda不会“组合”事物,这意味着这更好,因为LinqToEntities不应该有问题解释查询。

 static Func<TSource, bool> Create<TSource>(
                    Expression<Func<TSource, string>> memberSelector,
                    Expression<Func<string, bool>> methodSelector
                )
{
    // todo: classical validation of memberSelector, if necessary.
    // todo: classical validation of methodSelector, if necessary.

    var memberExpression = (MemberExpression)(memberSelector.Body);
    var methodCallExpression = (MethodCallExpression)(methodSelector.Body);

    // input TSource => ..
    var input = Expression.Parameter(typeof(TSource));

    var call = Expression.Call(
                Expression.MakeMemberAccess(
                                input, 
                                memberExpression.Member), 

                methodCallExpression.Method, 
                methodCallExpression.Arguments);

    return Expression.Lambda<Func<TSource, bool>>(call, input)
                .Compile();
}

谢谢Chris Eelmaa。您的解决方案在传统语境下非常有效。但是,由于我正在使用LinQ,因此在运行时我会得到以下System.NotSupportedException:在实体的LINQ中不支持LINQ表达式节点类型“Invoke”。 - Numer_11
@Numer_11:更新了帖子。这是你要找的吗? - Erti-Chris Eelmaa
是的!我测试了你的新答案,但仍然出现异常。之后我移除了.Compile()调用,并更改了Create方法的返回类型以匹配。最终通过这个小修改,它在LinQ中完美地工作了!非常感谢!(发布修改以便其他人清楚) - Numer_11

2

Chris Eelmaa的第二个回答是可以的,只需删除.Compile()调用即可避免使用Invoke时出现异常:

static Expression<Func<TSource, bool>> Create<TSource>(
                    Expression<Func<TSource, string>> memberSelector,
                    Expression<Func<string, bool>> methodSelector
                )
{
    // todo: classical validation of memberSelector, if necessary.
    // todo: classical validation of methodSelector, if necessary.

    var memberExpression = (MemberExpression)(memberSelector.Body);
    var methodCallExpression = (MethodCallExpression)(methodSelector.Body);

    // input TSource => ..
    var input = Expression.Parameter(typeof(TSource));

    var call = Expression.Call(
                Expression.MakeMemberAccess(
                                input, 
                                memberExpression.Member), 

                methodCallExpression.Method, 
                methodCallExpression.Arguments);

    return Expression.Lambda<Func<TSource, bool>>(call, input);
}

在我的情况下,它会被用于以下方式: (selector 是一个表达式,例如 u => u.id, 而request是一个IQueryable<T>,两者都作为参数接收)
Expression<Func<T, bool>> contains = Create<T>(selector, u => u.Contains(searchExpression));
IQueryable<T> result = request.Where(contains);

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