只要十步,你就可以应用表达式树来优化动态调用(一)

表达式树是 .net 中一系列非常好用的类型 。 在一些场景中使用表达式树可以获得更好的性能和更佳的扩展性 。 本篇我们将通过构建一个 “模型验证器” 来理解和应用表达式树在构建动态调用方面的优势 。
Newbe.Claptrap 是一个用于轻松应对并发问题的分布式开发框架 。 如果您是首次阅读本系列文章 。 建议可以先从本文末尾的入门文章开始了解 。
开篇摘要前不久 , 我们发布了《如何使用 dotTrace 来诊断 netcore 应用的性能问题》 , 经过网友投票之后 , 网友们表示对其中表达式树的内容很感兴趣 , 因此本篇我们将展开讲讲 。
动态调用是在 .net 开发是时常遇到的一种需求 , 即在只知道方法名或者属性名等情况下动态的调用方法或者属性 。 最广为人知的一种实现方式就是使用 “反射” 来实现这样的需求 。 当然也有一些高性能场景会使用 Emit 来完成这个需求 。
本文将介绍 “使用表达式树” 来实现这种场景 , 因为这个方法相较于 “反射” 将拥有更好的性能和扩展性 , 相较于 Emit 又更容易掌握 。
我们将使用一个具体的场景来逐步使用表达式来实现动态调用 。
在该场景中 , 我们将构建一个模型验证器 , 这非常类似于 aspnet mvc 中 ModelState 的需求场景 。
这不是一篇简单的入门文章 , 初次涉足该内容的读者 , 建议在空闲时 , 在手边有 IDE 可以顺便操作时边看边做 。 同时 , 也不必在意样例中出现的细节方法 , 只需要了解其中的大意 , 能够依样画瓢即可 , 掌握大意之后再深入了解也不迟 。
为了缩短篇幅 , 文章中的样例代码会将没有修改的部分隐去 , 想要获取完整的测试代码 , 请打开文章末尾的代码仓库进行拉取 。
为什么要用表达式树 , 为什么可以用表达式树?首先需要确认的事情有两个:

  1. 使用表达式树取代反射是否有更好的性能?
  2. 使用表达式树进行动态调用是否有很大的性能损失?
有问题 , 做实验 。 我们采用两个单元测试来验证以上两个问题 。
调用一个对象的方法:
using System;using System.Diagnostics;using System.Linq.Expressions;using System.Reflection;using FluentAssertions;using NUnit.Framework;namespace Newbe.ExpressionsTests{public class X01CallMethodTest{private const int Count = 1_000_000;private const int Diff = 100;[SetUp]public void Init(){_methodInfo = typeof(Claptrap).GetMethod(nameof(Claptrap.LevelUp));Debug.Assert(_methodInfo != null, nameof(_methodInfo) + " != null");var instance = Expression.Parameter(typeof(Claptrap), "c");var levelP = Expression.Parameter(typeof(int), "l");var callExpression = Expression.Call(instance, _methodInfo, levelP);var lambdaExpression = Expression.Lambda>(callExpression, instance, levelP);// lambdaExpression should be as (Claptrap c,int l) =>{ c.LevelUp(l); }_func = lambdaExpression.Compile();}[Test]public void RunReflection(){var claptrap = new Claptrap();for (int i = 0; i < Count; i++){_methodInfo.Invoke(claptrap, new[] {(object) Diff});}claptrap.Level.Should().Be(Count * Diff);}[Test]public void RunExpression(){var claptrap = new Claptrap();for (int i = 0; i < Count; i++){_func.Invoke(claptrap, Diff);}claptrap.Level.Should().Be(Count * Diff);}[Test]public void Directly(){var claptrap = new Claptrap();for (int i = 0; i < Count; i++){claptrap.LevelUp(Diff);}claptrap.Level.Should().Be(Count * Diff);}private MethodInfo _methodInfo;private Action _func;public class Claptrap{public int Level { get; set; }public void LevelUp(int diff){Level += diff;}}}}


推荐阅读