在 c# 中将表达式的一部分定义为变量

本文关键字:一部分 定义 变量 表达式 | 更新日期: 2023-09-27 18:32:06

我有以下代码:

public class MyClass<T>
{
   Expression<Func<T,bool>> Criteria {get; set;}
}
public class Customer
{
   //..
   public string Name {get; set;}
} 

并按如下方式使用它:

var c = new MyClass<Customer>();
c.Criteria = x.Name.StartWith("SomeTexts");

有没有办法定义这样的东西:

? p = x=>x.Customer.Name;
var c = new MyClass<Customer>();
c.Criteria = p => p.StartWith("SomeTexts");

我使用 Expression<Func<T,bool>> 将其用作linq to entities查询中的 where 子句(首先是 EF 代码)。

在 c# 中将表达式的一部分定义为变量

您可以使用以下帮助程序函数(可以给它们一个更好的名称,但这不是必需的):

public static class ExpressionUtils
{
    public static Expression<Func<TOuter, TResult>> Bind<TOuter, TInner, TResult>(this Expression<Func<TOuter, TInner>> source, Expression<Func<TInner, TResult>> resultSelector)
    {
        var body = new ParameterExpressionReplacer { source = resultSelector.Parameters[0], target = source.Body }.Visit(resultSelector.Body);
        var lambda = Expression.Lambda<Func<TOuter, TResult>>(body, source.Parameters);
        return lambda;
    }
    public static Expression<Func<TOuter, TResult>> ApplyTo<TInner, TResult, TOuter>(this Expression<Func<TInner, TResult>> source, Expression<Func<TOuter, TInner>> innerSelector)
    {
        return innerSelector.Bind(source);
    }
    class ParameterExpressionReplacer : ExpressionVisitor
    {
        public ParameterExpression source;
        public Expression target;
        protected override Expression VisitParameter(ParameterExpression node)
        {
            return node == source ? target : base.VisitParameter(node);
        }
    }
}

让我们看看示例表达式如何

c.Criteria = x => x.Name.StartsWith("SomeTexts");

可以从两个不同的部分构建。

如果你有

Expression<Func<Customer, string>> e = x => x.Name;

然后

c.Criteria = e.Bind(x => x.StartsWith("SomeTexts"));

或者如果你有这个

Expression<Func<string, bool>> e = x => x.StartsWith("SomeTexts");

然后

c.Criteria = e.ApplyTo((Customer x) => x.Name);

如果您同时具有这两个表达式,则可以使用这两个函数中的任何一个,因为a.Bind(b)等效于b.ApplyTo(a)

您必须显式定义类型变量,但下一个代码将帮助您解决您的方案:

// define new expression that get an Order object and returns string value
Expression<Func<Order, string>> p = x => x.Customer.Name;
var c = new MyClass<Order>();
// Compile the expression to the Func then invoke it and call extra criteria
c.Criteria = o => p.Compile().Invoke(o).StartsWith("SomeText");

没有表达式的解决方案更简单:

Func<Order, string> p = x => x.Customer.Name;
var c = new MyClass<Order>();
c.Criteria = o => p(o).StartsWith("SomeText");

您也可以在MyClass中使用Func<>而不是Expression<>

public MyClass<T>
{
   Func<T,bool> Criteria {get; set;}
}

我在这里看不到使用Expression的好处。直接Func怎么样?

public class MyClass<T> 
{
    public Func<T, string, bool> Criteria { get; set; }
}

然后。。。

var myCustomer = new MyClass<Customer>
{
    Criteria = (c, s) => c.Name.StartsWith(s)
};
var customer = new Customer { Name = "Bob" };
var x = myCustomer.Criteria(customer, "B");

如果需要表达式,则可以使用 LinqKit 执行以下操作:

Expression<Func<Customer, string>> p = x => x.Name;
var c = new MyClass<Customer>();
c.Criteria = x => p.Invoke(x).StartsWith("asd"); //Reuse p expression
c.Criteria = c.Criteria.Expand();

Invoke 是 LinqKit 提供的一种扩展方法,可帮助您轻松编写表达式。

调用 Expand 方法后,c.Criteria将包含一个表达式,该表达式与您执行此操作完全相同:

c.Criteria = x => x.Name.StartsWith("asd");