对于大多数开发人员而言,表达式树的使用仅限于LINQ中的lambda表达式。 通常,我们并不十分重视该技术在“幕后”的工作方式。
在本文中,我将向您展示使用表达式树的高级技术:消除LINQ中的代码重复,代码生成,元编程,转译,测试自动化。
您将学习如何直接使用表达式树,技术已准备好了哪些陷阱以及如何解决它们。

在
我的 DotNext 2018 Piter
报告的视频和文本记录的剪辑下。
我叫Maxim Arshinov,我是高科技集团外包公司的联合创始人。 我们正在开发用于商业的软件,今天我将讨论表达式树技术如何在日常工作中使用以及如何开始为我们提供帮助。
我从来没有特别想研究表达式树的内部结构,这似乎是LINQ的.NET团队可以使用的某种内部技术,并且应用程序程序员不需要了解它的API。 原来,有一些应用问题需要解决。 为了使我喜欢该解决方案,我不得不爬上肠道。
整个故事是时间紧迫的,有不同的项目,不同的案例。 出来了一些东西,我完成了,但是我会允许自己牺牲历史真实性,以便进行更具艺术性的展示,因此所有示例都将基于同一主题模型-在线商店。

想象我们都在写在线商店。 它在管理面板中有产品和带有“出售”的复选标记。 我们将仅显示在公共部分上带有此复选标记的产品。

我们使用一些DbContext或NHibernate,我们编写Where(),然后输出IsForSale。
一切都很好,但是业务规则并不相同,因此我们要一劳永逸地编写它们。 它们随着时间的流逝而发展。 一位经理来告诉我们,我们仍然必须监视余额并仅在公共部分显示有余额的商品,而不要忘记选中标记。

我们很容易添加这样的属性。 现在我们的业务规则已经封装,我们可以重复使用它们。

让我们尝试编辑LINQ。 这里一切都还好吗?
不,这将不起作用,因为IsAvailable不会映射到数据库,这是我们的代码,查询提供程序也不知道如何解析它。

我们可以告诉他,我们的财产有这样的故事。 但是现在,此lambda在linq表达式和属性中都重复了。
Where(x => x.IsForSale && x.InStock > 0) IsAvailable => IsForSale && InStock > 0;
因此,下次此lambda更改时,我们将必须在项目上执行Ctrl + Shift +F。 自然,我们所有人都找不到-错误和时间。 我想避免这种情况。

我们可以从这一侧开始,在Where()前面放置另一个ToList()。 这是一个错误的决定,因为如果数据库中有上百万种商品,那么每个人都会使用RAM并在那里进行过滤。

如果商店中有三种产品,那么解决方案是不错的选择,但是在电子商务中通常有更多产品。 这之所以起作用,是因为尽管lambda彼此相似,但它们具有完全不同的类型。 在第一种情况下,这是Func委托,在第二种情况下,这是表达式树。 看起来一样,类型不同,字节码完全不同。

要从表达式移动到委托,只需调用Compile()方法。 该API提供.NET:有表达式编译的,接收了一个委托。
但是如何回去? .NET中是否有任何东西可以从委托树转移到表达式树? 例如,如果您熟悉LISP,则可以使用一种引用机制,该机制允许将代码解释为数据结构,但在.NET中则不是。
快递还是代表?
考虑到我们有两种类型的lambda,我们可以将主要的概念化:表达式树或委托。
乍一看,答案是显而易见的:由于有一个很棒的Compile()方法,因此表达式树是主要的。 我们应该通过编译表达式来接收委托。 但是编译是一个缓慢的过程,如果我们在任何地方都开始这样做,则会降低性能。 此外,我们将在必须将表达式编译成委托的随机位置接收它,这将降低性能。 您可以找到这些位置,但是它们会随机影响服务器的响应时间。

因此,它们需要以某种方式进行缓存。 如果您听过有关并发数据结构的讨论,那么您将了解ConcurrentDictionary(或者只是了解它)。 我将省略有关缓存方法的详细信息(使用锁,而不是锁)。 只是ConcurrentDictionary有一个简单的GetOrAdd()方法,最简单的实现是将其弹出到ConcurrentDictionary中并对其进行缓存。 第一次获得编译结果,但是随后一切都会很快,因为委托已被编译。

然后,您可以使用这种扩展方法,可以通过IsAvailable()使用和重构我们的代码,描述表达式,编译IsAvailable()属性,并相对于当前对象调用它。
至少有两个软件包可以实现此目的:
Microsoft.Linq.Translations和
Signum Framework (由商业公司编写的开源框架)。 那里的代表与汇编有关的故事差不多。 API稍有不同,但一切如我在上一张幻灯片中所示。
但是,这不是唯一的方法,您可以从委托到表达式。 很长时间以来,在Habre上有
一篇关于Delegate Decompiler
的文章 ,作者声称所有编译都是不好的,因为很长时间了。
通常,委托位于表达式之前,您可以从委托移至它们。 为此,作者使用methodBody.GetILAsByteArray();。 来自Reflection,它实际上以字节数组形式返回该方法的整个IL代码。 如果将其进一步放在Reflection中,则可以得到这种情况的对象表示,并通过循环进行遍历并构建一个表达式树。 因此,反向转换也是可能的,但必须手动完成。

为了不遍历所有属性,作者建议挂起Computed属性以指示需要内联该属性。 在请求之前,我们进入IsAvailable(),拉出其IL代码,将其转换为表达式树,然后用此getter中编写的内容替换IsAvailable()调用。 事实证明,这种手动内联。

为此,在将所有内容传递给ToList()之前,我们调用特殊方法Decompile()。 它为原始的可查询和内联提供了一个装饰器。 只有在那之后,我们才将所有内容传递给查询提供者,然后一切都变得很好。

这种方法的唯一问题是Delegate Decompiler 0.23.0不会向前发展,没有Core支持,并且作者本人说这是深层次的alpha,有许多未完成的版本,因此您不能在生产中使用它。 尽管我们将回到这个主题。
布尔运算
事实证明,我们已经解决了重复特定条件的问题。

但是条件通常需要使用布尔逻辑进行组合。 我们有IsForSale(),InStock()> 0,并且它们之间的条件是“ AND”。 如果还有其他条件,或者需要“ OR”条件。

在“与”的情况下,您可以欺骗并转储查询提供程序上的所有工作,也就是说,连续写很多Where(),他知道该怎么做。

如果需要“ OR”,这将不起作用,因为LINQ中没有WhereOr(),并且| |运算符也没有被表达式重载。
技术指标
如果您熟悉Evans的DDD书,或者只是对Specification模式有所了解,即专门为此设计的设计模式。 有几个业务规则,并且您希望以布尔逻辑组合操作-实现规范。

规范就是这样一个术语,是Java的一种旧模式。 而且在Java中,尤其是在旧的Java中,由于没有LINQ,因此仅以isSatisfiedBy()方法的形式(即仅委托)的形式在其中实现,但没有讨论表达式。 互联网上有一个名为
LinqSpecs的实现,您将在幻灯片上看到它。 我为自己归档了一点,但是这个想法属于图书馆。
在这里,所有布尔运算符都被重载,true和false运算符都被重载,以便两个运算符“ &&”和“ ||”起作用,而没有它们的情况下,只有一个&号起作用。

接下来,我们添加隐式语句,使编译器假定规范既是表达式又是委托。 在表达式<>或Func <>应该加入函数的任何位置,您都可以传递规范。 由于隐式运算符已重载,因此编译器将解析并替换Expression或IsSatisfiedBy属性。

IsSatisfiedBy()可以通过缓存出现的表达式来实现。 无论如何,事实证明我们来自Expression,委托与之对应,我们增加了对布尔运算符的支持。 现在可以安排所有这些。 可以将业务规则放入静态规范中,进行声明和合并。
public static readonly Spec<Product> IsForSaleSpec = new Spec<Product>(x => x.IsForSale); public static readonly Spec<Product> IsInStockSpec = new Spec<Product>(x => x.InStock > 0);

每个业务规则仅编写一次,不会在任何地方丢失,也不会重复,可以合并。 参加该项目的人可以看到您所拥有的,什么条件,了解主题模型。

有一个小问题:表达式没有And(),Or()和Not()方法。 这些是扩展方法,必须独立实现。

实现的第一个尝试就是这样。 关于表达式树,Internet上有很多文档,并且都没有详细介绍。 因此,我尝试仅使用Expression,按Ctrl + Space,查看OrElse(),并阅读有关内容。 通过两个Expression进行编译并获取lambda。 这是行不通的。

事实是该表达式由两部分组成:参数和主体。 第二个还包括一个参数和一个主体。 在OrElse()中,您需要传递表达式的主体,也就是说,将lambda与“ AND”和“ OR”进行比较是没有用的,这是行不通的。 我们已修复,但将无法再次使用。
但是,如果上次出现NotSupportedException表示不支持lambda的情况,那么现在有一个关于参数1,参数2的奇怪故事:“出了点问题,我行不通”。
C#7.0简而言之
然后,我认为科学的戳方法无效,我需要弄清楚。 他开始在Google上搜索,并找到了Albahari的书“
坚果壳中的
C#7.0 ”的网站。

约瑟夫·阿尔巴哈里(Joseph Albahari)也是流行的LINQKit库和LINQPad的开发商,他只是描述了这个问题。 您不仅可以采用并组合Expression,而且如果采用了神奇的Expression.Invoke(),它也会起作用。
问题:什么是Expression.Invoke()? 再次转到Google。 它创建一个InvocationExpression,将委托或lambda表达式应用于参数列表。

如果现在我们使用Expression.Invoke()向您阅读此代码,我们将传递参数,那么同样的事情也是用英语编写的。 现在还不清楚。 有一些神奇的Expression.Invoke()可以出于某种原因使用参数解决此问题。 我们必须相信,没有必要去理解。

同时,如果您尝试将这样的组合表达式送入EF,它将再次掉落并说不支持Expression.Invoke()。 顺便说一句,EF核心开始支持,但EF 6不成立。 但是,Albarhari仅提供编写AsExpandable()的方法,并且一切正常。

您可以在需要委托的地方替换Expression子查询。 为了使它们匹配,我们编写Compile(),但是同时,如果我们如Albahari所建议的那样编写AsExpandable(),则实际上不会发生此Compile(),但是一切都会以某种神奇的方式正确完成。

我不敢说一个字就爬到源头。 什么是AsExpandable()方法? 它具有查询和QueryOptimizer。 我们将第二个放在括号之外,因为它没有意思,而只是粘贴Expression:如果3 + 5,则将8。

有趣的是,稍后在查询优化器之后调用Expand()方法,然后在Expand()方法之后将所有内容以某种方式重做传递给查询提供程序。

我们打开它,它是Visitor,在内部我们看到非原始的Compile(),它会编译其他内容。 即使有确切含义,我也不会告诉您确切的含义,但是我们删除一个编译,然后将其替换为另一个。 很好,但由于对性能的影响不会随处可见,因此它无法达到80级营销水平。
寻找替代方案
我认为这行不通,因此开始寻找其他解决方案。 并找到了。 有一位皮特·蒙哥马利(Pete Montgomery)也写过这个问题,并声称阿尔巴哈里(Albarhari)是假的。

皮特与EF的开发人员进行了交谈,他们教他在没有Expression.Evoke()的情况下合并所有内容。 这个想法很简单:伏击带有参数。 事实是,对于组合表达式,存在第一个表达式的参数和第二个表达式的参数。 他们不匹配。 车身被粘在一起,但参数仍然悬在空中。 他们需要以正确的方式包扎。
为此,如果lambda不是来自一个参数,则需要通过查看表达式的参数来编译字典。 我们编写了一个字典,然后将第二个参数的所有参数重新绑定到第一个参数,以便初始参数输入Expression,驱动我们粘合在一起的整个身体。

这种简单的方法允许您使用Expression.Invoke()摆脱所有伏击。 而且,在实施Pete Montgomery的过程中,这变得更加酷。 它具有Compose()方法,可让您组合任何表达式。

我们进行表达,并通过AndAlso进行连接,无需Expandable()。 布尔运算中使用的就是这种实现。
规格和单位
一切都很好,直到人们清楚自然界中存在聚集体为止。 对于不熟悉的人,我将解释:如果您有一个域模型,并且您以树的形式表示了彼此相关的所有实体,那么单独悬挂的树就是一个集合。 订单连同订单项将称为聚合,而订单本质就是聚合根。

如果除了产品之外,还有一些类别以规范的形式为它们宣布了业务规则,那么正如营销人员所说的那样,某个额定值应该超过50,并且我们希望以此方式使用它,那么这很好。

但是,如果我们想将商品从一个好的类别中剔除,那又很不好,因为我们的类型不匹配。 类别规范,但需要产品。

同样,我们需要以某种方式解决问题。 第一个选项:用SelectMany()替换Select()。 我不喜欢这里的两件事。 首先,我不知道在所有流行的查询提供程序中如何实现SelectMany()支持。 其次,如果某人编写了一个查询提供程序,那么他要做的第一件事就是写未实现的抛出异常和SelectMany()。 第三点:人们认为SelectMany()是功能或联接,通常不与SELECT查询关联。
组成
我想使用Select(),而不是SelectMany()。

大约在同一时间,我阅读了有关类别理论,功能成分的知识,并认为,如果下面的布尔中有产品的规格,则有一些功能可以从产品到类别,有一个有关类别的规格,然后用第一个替代作为第二个参数,我们得到了我们所需的产品规格。 与功能组合完全相同,但用于表达树。

这样就可能编写出Where()方法,从而有必要将其从产品移至类别并将规范应用于该相关实体。 这种符合我主观口味的语法看起来很容易理解。
public static IQueryable<T> Where<T, TParam>(this IQueryable<T> queryable, Expression<Func<T, TParam>> prop, Expression<Func<TParam, bool>> where) { return queryable.Where(prop.Compose(where)); }
使用Compose()方法,这也可以轻松完成。 我们从产品中获取输入的Expression,然后将其与产品的规格相结合,仅此而已。

现在,您可以编写这样的Where()。 如果您有任何长度的机器,这将起作用。 类别具有SuperCategory,并且可以替换任意数量的其他属性。
我想:“由于我们拥有用于功能组合的工具,并且我们可以对其进行编译,并且可以动态地进行组装,因此这意味着存在元编程的味道!”
投影
我们在哪里可以应用元编程,以便我们只需编写更少的代码。

第一种选择是投影。 拔出整个实体通常太昂贵。 大多数情况下,我们将其传递给前端,序列化JSON。 但这并不需要整体的本质和整体。 您可以通过手动编写Select()来尽可能高效地使用LINQ。 不难,但无聊。

相反,我建议每个人都使用ProjectToType()。 至少有两个库可以执行此操作:Automapper和Mapster。 由于某些原因,许多人知道AutoMapper可以在内存中进行映射,但是并不是每个人都知道AutoMapper具有Queryable Extensions,还具有Expression,并且可以构建SQL表达式。 如果您仍然编写手动查询并使用LINQ,因为您没有严重的性能限制,那么用手进行操作没有任何意义,这是机器的工作,而不是人的工作。
筛选
如果我们可以用投影来做到这一点,为什么不做过滤。

这也是代码。 过滤器进来。 许多业务应用程序如下所示:一个过滤器来了,添加了Where(),另一个过滤器来了,添加了Where()。 有多少个过滤器,如此之多并重复。 没什么复杂的,但是有很多复制粘贴。

如果我们作为AutoMapper做到这一点,编写AutoFilter,Project和Filter,以便他自己做所有事情,那将会很酷-更少的代码。

这并不复杂。 以Expression.Property为例,并仔细阅读DTO。 我们发现相同的通用属性。 如果它们被称为相同,则它看起来像一个过滤器。
接下来,您需要检查是否为空,使用常量从DTO获取值,在表达式中替换它,并在具有Int和NullableInt或其他Nullable的情况下添加转换,以使类型匹配。 并放入例如Equals()的检查相等性的过滤器。

然后收集lambda并遍历每个属性:如果有很多属性,请根据过滤器的工作方式通过“ AND”或“ OR”收集。

排序可以完成相同的事情,但是要复杂一点,因为OrderBy()方法有两个泛型,因此您必须动手填写它们,使用Reflections从两个泛型中创建OrderBy()方法,插入我们要使用的实体的类型,可排序的类型财产。 通常,您也可以这样做,这并不困难。
出现了一个问题:在实体级别上将Where()放在哪里,因为在发布规范时或在预测之后,它将在那里工作。

两者都是正确的,因为从定义上来说,规范是业务规则,我们必须珍惜和珍惜它们,并且不要误解它们。 这是一维层。 过滤器更多是关于UI的,这意味着它们通过DTO进行过滤。 因此,您可以放置两个Where()。 关于查询提供程序将如何很好地处理此问题,还有更多的问题,但我相信ORM解决方案无论如何都会编写错误的SQL,并且情况不会更糟。 如果这对您很重要,那么这个故事根本就与您无关。

正如他们所说,看一次比听一百次更好。
现在,该商店拥有三种产品:士力架,斯巴鲁翼豹和火星。 奇怪的商店。 让我们尝试找到士力架。 有。 让我们看看一百卢布。 也是士力架。 还有500? 放大,什么都没有。 对于100500斯巴鲁翼豹(Impreza)。 太好了,排序也一样。
按字母顺序和价格排序。 那里的代码写得和以前一样多。 这些过滤器适用于任何类,无论如何。 如果您尝试按名称搜索,那么斯巴鲁也将存在。 在我的演示文稿中是Equals()。 怎么会这样 事实是,此处和演示中的代码有些不同。 我评论了关于Equals()的内容,并添加了一些特殊的街头魔术。 如果我们具有String类型,则我们不需要Equals(),而是调用StartWith(),我也收到了。 因此,为行建立一个不同的筛选器。

这意味着您可以在这里按Ctrl + Shift + R,选择方法,而不编写if if,而是进行切换,或者甚至可以实施“ Strategy”模式然后疯狂。 您可以实现对过滤器操作的任何需求。 这完全取决于您使用的类型。 最重要的是,过滤器将发挥相同的作用。
您可以同意所有UI元素中的过滤器都应像这样工作:以一种方式搜索字符串,以另一种方式搜索货币。 协调所有这些操作,编写一次,所有操作都将在不同的接口中正确完成,并且没有其他开发人员会破坏它,因为此代码不是在应用程序级别上,而是在外部库或内核中的某个位置。
验证方式
除了过滤和投影之外,您还可以进行验证。 JS库
TComb.validation提出了这个想法。 TComb是类型组合器的缩写,它基于类型系统等。
完善,改进。
首先,声明与所有JS类型相对应的原语,以及与未定义或零相对应的其他nill类型。
然后,乐趣开始了。每种类型都可以使用谓词进行增强。如果我们希望数字大于零,则声明谓词x> = 0并针对正类型进行验证。因此,您可以从构件块中收集任何验证信息。我们注意到,也许还有lambda表达式。
通话被接听。我们进行同样的改进,用C#编写,编写IsValid()方法,并编译和执行Expression。现在,我们有机会进行验证。 public class RefinementAttribute: ValidationAttribute { public IValidator<object> Refinement { get; } public RefinementAttribute(Type refinmentType) { Refinement = (IValidator<object>) Activator.CreateInstance(refinmentType); } public override bool IsValid(object value) => Refinement.Validate(value).IsValid(); }
我们在ASP.NET MVC中与标准的DataAnnotations系统集成,因此所有这些都可以立即使用。我们声明RefinementAttribute(),将类型传递给构造函数。事实是RefinementAttribute是通用的,因此您必须使用这样的类型,因为很遗憾,您无法在.NET中声明通用的属性。
因此,请使用财务标记用户类别。年龄超过18岁的AdultRefinement。
要完全好,让我们在客户端和服务器上进行相同的验证。 NoJS支持者建议在JS上编写支持和开头。好的,我会在C#中前后写代码,没关系,我只是将其转换为JS。 Javascriptist可以在其JSX,ES6中编写并将其转换为JavaScript。我们为什么不能呢?我们编写来访者,检查需要哪些运算符并编写JavaScript。
一个单独的频繁验证案例是正则表达式,它们也需要反汇编。如果您有正则表达式,请使用StringBuilder,构建正则表达式。在这里,我使用了两个感叹号,因为JS是一种动态类型化的语言,所以此表达式将始终强制转换为bool,这样,该类型就可以了。让我们看看它的外观。 { predicate: “x=> (x >= 18)”, errorMessage: “For adults only» }
这是我们的提炼,来自后端,这是一个行谓词,因为在JS中没有lambda和errorMessage“仅适用于成人”。让我们尝试填写表格。不通过。我们看看它是如何制成的。这是React,我们从UserRefinment()方法的后端请求Expression和errorMessage,构造一个相对于数字的优化,使用eval来获取lambda。如果重做并删除类型限制,将其替换为通常的数字,验证将在JS上失败。输入单位,发送。我不知道它是否可见,这里显示为false。
该代码是警报。当我们发送onSubmit时,请警告来自后端的内容。后端就是这样一个简单的代码。
我们只返回Ok(ModelState.IsValid),这是我们从JavaScript表单中获得的User类。这是此优化属性。 using … namespace DemoApp.Core { public class User: HasNameBase { [Refinement(typeof(AdultRefinement))] public int Age { get; set; } } }
也就是说,验证也可以在此lambda中声明的后端上进行。然后将其翻译成JavaScript。事实证明,我们在C#中编写了lambda表达式,并且代码到处都在执行。我们的答案是NoJS,我们也可以这样做。测试中
通常是Timlids更关心代码中的错误数量。编写单元测试的人都知道Moq库。您是否要编写模拟或声明某些类-有最小起订量,它具有流利的语法。您可以画出您希望他表现如何,并滑动他的测试申请。最小起订量中的这些lambda也是Expression,而不是委托。他遍历表达式树,应用其逻辑,然后输入Castle.DynamicProxy。并且他在运行时创建必要的类。但是我们也可以做到。
我的一个朋友最近问我们的核心中是否存在类似WCF的内容。我回答说有一个WebAPI。他想像在WSDL上的WCF中那样在WebAPI中构建代理。 WebAPI中只有sw之以鼻。但是招摇只是文字,当API更改以及发生问题时,朋友不想每次都看。有WCF时,它将启用WSDL,如果API中的规范已更改,则编译会中断。这是有道理的,因为它不愿搜索,并且编译器可以提供帮助。类似于moq,您可以声明ProductController通用的GetResponse <>()方法,该方法中的lambda由控制器参数化。也就是说,当您开始编写lambda时,请按Ctrl +空格键,并查看此控制器具有的所有方法(如果有一个库,一个包含代码的dll)。有Intellisense,可以像调用控制器一样编写所有内容。此外,作为Moq,我们不会调用它,而只是构建一个表达式树,遍历它,并从API配置中提取所有路由信息。而且,因为我们必须在服务器上执行它,而不是使用无法执行的控制器做一些事情,我们只需要发出所需的POST或GET请求,然后反方向反序列化接收到的请求,因为我们知道所有返回类型的智能感知和表达式树。事实证明,我们编写了有关控制器的代码,但实际上我们进行Web请求。反射优化关于元编程的一切与反射都有很多共同点。
我们知道反射很慢,我想避免这种情况。在这里,也有很多使用Expression的案例。第一个是CreateInstance激活器。绝对不要使用它,因为这里有Expression.New(),您可以将其简单地驱动到lambda中,进行编译,然后获取构造函数。
我从一位出色的演讲者和音乐家Vagif那里借来了这张幻灯片。他正在博客上进行某种基准测试。这是激活剂,这是共产主义的顶峰;您会看到他正在尽一切努力。 Constructor_Invoke,大约是它的一半。左边是新的和编译的lambda。由于这是一个委托而不是一个构造函数,因此性能略有提高,但是选择是显而易见的,显然这要好得多。
用getter或setter可以完成相同的事情。
这非常简单。如果由于某种原因您对Fast Memember,Mark Gravelli或Fast Reflect不满意,如果您不想拖动此依赖项,则可以执行相同操作。唯一的困难是您需要监视所有这些编译,在某个地方存储和预热缓存。也就是说,如果有很多,那么一开始就要编译一次。
由于存在构造函数,getter和setter,因此只有行为,方法。但是它们也可以编译为委托,您将只需要管理一个大型的委托动物园。知道我所谈论的所有内容后,可能会想到某个人,如果有很多代表,很多表达式,那么可能会有所谓的DSL,Little Languages或解释器模式(免费的monad)的空间。当对于某些任务我们提出了一组命令,而对于他,我们编写了自己的解释器来执行这些命令时,这些都是相同的。也就是说,在应用程序内部,我们编写了另一个知道如何使用这些命令的编译器或解释器。在与IronPython和IronRuby语言一起使用的部分中,这正是DLR中完成的工作。表达式树用于在CLR中执行动态代码。在业务应用程序中也可以做同样的事情,但是到目前为止,我们还没有注意到这样的需求,这仍然超出了范围。总结
最后,我想谈一谈在实施和测试后得出的结论。如我所说,这发生在不同的项目上。我写的所有内容都不会在任何地方使用,但在必要的地方使用了一些东西。第一个优点是能够使例程自动化。如果您有10万个具有过滤,分页等功能的模具。莫扎特开玩笑说,用骰子,足够的时间和一杯红酒,你可以写任意数量的华尔兹。在这里,借助表达式树,通过一些元编程,您可以编写任意数量的表单。作为代码生成的一种替代方法,如果您不喜欢它,则代码量会大大减少,因为您获得了很多代码,因此无法编写代码,并将所有内容保留在运行时中。将这样的代码用于简单任务,由于命令性代码很少而且也没有错误的余地,我们进一步降低了对执行者的要求。将大量代码放入可重用的组件后,我们消除了此类错误。另一方面,我们极大地提高了对设计师资格的要求,因为问题来自于有关如何使用Expression,Reflection,它们的优化以及关于您可以用脚射击的地方的知识。这样的细微差别有很多,因此不熟悉此API的人将不会立即理解为什么Expression不能将其组合在一起。设计者应该更酷。在某些情况下,通过Expression.Compile(),您可以捕获性能下降。在缓存示例中,我有一个限制,即表达式是静态的,因为字典是用于缓存的。如果某人不知道它是如何内部排列的,那么他会不加思索地开始做,在内部声明规范为非静态的,缓存方法将不起作用,并且我们将在随机的地方获得对Compile()的调用。正是我想要避免的。最不愉快的是,代码不再看起来像C#代码,它变得越来越不习惯,出现了静态调用,奇怪的是附加了Where()方法,一些隐式运算符被重载。在示例中,这不在MSDN文档中。例如,如果有一个经验不足的人来找你,而又不习惯进入事件源代码中,那么他很可能会第一次陷入虚脱,因为这与世界情况不符,StackOverflow上没有这样的例子,但是有了这个将必须以某种方式工作。总的来说,这就是我今天要谈的全部。我在Habré 上写了很多有关细节的详细信息。库代码发布在github上,但是它有一个致命的缺陷-完全缺少文档。22-23 DotNext 2018 Moscow . , ( ).