日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

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

發(fā)布時間:2023/12/4 编程问答 31 豆豆
生活随笔 收集整理的這篇文章主要介紹了 只要十步,你就可以应用表达式树来优化动态调用 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

表達式樹是 .net 中一系列非常好用的類型。在一些場景中使用表達式樹可以獲得更好的性能和更佳的擴展性。本篇我們將通過構建一個 “模型驗證器” 來理解和應用表達式樹在構建動態(tài)調(diào)用方面的優(yōu)勢。

Newbe.Claptrap 是一個用于輕松應對并發(fā)問題的分布式開發(fā)框架。如果您是首次閱讀本系列文章。建議可以先從本文末尾的入門文章開始了解。


開篇摘要

前不久,我們發(fā)布了《如何使用 dotTrace 來診斷 netcore 應用的性能問題》https://www.newbe.pro/Newbe.Claptrap/How-to-Use-DotTrace/ ,經(jīng)過網(wǎng)友投票之后,網(wǎng)友們表示對其中表達式樹的內(nèi)容很感興趣,因此本篇我們將展開講講。

動態(tài)調(diào)用是在 .net 開發(fā)是時常遇到的一種需求,即在只知道方法名或者屬性名等情況下動態(tài)的調(diào)用方法或者屬性。最廣為人知的一種實現(xiàn)方式就是使用 “反射” 來實現(xiàn)這樣的需求。當然也有一些高性能場景會使用 Emit 來完成這個需求。

本文將介紹 “使用表達式樹” 來實現(xiàn)這種場景,因為這個方法相較于 “反射” 將擁有更好的性能和擴展性,相較于 Emit 又更容易掌握。

我們將使用一個具體的場景來逐步使用表達式來實現(xiàn)動態(tài)調(diào)用。

在該場景中,我們將構建一個模型驗證器,這非常類似于 aspnet mvc 中 ModelState 的需求場景。

不是一篇簡單的入門文章,初次涉足該內(nèi)容的讀者,建議在空閑時,在手邊有 IDE 可以順便操作時邊看邊做。同時,也不必在意樣例中出現(xiàn)的細節(jié)方法,只需要了解其中的大意,能夠依樣畫瓢即可,掌握大意之后再深入了解也不遲。

為了縮短篇幅,文章中的樣例代碼會將沒有修改的部分隱去,想要獲取完整的測試代碼,請打開文章末尾的代碼倉庫進行拉取。


為什么要用表達式樹,為什么可以用表達式樹?

首先需要確認的事情有兩個:

  • 使用表達式樹取代反射是否有更好的性能?

  • 使用表達式樹進行動態(tài)調(diào)用是否有很大的性能損失?

  • 有問題,做實驗。我們采用兩個單元測試來驗證以上兩個問題。

    調(diào)用一個對象的方法:

    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<Action<Claptrap, int>>(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<Claptrap, int> _func;public class Claptrap{public int Level { get; set; }public void LevelUp(int diff){Level += diff;}}} } MethodTime
    RunReflection217ms
    RunExpression20ms
    Directly19ms

    以上測試中,我們對第三種調(diào)用方式一百萬次調(diào)用,并記錄每個測試所花費的時間。可以得到類似以下的結果:

    可以得出以下結論:

  • 使用表達式樹創(chuàng)建委托進行動態(tài)調(diào)用可以得到和直接調(diào)用近乎相同的性能。

  • 使用表達式樹創(chuàng)建委托進行動態(tài)調(diào)用所消耗的時間約為十分之一。

  • 所以如果僅僅從性能上考慮,應該使用表達式樹,也可以是用表達式樹。

    不過這是在一百萬調(diào)用下體現(xiàn)出現(xiàn)的時間,對于單次調(diào)用而言其實就是納秒級別的區(qū)別,其實無足輕重。

    但其實表達式樹不僅僅在性能上相較于反射更優(yōu),其更強大的擴展性其實采用最為重要的特性。

    此處還有一個對屬性進行操作的測試,此處將測試代碼和結果羅列如下:

    using System; using System.Diagnostics; using System.Linq.Expressions; using System.Reflection; using FluentAssertions; using NUnit.Framework;namespace Newbe.ExpressionsTests {public class X02PropertyTest{private const int Count = 1_000_000;private const int Diff = 100;[SetUp]public void Init(){_propertyInfo = typeof(Claptrap).GetProperty(nameof(Claptrap.Level));Debug.Assert(_propertyInfo != null, nameof(_propertyInfo) + " != null");var instance = Expression.Parameter(typeof(Claptrap), "c");var levelProperty = Expression.Property(instance, _propertyInfo);var levelP = Expression.Parameter(typeof(int), "l");var addAssignExpression = Expression.AddAssign(levelProperty, levelP);var lambdaExpression = Expression.Lambda<Action<Claptrap, int>>(addAssignExpression, instance, levelP);// lambdaExpression should be as (Claptrap c,int l) => { c.Level += l; }_func = lambdaExpression.Compile();}[Test]public void RunReflection(){var claptrap = new Claptrap();for (int i = 0; i < Count; i++){var value = (int) _propertyInfo.GetValue(claptrap);_propertyInfo.SetValue(claptrap, value + 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.Level += Diff;}claptrap.Level.Should().Be(Count * Diff);}private PropertyInfo _propertyInfo;private Action<Claptrap, int> _func;public class Claptrap{public int Level { get; set; }}} }MethodTime
    RunReflection373ms
    RunExpression19ms
    Directly18ms

    耗時情況:

    由于反射多了一份裝拆箱的消耗,所以比起前一個測試樣例顯得更慢了,使用委托是沒有這種消耗的。


    第〇步,需求演示

    先通過一個測試來了解我們要創(chuàng)建的 “模型驗證器” 究竟是一個什么樣的需求。

    using System.ComponentModel.DataAnnotations; using FluentAssertions; using NUnit.Framework;namespace Newbe.ExpressionsTests {/// <summary>/// Validate data by static method/// </summary>public class X03PropertyValidationTest00{private const int Count = 10_000;[Test]public void Run(){for (int i = 0; i < Count; i++){// test 1{var input = new CreateClaptrapInput();var (isOk, errorMessage) = Validate(input);isOk.Should().BeFalse();errorMessage.Should().Be("missing Name");}// test 2{var input = new CreateClaptrapInput{Name = "1"};var (isOk, errorMessage) = Validate(input);isOk.Should().BeFalse();errorMessage.Should().Be("Length of Name should be great than 3");}// test 3{var input = new CreateClaptrapInput{Name = "yueluo is the only one dalao"};var (isOk, errorMessage) = Validate(input);isOk.Should().BeTrue();errorMessage.Should().BeNullOrEmpty();}}}public static ValidateResult Validate(CreateClaptrapInput input){return ValidateCore(input, 3);}public static ValidateResult ValidateCore(CreateClaptrapInput input, int minLength){if (string.IsNullOrEmpty(input.Name)){return ValidateResult.Error("missing Name");}if (input.Name.Length < minLength){return ValidateResult.Error($"Length of Name should be great than {minLength}");}return ValidateResult.Ok();}public class CreateClaptrapInput{[Required] [MinLength(3)] public string Name { get; set; }}public struct ValidateResult{public bool IsOk { get; set; }public string ErrorMessage { get; set; }public void Deconstruct(out bool isOk, out string errorMessage){isOk = IsOk;errorMessage = ErrorMessage;}public static ValidateResult Ok(){return new ValidateResult{IsOk = true};}public static ValidateResult Error(string errorMessage){return new ValidateResult{IsOk = false,ErrorMessage = errorMessage};}}} }


    主測試方法中,包含有三個基本的測試用例,并且每個都將執(zhí)行一萬次。后續(xù)所有的步驟都將會使用這樣的測試用例。從上而下,以上代碼的要點:

  • Validate 方法是被測試的包裝方法,后續(xù)將會調(diào)用該方法的實現(xiàn)以驗證效果。

  • ValidateCore 是 “模型驗證器” 的一個演示實現(xiàn)。從代碼中可以看出該方法對 CreateClaptrapInput 對象進行的驗證,并且得到驗證結果。但是該方法的缺點也非常明顯,這是一種典型的 “寫死”。后續(xù)我們將通過一系列改造。使得我們的 “模型驗證器” 更加的通用,并且,很重要的,保持和這個 “寫死” 的方法一樣的高效!

  • ValidateResult 是驗證器輸出的結果。后續(xù)將不斷重復的用到該結果。


  • 第一步,調(diào)用靜態(tài)方法

    首先我們構建第一個表達式樹,該表達式樹將直接使用上一節(jié)中的靜態(tài)方法 ValidateCore。

    using System; using System.ComponentModel.DataAnnotations; using System.Diagnostics; using System.Linq.Expressions; using FluentAssertions; using NUnit.Framework;namespace Newbe.ExpressionsTests {/// <summary>/// Validate date by func created with Expression/// </summary>public class X03PropertyValidationTest01{private const int Count = 10_000;private static Func<CreateClaptrapInput, int, ValidateResult> _func;[SetUp]public void Init(){try{var method = typeof(X03PropertyValidationTest01).GetMethod(nameof(ValidateCore));Debug.Assert(method != null, nameof(method) + " != null");var pExp = Expression.Parameter(typeof(CreateClaptrapInput));var minLengthPExp = Expression.Parameter(typeof(int));var body = Expression.Call(method, pExp, minLengthPExp);var expression = Expression.Lambda<Func<CreateClaptrapInput, int, ValidateResult>>(body,pExp,minLengthPExp);_func = expression.Compile();}catch (Exception e){Console.WriteLine(e);throw;}}[Test]public void Run(){// see code in demo repo}public static ValidateResult Validate(CreateClaptrapInput input){return _func.Invoke(input, 3);}public static ValidateResult ValidateCore(CreateClaptrapInput input, int minLength){if (string.IsNullOrEmpty(input.Name)){return ValidateResult.Error("missing Name");}if (input.Name.Length < minLength){return ValidateResult.Error($"Length of Name should be great than {minLength}");}return ValidateResult.Ok();}} }


    增加了一個單元測試的初始化方法,在單元測試啟動時創(chuàng)建的一個表達式樹將其編譯為委托保存在靜態(tài)字段 _func 中。從上而下,以上代碼的要點:

  • 省略了主測試方法 Run 中的代碼,以便讀者閱讀時減少篇幅。實際代碼沒有變化,后續(xù)將不再重復說明。可以在代碼演示倉庫中查看。

  • 修改了 Validate 方法的實現(xiàn),不再直接調(diào)用 ValidateCore ,而調(diào)用 _func 來進行驗證。

  • 運行該測試,開發(fā)者可以發(fā)現(xiàn),其消耗的時間和上一步直接調(diào)用的耗時,幾乎一樣,沒有額外消耗。

  • 這里提供了一種最為簡單的使用表達式進行動態(tài)調(diào)用的思路,如果可以寫出一個靜態(tài)方法(例如:ValidateCore)來表示動態(tài)調(diào)用的過程。那么我們只要使用類似于 Init 中的構建過程來構建表達式和委托即可。

  • 開發(fā)者可以試著為 ValidateCore 增加第三個參數(shù) name 以便拼接在錯誤信息中,從而了解如果構建這種簡單的表達式。


  • 第二步,組合表達式

    雖然前一步,我們將直接調(diào)用轉(zhuǎn)變了動態(tài)調(diào)用,但由于 ValidateCore 還是寫死的,因此還需要進一步修改。

    本步驟,我們將會把 ValidateCore 中寫死的三個 return 路徑拆分為不同的方法,然后再采用表達式拼接在一起。

    如果我們實現(xiàn)了,那么我們就有條件將更多的方法拼接在一起,實現(xiàn)一定程度的擴展。

    注意:演示代碼將瞬間邊長,不必感受太大壓力,可以輔助后面的代碼要點說明進行查看。

    using System; using System.ComponentModel.DataAnnotations; using System.Diagnostics; using System.Linq.Expressions; using FluentAssertions; using NUnit.Framework;// ReSharper disable InvalidXmlDocCommentnamespace Newbe.ExpressionsTests {/// <summary>/// Block Expression/// </summary>public class X03PropertyValidationTest02{private const int Count = 10_000;private static Func<CreateClaptrapInput, int, ValidateResult> _func;[SetUp]public void Init(){try{var finalExpression = CreateCore();_func = finalExpression.Compile();Expression<Func<CreateClaptrapInput, int, ValidateResult>> CreateCore(){// exp for inputvar inputExp = Expression.Parameter(typeof(CreateClaptrapInput), "input");var minLengthPExp = Expression.Parameter(typeof(int), "minLength");// exp for outputvar resultExp = Expression.Variable(typeof(ValidateResult), "result");// exp for return statementvar returnLabel = Expression.Label(typeof(ValidateResult));// build whole blockvar body = Expression.Block(new[] {resultExp},CreateDefaultResult(),CreateValidateNameRequiredExpression(),CreateValidateNameMinLengthExpression(),Expression.Label(returnLabel, resultExp));// build lambda from bodyvar final = Expression.Lambda<Func<CreateClaptrapInput, int, ValidateResult>>(body,inputExp,minLengthPExp);return final;Expression CreateDefaultResult(){var okMethod = typeof(ValidateResult).GetMethod(nameof(ValidateResult.Ok));Debug.Assert(okMethod != null, nameof(okMethod) + " != null");var methodCallExpression = Expression.Call(okMethod);var re = Expression.Assign(resultExp, methodCallExpression);/*** final as:* result = ValidateResult.Ok()*/return re;}Expression CreateValidateNameRequiredExpression(){var requireMethod = typeof(X03PropertyValidationTest02).GetMethod(nameof(ValidateNameRequired));var isOkProperty = typeof(ValidateResult).GetProperty(nameof(ValidateResult.IsOk));Debug.Assert(requireMethod != null, nameof(requireMethod) + " != null");Debug.Assert(isOkProperty != null, nameof(isOkProperty) + " != null");var requiredMethodExp = Expression.Call(requireMethod, inputExp);var assignExp = Expression.Assign(resultExp, requiredMethodExp);var resultIsOkPropertyExp = Expression.Property(resultExp, isOkProperty);var conditionExp = Expression.IsFalse(resultIsOkPropertyExp);var ifThenExp =Expression.IfThen(conditionExp,Expression.Return(returnLabel, resultExp));var re = Expression.Block(new[] {resultExp},assignExp,ifThenExp);/*** final as:* result = ValidateNameRequired(input);* if (!result.IsOk)* {* return result;* }*/return re;}Expression CreateValidateNameMinLengthExpression(){var minLengthMethod =typeof(X03PropertyValidationTest02).GetMethod(nameof(ValidateNameMinLength));var isOkProperty = typeof(ValidateResult).GetProperty(nameof(ValidateResult.IsOk));Debug.Assert(minLengthMethod != null, nameof(minLengthMethod) + " != null");Debug.Assert(isOkProperty != null, nameof(isOkProperty) + " != null");var requiredMethodExp = Expression.Call(minLengthMethod, inputExp, minLengthPExp);var assignExp = Expression.Assign(resultExp, requiredMethodExp);var resultIsOkPropertyExp = Expression.Property(resultExp, isOkProperty);var conditionExp = Expression.IsFalse(resultIsOkPropertyExp);var ifThenExp =Expression.IfThen(conditionExp,Expression.Return(returnLabel, resultExp));var re = Expression.Block(new[] {resultExp},assignExp,ifThenExp);/*** final as:* result = ValidateNameMinLength(input, minLength);* if (!result.IsOk)* {* return result;* }*/return re;}}}catch (Exception e){Console.WriteLine(e);throw;}}[Test]public void Run(){// see code in demo repo}public static ValidateResult Validate(CreateClaptrapInput input){return _func.Invoke(input, 3);}public static ValidateResult ValidateNameRequired(CreateClaptrapInput input){return string.IsNullOrEmpty(input.Name)? ValidateResult.Error("missing Name"): ValidateResult.Ok();}public static ValidateResult ValidateNameMinLength(CreateClaptrapInput input, int minLength){return input.Name.Length < minLength? ValidateResult.Error($"Length of Name should be great than {minLength}"): ValidateResult.Ok();}} }


    ValidateCore 方法被拆分為了 ValidateNameRequired 和 ValidateNameMinLength 兩個方法,分別驗證 Name 的 Required 和 MinLength。代碼要點:

  • Init 方法中使用了 local function 從而實現(xiàn)了方法 “先使用后定義” 的效果。讀者可以自上而下閱讀,從頂層開始了解整個方法的邏輯。

  • Init 整體的邏輯就是通過表達式將 ValidateNameRequired 和 ValidateNameMinLength 重新組合成一個形如 ValidateCore 的委托?Func<CreateClaptrapInput, int, ValidateResult>。

  • Expression.Parameter 用于標明委托表達式的參數(shù)部分。

  • Expression.Variable 用于標明一個變量,就是一個普通的變量。類似于代碼中的?var a。

  • Expression.Label 用于標明一個特定的位置。在該樣例中,主要用于標定 return 語句的位置。熟悉 goto 語法的開發(fā)者知道, goto 的時候需要使用 label 來標記想要 goto 的地方。而實際上,return 就是一種特殊的 goto。所以想要在多個語句塊中 return 也同樣需要標記后才能 return。

  • Expression.Block 可以將多個表達式順序組合在一起。可以理解為按順序?qū)懘a。這里我們將 CreateDefaultResult、CreateValidateNameRequiredExpression、CreateValidateNameMinLengthExpression 和 Label 表達式組合在一起。效果就類似于把這些代碼按順序拼接在一起。

  • CreateValidateNameRequiredExpression 和 CreateValidateNameMinLengthExpression 的結構非常類似,因為想要生成的結果表達式非常類似。

  • 不必太在意 CreateValidateNameRequiredExpression 和 CreateValidateNameMinLengthExpression 當中的細節(jié)。可以在本樣例全部閱讀完之后再嘗試了解更多的 Expression.XXX 方法。

  • 經(jīng)過這樣的修改之后,我們就實現(xiàn)了擴展。假設現(xiàn)在需要對 Name 增加一個 MaxLength 不得超過 16 的驗證。只需要增加一個 ValidateNameMaxLength 的靜態(tài)方法,添加一個 CreateValidateNameMaxLengthExpression 的方法,并且加入到 Block 中即可。讀者可以嘗試動手操作一波實現(xiàn)這個效果。


  • 第三步,讀取屬性

    我們來改造 ValidateNameRequired 和 ValidateNameMinLength 兩個方法。因為現(xiàn)在這兩個方法接收的是 CreateClaptrapInput 作為參數(shù),內(nèi)部的邏輯也被寫死為驗證 Name,這很不優(yōu)秀。

    我們將改造這兩個方法,使其傳入 string name 表示驗證的屬性名稱,string value 表示驗證的屬性值。這樣我們就可以將這兩個驗證方法用于不限于 Name 的更多屬性。

    using System; using System.ComponentModel.DataAnnotations; using System.Diagnostics; using System.Linq.Expressions; using FluentAssertions; using NUnit.Framework;// ReSharper disable InvalidXmlDocCommentnamespace Newbe.ExpressionsTests {/// <summary>/// Property Expression/// </summary>public class X03PropertyValidationTest03{private const int Count = 10_000;private static Func<CreateClaptrapInput, int, ValidateResult> _func;[SetUp]public void Init(){try{var finalExpression = CreateCore();_func = finalExpression.Compile();Expression<Func<CreateClaptrapInput, int, ValidateResult>> CreateCore(){// exp for inputvar inputExp = Expression.Parameter(typeof(CreateClaptrapInput), "input");var nameProp = typeof(CreateClaptrapInput).GetProperty(nameof(CreateClaptrapInput.Name));Debug.Assert(nameProp != null, nameof(nameProp) + " != null");var namePropExp = Expression.Property(inputExp, nameProp);var nameNameExp = Expression.Constant(nameProp.Name);var minLengthPExp = Expression.Parameter(typeof(int), "minLength");// exp for outputvar resultExp = Expression.Variable(typeof(ValidateResult), "result");// exp for return statementvar returnLabel = Expression.Label(typeof(ValidateResult));// build whole blockvar body = Expression.Block(new[] {resultExp},CreateDefaultResult(),CreateValidateNameRequiredExpression(),CreateValidateNameMinLengthExpression(),Expression.Label(returnLabel, resultExp));// build lambda from bodyvar final = Expression.Lambda<Func<CreateClaptrapInput, int, ValidateResult>>(body,inputExp,minLengthPExp);return final;Expression CreateDefaultResult(){var okMethod = typeof(ValidateResult).GetMethod(nameof(ValidateResult.Ok));Debug.Assert(okMethod != null, nameof(okMethod) + " != null");var methodCallExpression = Expression.Call(okMethod);var re = Expression.Assign(resultExp, methodCallExpression);/*** final as:* result = ValidateResult.Ok()*/return re;}Expression CreateValidateNameRequiredExpression(){var requireMethod = typeof(X03PropertyValidationTest03).GetMethod(nameof(ValidateStringRequired));var isOkProperty = typeof(ValidateResult).GetProperty(nameof(ValidateResult.IsOk));Debug.Assert(requireMethod != null, nameof(requireMethod) + " != null");Debug.Assert(isOkProperty != null, nameof(isOkProperty) + " != null");var requiredMethodExp = Expression.Call(requireMethod, nameNameExp, namePropExp);var assignExp = Expression.Assign(resultExp, requiredMethodExp);var resultIsOkPropertyExp = Expression.Property(resultExp, isOkProperty);var conditionExp = Expression.IsFalse(resultIsOkPropertyExp);var ifThenExp =Expression.IfThen(conditionExp,Expression.Return(returnLabel, resultExp));var re = Expression.Block(new[] {resultExp},assignExp,ifThenExp);/*** final as:* result = ValidateNameRequired("Name", input.Name);* if (!result.IsOk)* {* return result;* }*/return re;}Expression CreateValidateNameMinLengthExpression(){var minLengthMethod =typeof(X03PropertyValidationTest03).GetMethod(nameof(ValidateStringMinLength));var isOkProperty = typeof(ValidateResult).GetProperty(nameof(ValidateResult.IsOk));Debug.Assert(minLengthMethod != null, nameof(minLengthMethod) + " != null");Debug.Assert(isOkProperty != null, nameof(isOkProperty) + " != null");var requiredMethodExp = Expression.Call(minLengthMethod,nameNameExp,namePropExp,minLengthPExp);var assignExp = Expression.Assign(resultExp, requiredMethodExp);var resultIsOkPropertyExp = Expression.Property(resultExp, isOkProperty);var conditionExp = Expression.IsFalse(resultIsOkPropertyExp);var ifThenExp =Expression.IfThen(conditionExp,Expression.Return(returnLabel, resultExp));var re = Expression.Block(new[] {resultExp},assignExp,ifThenExp);/*** final as:* result = ValidateNameMinLength("Name", input.Name, minLength);* if (!result.IsOk)* {* return result;* }*/return re;}}}catch (Exception e){Console.WriteLine(e);throw;}}[Test]public void Run(){// see code in demo repo}public static ValidateResult Validate(CreateClaptrapInput input){return _func.Invoke(input, 3);}public static ValidateResult ValidateStringRequired(string name, string value){return string.IsNullOrEmpty(value)? ValidateResult.Error($"missing {name}"): ValidateResult.Ok();}public static ValidateResult ValidateStringMinLength(string name, string value, int minLength){return value.Length < minLength? ValidateResult.Error($"Length of {name} should be great than {minLength}"): ValidateResult.Ok();}} }


    正如前文所述,我們修改了 ValidateNameRequired ,并重命名為 ValidateStringRequired。ValidateNameMinLength -> ValidateStringMinLength。代碼要點:

  • 修改了 CreateValidateNameRequiredExpression 和 CreateValidateNameMinLengthExpression,因為靜態(tài)方法的參數(shù)發(fā)生了變化。

  • 通過這樣的改造,我們便可以將兩個靜態(tài)方法用于更多的屬性驗證。讀者可以嘗試增加一個 NickName 屬性。并且進行相同的驗證。


  • 第四步,支持多個屬性驗證

    接下來,我們通過將驗證 CreateClaptrapInput 所有的 string 屬性。

    using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Diagnostics; using System.Linq; using System.Linq.Expressions; using System.Reflection; using FluentAssertions; using NUnit.Framework;// ReSharper disable InvalidXmlDocCommentnamespace Newbe.ExpressionsTests {/// <summary>/// Reflect Properties/// </summary>public class X03PropertyValidationTest04{private const int Count = 10_000;private static Func<CreateClaptrapInput, int, ValidateResult> _func;[SetUp]public void Init(){try{var finalExpression = CreateCore();_func = finalExpression.Compile();Expression<Func<CreateClaptrapInput, int, ValidateResult>> CreateCore(){// exp for inputvar inputExp = Expression.Parameter(typeof(CreateClaptrapInput), "input");var minLengthPExp = Expression.Parameter(typeof(int), "minLength");// exp for outputvar resultExp = Expression.Variable(typeof(ValidateResult), "result");// exp for return statementvar returnLabel = Expression.Label(typeof(ValidateResult));var innerExps = new List<Expression> {CreateDefaultResult()};var stringProps = typeof(CreateClaptrapInput).GetProperties().Where(x => x.PropertyType == typeof(string));foreach (var propertyInfo in stringProps){innerExps.Add(CreateValidateStringRequiredExpression(propertyInfo));innerExps.Add(CreateValidateStringMinLengthExpression(propertyInfo));}innerExps.Add(Expression.Label(returnLabel, resultExp));// build whole blockvar body = Expression.Block(new[] {resultExp},innerExps);// build lambda from bodyvar final = Expression.Lambda<Func<CreateClaptrapInput, int, ValidateResult>>(body,inputExp,minLengthPExp);return final;Expression CreateDefaultResult(){var okMethod = typeof(ValidateResult).GetMethod(nameof(ValidateResult.Ok));Debug.Assert(okMethod != null, nameof(okMethod) + " != null");var methodCallExpression = Expression.Call(okMethod);var re = Expression.Assign(resultExp, methodCallExpression);/*** final as:* result = ValidateResult.Ok()*/return re;}Expression CreateValidateStringRequiredExpression(PropertyInfo propertyInfo){var requireMethod = typeof(X03PropertyValidationTest04).GetMethod(nameof(ValidateStringRequired));var isOkProperty = typeof(ValidateResult).GetProperty(nameof(ValidateResult.IsOk));Debug.Assert(requireMethod != null, nameof(requireMethod) + " != null");Debug.Assert(isOkProperty != null, nameof(isOkProperty) + " != null");var namePropExp = Expression.Property(inputExp, propertyInfo);var nameNameExp = Expression.Constant(propertyInfo.Name);var requiredMethodExp = Expression.Call(requireMethod, nameNameExp, namePropExp);var assignExp = Expression.Assign(resultExp, requiredMethodExp);var resultIsOkPropertyExp = Expression.Property(resultExp, isOkProperty);var conditionExp = Expression.IsFalse(resultIsOkPropertyExp);var ifThenExp =Expression.IfThen(conditionExp,Expression.Return(returnLabel, resultExp));var re = Expression.Block(new[] {resultExp},assignExp,ifThenExp);return re;}Expression CreateValidateStringMinLengthExpression(PropertyInfo propertyInfo){var minLengthMethod =typeof(X03PropertyValidationTest04).GetMethod(nameof(ValidateStringMinLength));var isOkProperty = typeof(ValidateResult).GetProperty(nameof(ValidateResult.IsOk));Debug.Assert(minLengthMethod != null, nameof(minLengthMethod) + " != null");Debug.Assert(isOkProperty != null, nameof(isOkProperty) + " != null");var namePropExp = Expression.Property(inputExp, propertyInfo);var nameNameExp = Expression.Constant(propertyInfo.Name);var requiredMethodExp = Expression.Call(minLengthMethod,nameNameExp,namePropExp,minLengthPExp);var assignExp = Expression.Assign(resultExp, requiredMethodExp);var resultIsOkPropertyExp = Expression.Property(resultExp, isOkProperty);var conditionExp = Expression.IsFalse(resultIsOkPropertyExp);var ifThenExp =Expression.IfThen(conditionExp,Expression.Return(returnLabel, resultExp));var re = Expression.Block(new[] {resultExp},assignExp,ifThenExp);return re;}}}catch (Exception e){Console.WriteLine(e);throw;}}[Test]public void Run(){// see code in demo repo}public static ValidateResult Validate(CreateClaptrapInput input){return _func.Invoke(input, 3);}public static ValidateResult ValidateStringRequired(string name, string value){return string.IsNullOrEmpty(value)? ValidateResult.Error($"missing {name}"): ValidateResult.Ok();}public static ValidateResult ValidateStringMinLength(string name, string value, int minLength){return value.Length < minLength? ValidateResult.Error($"Length of {name} should be great than {minLength}"): ValidateResult.Ok();}public class CreateClaptrapInput{[Required] [MinLength(3)] public string Name { get; set; }[Required] [MinLength(3)] public string NickName { get; set; }}} }


    在 CreateClaptrapInput 中增加了一個屬性 NickName ,測試用例也將驗證該屬性。代碼要點:

  • 通過?List<Expression>?我們將更多動態(tài)生成的表達式加入到了 Block 中。因此,我們可以對 Name 和 NickName 都生成驗證表達式。


  • 第五步,通過 Attribute 決定驗證內(nèi)容

    盡管前面我們已經(jīng)支持驗證多種屬性了,但是關于是否進行驗證以及驗證的參數(shù)依然是寫死的(例如:MinLength 的長度)。

    本節(jié),我們將通過 Attribute 來決定驗證的細節(jié)。例如被標記為 Required 是屬性才會進行必填驗證。

    using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Diagnostics; using System.Linq; using System.Linq.Expressions; using System.Reflection; using FluentAssertions; using NUnit.Framework;// ReSharper disable InvalidXmlDocCommentnamespace Newbe.ExpressionsTests {/// <summary>/// Using Attribute/// </summary>public class X03PropertyValidationTest05{private const int Count = 10_000;private static Func<CreateClaptrapInput, ValidateResult> _func;[SetUp]public void Init(){try{var finalExpression = CreateCore();_func = finalExpression.Compile();Expression<Func<CreateClaptrapInput, ValidateResult>> CreateCore(){// exp for inputvar inputExp = Expression.Parameter(typeof(CreateClaptrapInput), "input");// exp for outputvar resultExp = Expression.Variable(typeof(ValidateResult), "result");// exp for return statementvar returnLabel = Expression.Label(typeof(ValidateResult));var innerExps = new List<Expression> {CreateDefaultResult()};var stringProps = typeof(CreateClaptrapInput).GetProperties().Where(x => x.PropertyType == typeof(string));foreach (var propertyInfo in stringProps){if (propertyInfo.GetCustomAttribute<RequiredAttribute>() != null){innerExps.Add(CreateValidateStringRequiredExpression(propertyInfo));}var minlengthAttribute = propertyInfo.GetCustomAttribute<MinLengthAttribute>();if (minlengthAttribute != null){innerExps.Add(CreateValidateStringMinLengthExpression(propertyInfo, minlengthAttribute.Length));}}innerExps.Add(Expression.Label(returnLabel, resultExp));// build whole blockvar body = Expression.Block(new[] {resultExp},innerExps);// build lambda from bodyvar final = Expression.Lambda<Func<CreateClaptrapInput, ValidateResult>>(body,inputExp);return final;Expression CreateDefaultResult(){var okMethod = typeof(ValidateResult).GetMethod(nameof(ValidateResult.Ok));Debug.Assert(okMethod != null, nameof(okMethod) + " != null");var methodCallExpression = Expression.Call(okMethod);var re = Expression.Assign(resultExp, methodCallExpression);/*** final as:* result = ValidateResult.Ok()*/return re;}Expression CreateValidateStringRequiredExpression(PropertyInfo propertyInfo){var requireMethod = typeof(X03PropertyValidationTest05).GetMethod(nameof(ValidateStringRequired));var isOkProperty = typeof(ValidateResult).GetProperty(nameof(ValidateResult.IsOk));Debug.Assert(requireMethod != null, nameof(requireMethod) + " != null");Debug.Assert(isOkProperty != null, nameof(isOkProperty) + " != null");var namePropExp = Expression.Property(inputExp, propertyInfo);var nameNameExp = Expression.Constant(propertyInfo.Name);var requiredMethodExp = Expression.Call(requireMethod, nameNameExp, namePropExp);var assignExp = Expression.Assign(resultExp, requiredMethodExp);var resultIsOkPropertyExp = Expression.Property(resultExp, isOkProperty);var conditionExp = Expression.IsFalse(resultIsOkPropertyExp);var ifThenExp =Expression.IfThen(conditionExp,Expression.Return(returnLabel, resultExp));var re = Expression.Block(new[] {resultExp},assignExp,ifThenExp);return re;}Expression CreateValidateStringMinLengthExpression(PropertyInfo propertyInfo,int minlengthAttributeLength){var minLengthMethod =typeof(X03PropertyValidationTest05).GetMethod(nameof(ValidateStringMinLength));var isOkProperty = typeof(ValidateResult).GetProperty(nameof(ValidateResult.IsOk));Debug.Assert(minLengthMethod != null, nameof(minLengthMethod) + " != null");Debug.Assert(isOkProperty != null, nameof(isOkProperty) + " != null");var namePropExp = Expression.Property(inputExp, propertyInfo);var nameNameExp = Expression.Constant(propertyInfo.Name);var requiredMethodExp = Expression.Call(minLengthMethod,nameNameExp,namePropExp,Expression.Constant(minlengthAttributeLength));var assignExp = Expression.Assign(resultExp, requiredMethodExp);var resultIsOkPropertyExp = Expression.Property(resultExp, isOkProperty);var conditionExp = Expression.IsFalse(resultIsOkPropertyExp);var ifThenExp =Expression.IfThen(conditionExp,Expression.Return(returnLabel, resultExp));var re = Expression.Block(new[] {resultExp},assignExp,ifThenExp);return re;}}}catch (Exception e){Console.WriteLine(e);throw;}}[Test]public void Run(){// see code in demo repo}public class CreateClaptrapInput{[Required] [MinLength(3)] public string Name { get; set; }[Required] [MinLength(3)] public string NickName { get; set; }}} }


    在構建?List<Expression>?時通過屬性上的 Attribute 上的決定是否加入特定的表達式。代碼要點:


    第六步,將靜態(tài)方法換為表達式

    ValidateStringRequired 和 ValidateStringMinLength 兩個靜態(tài)方法的內(nèi)部實際上只包含一個判斷三目表達式,而且在 C# 中,可以將 Lambda 方法賦值個一個表達式。

    因此,我們可以直接將 ValidateStringRequired 和 ValidateStringMinLength 改換為表達式,這樣就不需要反射來獲取靜態(tài)方法再去構建表達式了。

    using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Diagnostics; using System.Linq; using System.Linq.Expressions; using System.Reflection; using FluentAssertions; using NUnit.Framework;// ReSharper disable InvalidXmlDocCommentnamespace Newbe.ExpressionsTests {/// <summary>/// Static Method to Expression/// </summary>public class X03PropertyValidationTest06{private const int Count = 10_000;private static Func<CreateClaptrapInput, ValidateResult> _func;[SetUp]public void Init(){try{var finalExpression = CreateCore();_func = finalExpression.Compile();Expression<Func<CreateClaptrapInput, ValidateResult>> CreateCore(){// exp for inputvar inputExp = Expression.Parameter(typeof(CreateClaptrapInput), "input");// exp for outputvar resultExp = Expression.Variable(typeof(ValidateResult), "result");// exp for return statementvar returnLabel = Expression.Label(typeof(ValidateResult));var innerExps = new List<Expression> {CreateDefaultResult()};var stringProps = typeof(CreateClaptrapInput).GetProperties().Where(x => x.PropertyType == typeof(string));foreach (var propertyInfo in stringProps){if (propertyInfo.GetCustomAttribute<RequiredAttribute>() != null){innerExps.Add(CreateValidateStringRequiredExpression(propertyInfo));}var minlengthAttribute = propertyInfo.GetCustomAttribute<MinLengthAttribute>();if (minlengthAttribute != null){innerExps.Add(CreateValidateStringMinLengthExpression(propertyInfo, minlengthAttribute.Length));}}innerExps.Add(Expression.Label(returnLabel, resultExp));// build whole blockvar body = Expression.Block(new[] {resultExp},innerExps);// build lambda from bodyvar final = Expression.Lambda<Func<CreateClaptrapInput, ValidateResult>>(body,inputExp);return final;Expression CreateDefaultResult(){var okMethod = typeof(ValidateResult).GetMethod(nameof(ValidateResult.Ok));Debug.Assert(okMethod != null, nameof(okMethod) + " != null");var methodCallExpression = Expression.Call(okMethod);var re = Expression.Assign(resultExp, methodCallExpression);/*** final as:* result = ValidateResult.Ok()*/return re;}Expression CreateValidateStringRequiredExpression(PropertyInfo propertyInfo){var isOkProperty = typeof(ValidateResult).GetProperty(nameof(ValidateResult.IsOk));Debug.Assert(isOkProperty != null, nameof(isOkProperty) + " != null");var namePropExp = Expression.Property(inputExp, propertyInfo);var nameNameExp = Expression.Constant(propertyInfo.Name);var requiredMethodExp = Expression.Invoke(ValidateStringRequiredExp, nameNameExp, namePropExp);var assignExp = Expression.Assign(resultExp, requiredMethodExp);var resultIsOkPropertyExp = Expression.Property(resultExp, isOkProperty);var conditionExp = Expression.IsFalse(resultIsOkPropertyExp);var ifThenExp =Expression.IfThen(conditionExp,Expression.Return(returnLabel, resultExp));var re = Expression.Block(new[] {resultExp},assignExp,ifThenExp);return re;}Expression CreateValidateStringMinLengthExpression(PropertyInfo propertyInfo,int minlengthAttributeLength){var isOkProperty = typeof(ValidateResult).GetProperty(nameof(ValidateResult.IsOk));Debug.Assert(isOkProperty != null, nameof(isOkProperty) + " != null");var namePropExp = Expression.Property(inputExp, propertyInfo);var nameNameExp = Expression.Constant(propertyInfo.Name);var requiredMethodExp = Expression.Invoke(ValidateStringMinLengthExp,nameNameExp,namePropExp,Expression.Constant(minlengthAttributeLength));var assignExp = Expression.Assign(resultExp, requiredMethodExp);var resultIsOkPropertyExp = Expression.Property(resultExp, isOkProperty);var conditionExp = Expression.IsFalse(resultIsOkPropertyExp);var ifThenExp =Expression.IfThen(conditionExp,Expression.Return(returnLabel, resultExp));var re = Expression.Block(new[] {resultExp},assignExp,ifThenExp);return re;}}}catch (Exception e){Console.WriteLine(e);throw;}}[Test]public void Run(){// see code in demo repo}private static readonly Expression<Func<string, string, ValidateResult>> ValidateStringRequiredExp =(name, value) =>string.IsNullOrEmpty(value)? ValidateResult.Error($"missing {name}"): ValidateResult.Ok();private static readonly Expression<Func<string, string, int, ValidateResult>> ValidateStringMinLengthExp =(name, value, minLength) =>value.Length < minLength? ValidateResult.Error($"Length of {name} should be great than {minLength}"): ValidateResult.Ok();} }


    將靜態(tài)方法換成了表達式。因此 CreateXXXExpression 相應的位置也進行了修改,代碼就更短了。代碼要點:


    第七步,柯里化

    柯理化,也稱為函數(shù)柯理化,是函數(shù)式編程當中的一種方法。簡單的可以表述為:通過固定一個多參數(shù)函數(shù)的一個或幾個參數(shù),從而得到一個參數(shù)更少的函數(shù)。術語化一些,也可以表述為將高階函數(shù)(函數(shù)的階其實就是說參數(shù)的個數(shù))轉(zhuǎn)換為低階函數(shù)的方法。

    例如,現(xiàn)在有一個 add (int,int) 的函數(shù),它實現(xiàn)了將兩個數(shù)相加的功能。假如我們固定集中第一個參數(shù)為 5 ,則我們會得到一個 add (5,int) 的函數(shù),它實現(xiàn)的是將一個數(shù)加 5 的功能。

    這有什么意義?

    函數(shù)降階可以使得函數(shù)變得一致,得到了一致的函數(shù)之后可以做一些代碼上的統(tǒng)一以便優(yōu)化。例如上面使用到的兩個表達式:

  • Expression<Func<string, string, ValidateResult>> ValidateStringRequiredExp

  • Expression<Func<string, string, int, ValidateResult>> ValidateStringMinLengthExp

  • 這兩個表達式中第二個表達式和第一個表達式之間僅僅區(qū)別在第三參數(shù)上。如果我們使用柯理化固定第三個 int 參數(shù),則可以使得兩個表達式的簽名完全一樣。這其實和面向?qū)ο笾械某橄蠓浅n愃啤?/p>using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Diagnostics; using System.Linq; using System.Linq.Expressions; using System.Reflection; using FluentAssertions; using NUnit.Framework;// ReSharper disable InvalidXmlDocCommentnamespace Newbe.ExpressionsTests {/// <summary>/// Currying/// </summary>public class X03PropertyValidationTest07{private const int Count = 10_000;private static Func<CreateClaptrapInput, ValidateResult> _func;[SetUp]public void Init(){try{var finalExpression = CreateCore();_func = finalExpression.Compile();Expression<Func<CreateClaptrapInput, ValidateResult>> CreateCore(){// exp for inputvar inputExp = Expression.Parameter(typeof(CreateClaptrapInput), "input");// exp for outputvar resultExp = Expression.Variable(typeof(ValidateResult), "result");// exp for return statementvar returnLabel = Expression.Label(typeof(ValidateResult));var innerExps = new List<Expression> {CreateDefaultResult()};var stringProps = typeof(CreateClaptrapInput).GetProperties().Where(x => x.PropertyType == typeof(string));foreach (var propertyInfo in stringProps){if (propertyInfo.GetCustomAttribute<RequiredAttribute>() != null){innerExps.Add(CreateValidateStringRequiredExpression(propertyInfo));}var minlengthAttribute = propertyInfo.GetCustomAttribute<MinLengthAttribute>();if (minlengthAttribute != null){innerExps.Add(CreateValidateStringMinLengthExpression(propertyInfo, minlengthAttribute.Length));}}innerExps.Add(Expression.Label(returnLabel, resultExp));// build whole blockvar body = Expression.Block(new[] {resultExp},innerExps);// build lambda from bodyvar final = Expression.Lambda<Func<CreateClaptrapInput, ValidateResult>>(body,inputExp);return final;Expression CreateDefaultResult(){var okMethod = typeof(ValidateResult).GetMethod(nameof(ValidateResult.Ok));Debug.Assert(okMethod != null, nameof(okMethod) + " != null");var methodCallExpression = Expression.Call(okMethod);var re = Expression.Assign(resultExp, methodCallExpression);/*** final as:* result = ValidateResult.Ok()*/return re;}Expression CreateValidateStringRequiredExpression(PropertyInfo propertyInfo){var isOkProperty = typeof(ValidateResult).GetProperty(nameof(ValidateResult.IsOk));Debug.Assert(isOkProperty != null, nameof(isOkProperty) + " != null");var namePropExp = Expression.Property(inputExp, propertyInfo);var nameNameExp = Expression.Constant(propertyInfo.Name);var requiredMethodExp =Expression.Invoke(CreateValidateStringRequiredExp(),nameNameExp,namePropExp);var assignExp = Expression.Assign(resultExp, requiredMethodExp);var resultIsOkPropertyExp = Expression.Property(resultExp, isOkProperty);var conditionExp = Expression.IsFalse(resultIsOkPropertyExp);var ifThenExp =Expression.IfThen(conditionExp,Expression.Return(returnLabel, resultExp));var re = Expression.Block(new[] {resultExp},assignExp,ifThenExp);return re;}Expression CreateValidateStringMinLengthExpression(PropertyInfo propertyInfo,int minlengthAttributeLength){var isOkProperty = typeof(ValidateResult).GetProperty(nameof(ValidateResult.IsOk));Debug.Assert(isOkProperty != null, nameof(isOkProperty) + " != null");var namePropExp = Expression.Property(inputExp, propertyInfo);var nameNameExp = Expression.Constant(propertyInfo.Name);var requiredMethodExp = Expression.Invoke(CreateValidateStringMinLengthExp(minlengthAttributeLength),nameNameExp,namePropExp);var assignExp = Expression.Assign(resultExp, requiredMethodExp);var resultIsOkPropertyExp = Expression.Property(resultExp, isOkProperty);var conditionExp = Expression.IsFalse(resultIsOkPropertyExp);var ifThenExp =Expression.IfThen(conditionExp,Expression.Return(returnLabel, resultExp));var re = Expression.Block(new[] {resultExp},assignExp,ifThenExp);return re;}}}catch (Exception e){Console.WriteLine(e);throw;}}[Test]public void Run(){// see code in demo repo}private static Expression<Func<string, string, ValidateResult>> CreateValidateStringRequiredExp(){return (name, value) =>string.IsNullOrEmpty(value)? ValidateResult.Error($"missing {name}"): ValidateResult.Ok();}private static Expression<Func<string, string, ValidateResult>> CreateValidateStringMinLengthExp(int minLength){return (name, value) =>value.Length < minLength? ValidateResult.Error($"Length of {name} should be great than {minLength}"): ValidateResult.Ok();}} }


    CreateValidateStringMinLengthExp 靜態(tài)方法,傳入一個參數(shù)創(chuàng)建得到一個和 CreateValidateStringRequiredExp 返回值一樣的表達式。對比上一節(jié)中的 ValidateStringMinLengthExp ,實現(xiàn)了固定 int 參數(shù)而得到一個新表達式的操作。這就是一種柯理化的體現(xiàn)。代碼要點:

  • 為了統(tǒng)一都采用靜態(tài)方法,我們將上一節(jié)中的 ValidateStringRequiredExp 也改為 CreateValidateStringRequiredExp 靜態(tài)方法,這僅僅只是為了看起來一致(但實際上增加了一點點開銷,因為沒必要重復創(chuàng)建一個不變的表達式)。

  • 相應的調(diào)整一下?List<Expression>?組裝過程的代碼。


  • 第八步,合并重復代碼

    本節(jié),我們將合并 CreateValidateStringRequiredExpression 和 CreateValidateStringMinLengthExpression 中重復的代碼。

    其中只有 requiredMethodExp 的創(chuàng)建方式不同。因此,只要將這個參數(shù)從方法外面?zhèn)魅刖涂梢猿殡x出公共部分。

    using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Diagnostics; using System.Linq; using System.Linq.Expressions; using System.Reflection; using FluentAssertions; using NUnit.Framework;// ReSharper disable InvalidXmlDocCommentnamespace Newbe.ExpressionsTests {/// <summary>/// Refactor to CreateValidateExpression/// </summary>public class X03PropertyValidationTest08{private const int Count = 10_000;private static Func<CreateClaptrapInput, ValidateResult> _func;[SetUp]public void Init(){try{var finalExpression = CreateCore();_func = finalExpression.Compile();Expression<Func<CreateClaptrapInput, ValidateResult>> CreateCore(){// exp for inputvar inputExp = Expression.Parameter(typeof(CreateClaptrapInput), "input");// exp for outputvar resultExp = Expression.Variable(typeof(ValidateResult), "result");// exp for return statementvar returnLabel = Expression.Label(typeof(ValidateResult));var innerExps = new List<Expression> {CreateDefaultResult()};var stringProps = typeof(CreateClaptrapInput).GetProperties().Where(x => x.PropertyType == typeof(string));foreach (var propertyInfo in stringProps){if (propertyInfo.GetCustomAttribute<RequiredAttribute>() != null){innerExps.Add(CreateValidateStringRequiredExpression(propertyInfo));}var minlengthAttribute = propertyInfo.GetCustomAttribute<MinLengthAttribute>();if (minlengthAttribute != null){innerExps.Add(CreateValidateStringMinLengthExpression(propertyInfo, minlengthAttribute.Length));}}innerExps.Add(Expression.Label(returnLabel, resultExp));// build whole blockvar body = Expression.Block(new[] {resultExp},innerExps);// build lambda from bodyvar final = Expression.Lambda<Func<CreateClaptrapInput, ValidateResult>>(body,inputExp);return final;Expression CreateDefaultResult(){var okMethod = typeof(ValidateResult).GetMethod(nameof(ValidateResult.Ok));Debug.Assert(okMethod != null, nameof(okMethod) + " != null");var methodCallExpression = Expression.Call(okMethod);var re = Expression.Assign(resultExp, methodCallExpression);/*** final as:* result = ValidateResult.Ok()*/return re;}Expression CreateValidateStringRequiredExpression(PropertyInfo propertyInfo)=> CreateValidateExpression(propertyInfo,CreateValidateStringRequiredExp());Expression CreateValidateStringMinLengthExpression(PropertyInfo propertyInfo,int minlengthAttributeLength)=> CreateValidateExpression(propertyInfo,CreateValidateStringMinLengthExp(minlengthAttributeLength));Expression CreateValidateExpression(PropertyInfo propertyInfo,Expression<Func<string, string, ValidateResult>> validateFuncExpression){var isOkProperty = typeof(ValidateResult).GetProperty(nameof(ValidateResult.IsOk));Debug.Assert(isOkProperty != null, nameof(isOkProperty) + " != null");var namePropExp = Expression.Property(inputExp, propertyInfo);var nameNameExp = Expression.Constant(propertyInfo.Name);var requiredMethodExp = Expression.Invoke(validateFuncExpression,nameNameExp,namePropExp);var assignExp = Expression.Assign(resultExp, requiredMethodExp);var resultIsOkPropertyExp = Expression.Property(resultExp, isOkProperty);var conditionExp = Expression.IsFalse(resultIsOkPropertyExp);var ifThenExp =Expression.IfThen(conditionExp,Expression.Return(returnLabel, resultExp));var re = Expression.Block(new[] {resultExp},assignExp,ifThenExp);return re;}}}catch (Exception e){Console.WriteLine(e);throw;}}[Test]public void Run(){// see code in demo repo}} }


    CreateValidateExpression 就是被抽離出來的公共方法。代碼要點:

  • 如果沒有前一步柯理化,CreateValidateExpression 的第二個參數(shù) validateFuncExpression 將很難確定。

  • CreateValidateStringRequiredExpression 和 CreateValidateStringMinLengthExpression 內(nèi)部調(diào)用了 CreateValidateExpression,但是固定了幾個參數(shù)。這其實也可以被認為是一種柯理化,因為返回值是表達式其實可以被認為是一種函數(shù)的表現(xiàn)形式,當然理解為重載也沒有問題,不必太過糾結。


  • 第九步,支持更多模型

    到現(xiàn)在,我們已經(jīng)得到了一個支持驗證 CreateClaptrapInput 多個 string 字段的驗證器。并且,即使要擴展多更多類型也不是太難,只要增加表達式即可。

    本節(jié),我們將 CreateClaptrapInput 抽象為更抽象的類型,畢竟沒有模型驗證器是專門只能驗證一個 class 的。

    using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Diagnostics; using System.Linq; using System.Linq.Expressions; using System.Reflection; using FluentAssertions; using NUnit.Framework;// ReSharper disable InvalidXmlDocCommentnamespace Newbe.ExpressionsTests {/// <summary>/// Multiple Type/// </summary>public class X03PropertyValidationTest09{private const int Count = 10_000;private static readonly Dictionary<Type, Func<object, ValidateResult>> ValidateFunc =new Dictionary<Type, Func<object, ValidateResult>>();[SetUp]public void Init(){try{var finalExpression = CreateCore(typeof(CreateClaptrapInput));ValidateFunc[typeof(CreateClaptrapInput)] = finalExpression.Compile();Expression<Func<object, ValidateResult>> CreateCore(Type type){// exp for inputvar inputExp = Expression.Parameter(typeof(object), "input");// exp for outputvar resultExp = Expression.Variable(typeof(ValidateResult), "result");// exp for return statementvar returnLabel = Expression.Label(typeof(ValidateResult));var innerExps = new List<Expression> {CreateDefaultResult()};var stringProps = type.GetProperties().Where(x => x.PropertyType == typeof(string));foreach (var propertyInfo in stringProps){if (propertyInfo.GetCustomAttribute<RequiredAttribute>() != null){innerExps.Add(CreateValidateStringRequiredExpression(propertyInfo));}var minlengthAttribute = propertyInfo.GetCustomAttribute<MinLengthAttribute>();if (minlengthAttribute != null){innerExps.Add(CreateValidateStringMinLengthExpression(propertyInfo, minlengthAttribute.Length));}}innerExps.Add(Expression.Label(returnLabel, resultExp));// build whole blockvar body = Expression.Block(new[] {resultExp},innerExps);// build lambda from bodyvar final = Expression.Lambda<Func<object, ValidateResult>>(body,inputExp);return final;Expression CreateDefaultResult(){var okMethod = typeof(ValidateResult).GetMethod(nameof(ValidateResult.Ok));Debug.Assert(okMethod != null, nameof(okMethod) + " != null");var methodCallExpression = Expression.Call(okMethod);var re = Expression.Assign(resultExp, methodCallExpression);/*** final as:* result = ValidateResult.Ok()*/return re;}Expression CreateValidateStringRequiredExpression(PropertyInfo propertyInfo)=> CreateValidateExpression(propertyInfo,CreateValidateStringRequiredExp());Expression CreateValidateStringMinLengthExpression(PropertyInfo propertyInfo,int minlengthAttributeLength)=> CreateValidateExpression(propertyInfo,CreateValidateStringMinLengthExp(minlengthAttributeLength));Expression CreateValidateExpression(PropertyInfo propertyInfo,Expression<Func<string, string, ValidateResult>> validateFuncExpression){var isOkProperty = typeof(ValidateResult).GetProperty(nameof(ValidateResult.IsOk));Debug.Assert(isOkProperty != null, nameof(isOkProperty) + " != null");var convertedExp = Expression.Convert(inputExp, type);var namePropExp = Expression.Property(convertedExp, propertyInfo);var nameNameExp = Expression.Constant(propertyInfo.Name);var requiredMethodExp = Expression.Invoke(validateFuncExpression,nameNameExp,namePropExp);var assignExp = Expression.Assign(resultExp, requiredMethodExp);var resultIsOkPropertyExp = Expression.Property(resultExp, isOkProperty);var conditionExp = Expression.IsFalse(resultIsOkPropertyExp);var ifThenExp =Expression.IfThen(conditionExp,Expression.Return(returnLabel, resultExp));var re = Expression.Block(new[] {resultExp},assignExp,ifThenExp);return re;}}}catch (Exception e){Console.WriteLine(e);throw;}}[Test]public void Run(){// see code in demo repo}public static ValidateResult Validate(CreateClaptrapInput input){return ValidateFunc[typeof(CreateClaptrapInput)].Invoke(input);}} }


    將?Func<CreateClaptrapInput, ValidateResult>?替換為了?Func<object, ValidateResult>,并且將寫死的 typeof (CreateClaptrapInput) 都替換為了 type。代碼要點:

  • 將對應類型的驗證器創(chuàng)建好之后保存在 ValidateFunc 中。這樣就不需要每次都重建整個 Func。


  • 第十步,加入一些細節(jié)

    最后的最后,我們又到了令人愉快的 “加入一些細節(jié)” 階段:按照業(yè)務特性對抽象接口和實現(xiàn)進行調(diào)整。于是我們就得到了本示例最終的版本。

    using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Diagnostics; using System.Linq; using System.Linq.Expressions; using System.Reflection; using Autofac; using FluentAssertions; using NUnit.Framework; using Module = Autofac.Module;// ReSharper disable InvalidXmlDocCommentnamespace Newbe.ExpressionsTests {/// <summary>/// Final/// </summary>public class X03PropertyValidationTest10{private const int Count = 10_000;private IValidatorFactory _factory = null!;[SetUp]public void Init(){try{var builder = new ContainerBuilder();builder.RegisterModule<ValidatorModule>();var container = builder.Build();_factory = container.Resolve<IValidatorFactory>();}catch (Exception e){Console.WriteLine(e);throw;}}[Test]public void Run(){for (int i = 0; i < Count; i++){// test 1{var input = new CreateClaptrapInput{NickName = "newbe36524"};var (isOk, errorMessage) = Validate(input);isOk.Should().BeFalse();errorMessage.Should().Be("missing Name");}// test 2{var input = new CreateClaptrapInput{Name = "1",NickName = "newbe36524"};var (isOk, errorMessage) = Validate(input);isOk.Should().BeFalse();errorMessage.Should().Be("Length of Name should be great than 3");}// test 3{var input = new CreateClaptrapInput{Name = "yueluo is the only one dalao",NickName = "newbe36524"};var (isOk, errorMessage) = Validate(input);isOk.Should().BeTrue();errorMessage.Should().BeNullOrEmpty();}}}public ValidateResult Validate(CreateClaptrapInput input){Debug.Assert(_factory != null, nameof(_factory) + " != null");var validator = _factory.GetValidator(typeof(CreateClaptrapInput));return validator.Invoke(input);}public class CreateClaptrapInput{[Required] [MinLength(3)] public string Name { get; set; }[Required] [MinLength(3)] public string NickName { get; set; }}public struct ValidateResult{public bool IsOk { get; set; }public string ErrorMessage { get; set; }public void Deconstruct(out bool isOk, out string errorMessage){isOk = IsOk;errorMessage = ErrorMessage;}public static ValidateResult Ok(){return new ValidateResult{IsOk = true};}public static ValidateResult Error(string errorMessage){return new ValidateResult{IsOk = false,ErrorMessage = errorMessage};}}private class ValidatorModule : Module{protected override void Load(ContainerBuilder builder){base.Load(builder);builder.RegisterType<ValidatorFactory>().As<IValidatorFactory>().SingleInstance();builder.RegisterType<StringRequiredPropertyValidatorFactory>().As<IPropertyValidatorFactory>().SingleInstance();builder.RegisterType<StringLengthPropertyValidatorFactory>().As<IPropertyValidatorFactory>().SingleInstance();}}public interface IValidatorFactory{Func<object, ValidateResult> GetValidator(Type type);}public interface IPropertyValidatorFactory{IEnumerable<Expression> CreateExpression(CreatePropertyValidatorInput input);}public abstract class PropertyValidatorFactoryBase<TValue> : IPropertyValidatorFactory{public virtual IEnumerable<Expression> CreateExpression(CreatePropertyValidatorInput input){if (input.PropertyInfo.PropertyType != typeof(TValue)){return Enumerable.Empty<Expression>();}var expressionCore = CreateExpressionCore(input);return expressionCore;}protected abstract IEnumerable<Expression> CreateExpressionCore(CreatePropertyValidatorInput input);protected Expression CreateValidateExpression(CreatePropertyValidatorInput input,Expression<Func<string, TValue, ValidateResult>> validateFuncExpression){var propertyInfo = input.PropertyInfo;var isOkProperty = typeof(ValidateResult).GetProperty(nameof(ValidateResult.IsOk));Debug.Assert(isOkProperty != null, nameof(isOkProperty) + " != null");var convertedExp = Expression.Convert(input.InputExpression, input.InputType);var propExp = Expression.Property(convertedExp, propertyInfo);var nameExp = Expression.Constant(propertyInfo.Name);var requiredMethodExp = Expression.Invoke(validateFuncExpression,nameExp,propExp);var assignExp = Expression.Assign(input.ResultExpression, requiredMethodExp);var resultIsOkPropertyExp = Expression.Property(input.ResultExpression, isOkProperty);var conditionExp = Expression.IsFalse(resultIsOkPropertyExp);var ifThenExp =Expression.IfThen(conditionExp,Expression.Return(input.ReturnLabel, input.ResultExpression));var re = Expression.Block(new[] {input.ResultExpression},assignExp,ifThenExp);return re;}}public class StringRequiredPropertyValidatorFactory : PropertyValidatorFactoryBase<string>{private static Expression<Func<string, string, ValidateResult>> CreateValidateStringRequiredExp(){return (name, value) =>string.IsNullOrEmpty(value)? ValidateResult.Error($"missing {name}"): ValidateResult.Ok();}protected override IEnumerable<Expression> CreateExpressionCore(CreatePropertyValidatorInput input){var propertyInfo = input.PropertyInfo;if (propertyInfo.GetCustomAttribute<RequiredAttribute>() != null){yield return CreateValidateExpression(input, CreateValidateStringRequiredExp());}}}public class StringLengthPropertyValidatorFactory : PropertyValidatorFactoryBase<string>{private static Expression<Func<string, string, ValidateResult>> CreateValidateStringMinLengthExp(int minLength){return (name, value) =>string.IsNullOrEmpty(value) || value.Length < minLength? ValidateResult.Error($"Length of {name} should be great than {minLength}"): ValidateResult.Ok();}protected override IEnumerable<Expression> CreateExpressionCore(CreatePropertyValidatorInput input){var propertyInfo = input.PropertyInfo;var minlengthAttribute = propertyInfo.GetCustomAttribute<MinLengthAttribute>();if (minlengthAttribute != null){yield return CreateValidateExpression(input,CreateValidateStringMinLengthExp(minlengthAttribute.Length));}}}public class CreatePropertyValidatorInput{public Type InputType { get; set; } = null!;public Expression InputExpression { get; set; } = null!;public PropertyInfo PropertyInfo { get; set; } = null!;public ParameterExpression ResultExpression { get; set; } = null!;public LabelTarget ReturnLabel { get; set; } = null!;}public class ValidatorFactory : IValidatorFactory{private readonly IEnumerable<IPropertyValidatorFactory> _propertyValidatorFactories;public ValidatorFactory(IEnumerable<IPropertyValidatorFactory> propertyValidatorFactories){_propertyValidatorFactories = propertyValidatorFactories;}private Func<object, ValidateResult> CreateValidator(Type type){var finalExpression = CreateCore();return finalExpression.Compile();Expression<Func<object, ValidateResult>> CreateCore(){// exp for inputvar inputExp = Expression.Parameter(typeof(object), "input");// exp for outputvar resultExp = Expression.Variable(typeof(ValidateResult), "result");// exp for return statementvar returnLabel = Expression.Label(typeof(ValidateResult));var innerExps = new List<Expression> {CreateDefaultResult()};var validateExpressions = type.GetProperties().SelectMany(p => _propertyValidatorFactories.SelectMany(f =>f.CreateExpression(new CreatePropertyValidatorInput{InputExpression = inputExp,PropertyInfo = p,ResultExpression = resultExp,ReturnLabel = returnLabel,InputType = type,}))).ToArray();innerExps.AddRange(validateExpressions);innerExps.Add(Expression.Label(returnLabel, resultExp));// build whole blockvar body = Expression.Block(new[] {resultExp},innerExps);// build lambda from bodyvar final = Expression.Lambda<Func<object, ValidateResult>>(body,inputExp);return final;Expression CreateDefaultResult(){var okMethod = typeof(ValidateResult).GetMethod(nameof(ValidateResult.Ok));Debug.Assert(okMethod != null, nameof(okMethod) + " != null");var methodCallExpression = Expression.Call(okMethod);var re = Expression.Assign(resultExp, methodCallExpression);/*** final as:* result = ValidateResult.Ok()*/return re;}}}private static readonly ConcurrentDictionary<Type, Func<object, ValidateResult>> ValidateFunc =new ConcurrentDictionary<Type, Func<object, ValidateResult>>();public Func<object, ValidateResult> GetValidator(Type type){var re = ValidateFunc.GetOrAdd(type, CreateValidator);return re;}}} }


    IValidatorFactory 模型驗證器工廠,表示創(chuàng)建特定類型的驗證器委托代碼要點:

  • IPropertyValidatorFactory 具體屬性的驗證表達式創(chuàng)建工廠,可以根據(jù)規(guī)則的增加,追加新的實現(xiàn)。

  • 使用 Autofac 進行模塊管理。

  • 可以通過《在 C# 中使用依賴注入》來了解如何添加一些細節(jié)。https://www.newbe.pro/Use-Dependency-Injection/Use-Dependency-Injection-In-CSharp/


    隨堂小練

    別走!您還有作業(yè)。

    以下有一個按照難度分級的需求,開發(fā)者可以嘗試完成這些任務,進一步理解和使用本樣例中的代碼。


    增加一個驗證 string max length 的規(guī)則

    難度:D

    思路:

    和 min length 類似,別忘記注冊就行。


    增加一個驗證 int 必須大于等于 0 的規(guī)則

    難度:D

    思路:

    只是多了一個新的屬性類型,別忘記注冊就行。


    增加一個?IEnumerable<T>?對象必須包含至少一個元素的規(guī)則

    難度:C

    思路:

    可以用 Linq 中的 Any 方法來驗證


    增加一個?IEnumerable<T>?必須已經(jīng) ToList 或者 ToArray,類比 mvc 中的規(guī)則

    難度:C

    思路:

    其實只要驗證是否已經(jīng)是 ICollection 就可以了。


    支持空對象也輸出驗證結果

    難度:C

    思路:

    如果 input 為空。則也要能夠輸出第一條不滿足條件的規(guī)則。例如 Name Required。


    增加一個驗證 int? 必須有值的規(guī)則

    難度:B

    思路:

    int? 其實是語法糖,實際類型是?Nullable<int>。


    增加一個驗證枚舉必須符合給定的范圍

    難度:B

    思路:

    枚舉是可以被賦值以任意數(shù)值范圍的,例如定義了 Enum TestEnum {None = 0;} 但是,強行賦值 233 給這樣的屬性并不會報錯。該驗證需要驗證屬性值只能是定義的值。

    也可以增加自己的難度,例如支持驗證標記為 Flags 的枚舉的混合值范圍。


    添加一個驗證 int A 屬性必須和 int B 屬性大

    難度:A

    思路:

    需要有兩個屬性參與。啥都別管,先寫一個靜態(tài)函數(shù)來比較兩個數(shù)值的大小。然后在考慮如何表達式化,如何柯理化。可以參考前面思路。

    額外限定條件,不能修改現(xiàn)在接口定義。


    添加一個驗證 string A 屬性必須和 string B 屬性相等,忽略大小寫

    難度:A

    思路:

    和前一個類似。但是,string 的比較比 int 特殊,并且需要忽略大小寫。


    支持返回全部的驗證結果

    難度:S

    思路:

    調(diào)整驗證結果返回值,從返回第一個不滿足的規(guī)則,修改為返回所有不滿足的規(guī)則,類比 mvc model state 的效果。

    需要修改組合結果的表達式,可以有兩種辦法,一種是內(nèi)部創(chuàng)建 List 然后將結果放入,更為簡單的一種是使用 yield return 的方法進行返回。

    需要而外注意的是,由于所有規(guī)則都運行,一些判斷就需要進行防御性判斷。例如在 string 長度判斷時,需要先判斷其是否為空。至于 string 為空是否屬于滿足最小長度要求,開發(fā)者可以自由決定,不是重點。


    支持對象的遞歸驗證

    難度:SS

    思路:

    即如果對象包含一個屬性又是一個對象,則子對象也需要被驗證。

    有兩種思路:

    一是修改 ValidatorFactory 使其支持從 ValidateFunc 中獲取驗證器作為表達式的一部分。該思路需要解決的主要問題是,ValidateFunc 集合中可能提前不存在子模型的驗證器。可以使用 Lazy 來解決這個問題。

    二是創(chuàng)建一個 IPropertyValidatorFactory 實現(xiàn),使其能夠從 ValidatorFactory 中獲取 ValidateFunc 來驗證子模型。該思路主要要解決的問題是,直接實現(xiàn)可能會產(chǎn)生循環(huán)依賴。可以保存和生成 ValidateFunc 劃分在兩個接口中,解除這種循環(huán)依賴。該方案較為簡單。

    另外,晉級難度為 SSS,驗證?IEnumerable<>?中所有的元素。開發(fā)者可以嘗試。


    支持鏈式 API

    難度:SSS

    思路:

    形如 EntityFramework 中同時支持 Attribute 和鏈式 API 一樣,添加鏈式設置驗證的特性。

    這需要增加新的接口以便進行鏈式注冊,并且原來使用 Attribute 直接生成表達式的方法也應該調(diào)整為 Attribute -> 注冊數(shù)據(jù) -> 生成表達式。


    實現(xiàn)一個屬性修改器

    難度:SSS

    思路:

    實現(xiàn)一條規(guī)則,手機號碼加密,當對象的某個屬性是滿足長度為 11 的字符串,并且開頭是 1。則除了前三位和后四位之外的字符全部替換為?*。

    建議從頭開始實現(xiàn)屬性修改器,不要在上面的代碼上做變更。因為驗證和替換通常來說是兩個不同的業(yè)務,一個是為了輸入,一個是為了輸出。

    這里有一些額外的要求:

  • 在替換完成后,將此次被替換的所有值的前后情況輸出在日志中。

  • 注意,測試的性能要與直接調(diào)用方法相當,否則肯定是代碼實現(xiàn)存在問題。


  • 本文總結

    在.net 中,表達式樹可以用于兩種主要的場景。一種是用于解析結果,典型的就是 EntityFramework,而另外一種就是用于構建委托。

    本文通過構建委托的方式實現(xiàn)了一個模型驗證器的需求。生產(chǎn)實際中還可以用于很多動態(tài)調(diào)用的地方。

    掌握表達式樹,就掌握了一種可以取代反射進行動態(tài)調(diào)用的方法,這種方法不僅擴展性更好,而且性能也不錯。

    本篇內(nèi)容中的示例代碼,均可以在以下鏈接倉庫中找到:

    • https://github.com/newbe36524/Newbe.Demo

    • https://gitee.com/yks/Newbe.Demo

    最后但是最重要!

    如果讀者對該內(nèi)容感興趣,歡迎轉(zhuǎn)發(fā)、評論、收藏文章以及項目。

    最近作者正在構建以反應式、Actor模式和事件溯源為理論基礎的一套服務端開發(fā)框架。希望為開發(fā)者提供能夠便于開發(fā)出 “分布式”、“可水平擴展”、“可測試性高” 的應用系統(tǒng) ——Newbe.Claptrap

    本篇文章是該框架的一篇技術選文,屬于技術構成的一部分。

    聯(lián)系方式:

    • Github Issue

    • Gitee Issue

    • 公開郵箱?newbe-claptrap@googlegroups.com?(發(fā)送到該郵箱的內(nèi)容將被公開)

    • Gitter

    • QQ 群 610394020

    您還可以查閱本系列的其他選文:

    理論入門篇

  • Newbe.Claptrap - 一套以 “事件溯源” 和 “Actor 模式” 作為基本理論的服務端開發(fā)框架

  • 術語介紹篇

  • Actor 模式

  • 事件溯源(Event Sourcing)

  • Claptrap

  • Minion

  • 事件 (Event)

  • 狀態(tài) (State)

  • 狀態(tài)快照 (State Snapshot)

  • Claptrap 設計圖 (Claptrap Design)

  • Claptrap 工廠 (Claptrap Factory)

  • Claptrap Identity

  • Claptrap Box

  • Claptrap 生命周期(Claptrap Lifetime Scope)

  • 序列化(Serialization)

  • 實現(xiàn)入門篇

  • Newbe.Claptrap 框架入門,第一步 —— 創(chuàng)建項目,實現(xiàn)簡易購物車

  • Newbe.Claptrap 框架入門,第二步 —— 簡單業(yè)務,清空購物車

  • Newbe.Claptrap 框架入門,第三步 —— 定義 Claptrap,管理商品庫存

  • Newbe.Claptrap 框架入門,第四步 —— 利用 Minion,商品下單

  • 樣例實踐篇

  • 構建一個簡易的火車票售票系統(tǒng),Newbe.Claptrap 框架用例,第一步 —— 業(yè)務分析

  • 在線體驗火車票售票系統(tǒng)

  • 其他番外篇

  • 談反應式編程在服務端中的應用,數(shù)據(jù)庫操作優(yōu)化,從 20 秒到 0.5 秒

  • 談反應式編程在服務端中的應用,數(shù)據(jù)庫操作優(yōu)化,提速 Upsert

  • 十萬同時在線用戶,需要多少內(nèi)存?——Newbe.Claptrap 框架水平擴展實驗

  • docker-mcr 助您全速下載 dotnet 鏡像

  • 十多位全球技術專家,為你獻上近十個小時的.Net 微服務介紹

  • 年輕的樵夫喲,你掉的是這個免費 8 核 4G 公網(wǎng)服務器,還是這個隨時可用的 Docker 實驗平臺?

  • 如何使用 dotTrace 來診斷 netcore 應用的性能問題

  • 只要十步,你就可以應用表達式樹來優(yōu)化動態(tài)調(diào)用

  • GitHub 項目地址:https://github.com/newbe36524/Newbe.Claptrap

    Gitee 項目地址:https://gitee.com/yks/Newbe.Claptrap

    您當前查看的是先行發(fā)布于?www.newbe.pro?上的博客文章,實際開發(fā)文檔隨版本而迭代。若要查看最新的開發(fā)文檔,需要移步?claptrap.newbe.pro。

    總結

    以上是生活随笔為你收集整理的只要十步,你就可以应用表达式树来优化动态调用的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。