为什么使用表达式树
为什么使用表达式树?
表达式树是一种让编译器将通常作为指令的内容嵌入到可探索的表达式树中的方法。当你需要理解一个表达式时,表达式树可以帮助你捕捉函数的构造方式,而不仅仅是函数的结果。
一般来说,我经常看到初级开发人员问的一个问题是“Func
只有当你需要知道表达式的构成方式时,这个区别才变得相关。
我只见过表达式树被用于两种情况:
1. 创建一个LINQ提供程序(如LINQ-to-SQL),以解释一个表达式并将其转换为另一种语言。正如你所知,这涉及实现IQueryable,是一项重要的工作。
2. 从对象中捕获属性访问器(如Razor HTML Helpers),以便不仅可以访问值,还可以使用属性的名称生成其他内容(如HTML字段名称)。
这些常见的任务往往涉及将内容翻译成另一种语言。对于LINQ提供程序,它是将查询直接翻译成另一种查询语言。对于Razor,它是将属性转换为HTML字段。
因此,当你需要理解一个表达式时,使用表达式树可以捕捉函数是如何构造的,以便进行进一步的处理,如翻译成另一种语言或生成其他内容。
为什么要使用表达式树?
表达式树经常被用于在运行时动态生成代码。假设你需要创建大量未知类型的实例。你可以使用反射进行创建,但性能会很差,或者你可以创建一个表达式树并将其编译为一个方法。这个方法的性能将与使用Visual Studio编译它时的性能相同。
下面是一个使用表达式树的示例代码:
public static class TypeFactory { private static Dictionarydictionary = new Dictionary (); public static Func CreateFactory () { object factory; var typeofType = typeof(TType); if (dictionary.TryGetValue(typeofType, out factory)) { return (Func )factory; } return CreateCachedFactory (typeofType); } private static Func CreateCachedFactory (Type typeofType) { var ctorInfo = typeofType.GetConstructor(Type.EmptyTypes); var lambdaExpression = Expression.Lambda >(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 (); var instance = myClassFactory(); Console.WriteLine(instance.GetType().FullName); } }
在幕后,表达式树使用了轻量级代码生成(LCG)或反射发射(Reflection.Emit)来构建方法,这使得方法更难理解。因此,可以说表达式树也是代码生成的一种抽象。
为什么要使用表达式树
在.NET中,当需要解析表达式时,表达式树经常被使用,例如确定表达式参数的名称等。例如,.NET中有ArgumentNullException,要创建此异常的实例,您需要将参数名称作为字符串传递,这不是类型安全的方法,因此可以使用以下包装器:
public static string GetName(this Expression > 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 (params Expression >[] expressions) { foreach (var expression in expressions) { if (EqualityComparer .Default.Equals(expression.Compile()(), default(T))) { throw new ArgumentNullException(expression.GetName()); } } } public void Test(string parameter) { CheckForNullArg(() => parameter); }
另一个类似的用途是在WPF中类型安全地处理PropertyChanged事件- Handling PropertyChanged in a type-safe way
此外,它对于日志记录非常有用,因为您可以将表达式的字符串表示保存在日志中,调用ToString()方法。
在我看来,这是一种肮脏的hack,现在可以使用C# 6中的nameof运算符和C# 5中的CallerMemberName属性来替代它。
除此之外,有些人可能无法使用新版本的C#,因为他们不想强制用户安装新的.NET版本。
老实说,有时C#感觉过于设计过度,部分原因是因为过度推崇面向对象编程。