为什么要使用表达式树

6

我不确定为什么要研究表达式树,但我正在阅读相关内容。因此,我对该对象没有理解,也不知道它在哪里或如何使用。

从其他问题中了解到,例如什么是表达式树,如何使用以及为什么要使用它们?(以及他们引用的链接),现在我对表达式树有一点了解,但是我仍然不明白为什么要使用它。

唯一我见过的关于为什么使用表达式树的例子是像Linq to SQL这样的东西,但是当你更深入地研究时,似乎实际上更多地涉及与IQueryable<T>接口的使用。

表达式树仅在与 IQueryable 接口结合使用时有用吗?


5
这是一个乐高积木,也许有一天你建造自己的死星时会派上用场。微软需要它来实现LINQ和 动态 关键字。把它放在脑后,也许有一天你也需要动态生成代码。你有 System.CodeDom 这种高级方法,还有 Reflection.Emit 这种低级方法,表达式树则是中间解决方案。 - Hans Passant
3个回答

9
有点循环论证,但答案是“每当你需要理解表达式时”。来自初级开发者的常见问题是,“Func与Expression>之间有什么区别?” 对于大多数人而言,后者需要在表达式上调用Compile()以获取它所代表的函数,但否则它们似乎完成了相同的工作。
表达式树使编译器将通常作为指令的内容嵌入可浏览的表达式树中。如果您正在编写某些需要了解调用方如何构造其函数而不仅仅是函数结果的内容,则表达式树可以为您捕获这一点。
在区别变得相关之前,您必须需要知道表达式是如何组成的。
我只看到过表达式被用于两种方式:
1. 创建LINQ提供程序(例如LINQ-to-SQL)来解释表达式并将其转换为另一种语言。就像您已经知道的那样,这涉及实现IQueryable并且是一项重要任务。
2. 从对象中捕获属性访问器(例如Razor HTML Helper),以便不仅可以访问该值,还可以使用该属性的名称生成其他内容(例如HTML字段名称)。
通常任务倾向于对另一种语言进行某种形式的翻译。对于LINQ提供程序而言,它是将查询直接转换为另一种查询语言。对于Razor而言,它是将属性转换为HTML字段。

4

表达式树经常用于在运行时动态生成代码。假设你需要创建很多未知类型的实例。你可以使用反射来创建它们,但是性能会很差,或者你可以创建一个表达式树并将其编译为方法。那个方法的性能将与使用Visual Studio编译它的性能相同。

public static class TypeFactory
{
    private static Dictionary<Type, object> dictionary = new Dictionary<Type, object>();

    public static Func<TType> CreateFactory<TType>() {
        object factory;
        var typeofType = typeof(TType);

        if (dictionary.TryGetValue(typeofType, out factory)) {
            return (Func<TType>)factory;
        }

        return CreateCachedFactory<TType>(typeofType);
    }

    private static Func<TType> CreateCachedFactory<TType>(Type typeofType) {
        var ctorInfo = typeofType.GetConstructor(Type.EmptyTypes);
        var lambdaExpression = Expression.Lambda<Func<TType>>(Expression.New(ctorInfo));
        var factory = lambdaExpression.Compile();

        dictionary.Add(typeofType, factory);

        return factory;
    }
}

public class MyClass
{
}

static class Program
{

    static void Main() {
        var myClassFactory = TypeFactory.CreateFactory<MyClass>();
        var instance = myClassFactory();

        Console.WriteLine(instance.GetType().FullName);
    }
}

Expression trees在幕后使用lcg或轻量级代码生成来构建方法,使用Reflection.Emit,这使得它更难以理解。因此,你可以说,表达式树也是用于代码生成的一种抽象。

2

当您需要解析表达式并确定表达式参数名称等内容时,通常会使用它。例如,在.NET中有ArgumentNullException,要创建此类的实例,您应该将参数名称作为字符串传递,这不是类型安全的方法,因此您可以使用此包装器:

    public static string GetName<T>(this Expression<Func<T>> expr)
    {
        if (expr.Body.NodeType == ExpressionType.MemberAccess)
            return ((MemberExpression)expr.Body).Member.Name;

        if (expr.Body.NodeType == ExpressionType.Convert
            && ((UnaryExpression)expr.Body).Operand.NodeType
            == ExpressionType.MemberAccess)
            return ((MemberExpression)((UnaryExpression)expr.Body).Operand)
                .Member.Name;

        throw new ArgumentException(
            "Argument 'expr' must be of the form ()=>variableName.");
    }

    public static void CheckForNullArg<T>(params Expression<Func<T>>[] expressions)
    {
        foreach (var expression in expressions)
        {
            if (EqualityComparer<T>.Default.Equals(expression.Compile()(), default(T)))
            {
                throw new ArgumentNullException(expression.GetName());
            }
        }
    }

    public void Test(string parameter)
    {
        CheckForNullArg(() => parameter);
    }

另一个类似的用途是在WPF中类型安全地处理PropertyChanged事件 — 以类型安全的方式处理PropertyChanged

此外,它在日志记录方面非常有用,因为您可以保存Expression的字符串表示形式在日志中,并调用ToString()方法。


在我看来,这是一种肮脏的黑科技,现在可以安息了,因为C# 6中的nameof运算符和C# 5的CallerMemberName属性。 - quadroid
1
顺便提一下,如果T是整数且其值为0,则您的代码会引发参数空异常,最好使用通用类型约束。 - quadroid
2
完全同意,这只是一个例子。但是,同时并不是每个人都能负担得起使用新版本的C#,因为例如不愿强制用户安装新的.NET版本。 - olegk
说实话,有时候C#感觉过度设计到了极致,我认为这在很大程度上是因为过分推崇面向对象编程。 - Paul-Sebastian Manole

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