Lambda應(yīng)用設(shè)計模式

本文轉(zhuǎn)自https://www.cnblogs.com/lonelyxmas/p/3590472.html

前言

在使用 Lambda 表達式時,我們常會碰到一些典型的應(yīng)用場景断箫,而從常用場景中抽取出來的應(yīng)用方式可以描述為應(yīng)用模式扔水。這些模式可能不全是新的模式立膛,有的參考自 JavaScript 的設(shè)計模式掌动,但至少我看到了一些人為它們打上了名字標簽德澈。無論名字的好與壞挣棕,我還是決定給這些模式進行命名译隘,至少這些名字很具有描述性。同時我也會給出這些模式的可用性穴张、強大的部分和危險的部分细燎。提前先說明:絕大多數(shù)模式是非常強大的两曼,但有可能在代碼中引入些潛在的 Bug皂甘。所以,慎用悼凑。

目錄導(dǎo)航

回調(diào)模式 (Callback Pattern)

函數(shù)作為返回值 (Returning Functions)

自定義函數(shù) (Self-Defining Functions)

立即調(diào)用的函數(shù)表達式 (Immediately-Invoked Function Expression)

對象即時初始化 (Immediate Object Initialization)

初始化時間分支(Init-Time Branching)

延遲加載 (Lazy Loading)

屬性多態(tài)模式 (Lambda Property Polymorphism Pattern)

函數(shù)字典模式 (Function Dictionary Pattern)

函數(shù)式特性 (Functional Attribute Pattern)

避免循環(huán)引用 (Avoiding cyclic references)

回調(diào)模式 (Callback Pattern)

老生常談了偿枕。事實上,在 .NET 的第一個版本中就已經(jīng)支持回調(diào)模式了户辫,但形式有所不同〗タ洌現(xiàn)在通過 Lambda 表達式中的閉包和局部變量捕獲,這個功能變得越來越有趣了∮婊叮現(xiàn)在我們的代碼可以類似于:

void CreateTextBox()

? ? {

? ? ? var tb = new TextBox();

? ? ? tb.IsReadOnly = true;

? ? ? tb.Text = "Please wait ...";

? ? ? DoSomeStuff(() =>

? ? ? {

? ? ? ? tb.Text = string.Empty;

? ? ? ? tb.IsReadOnly = false;

? ? ? });

? ? }

? ? void DoSomeStuff(Action callback)

? ? {

? ? ? // Do some stuff - asynchronous would be helpful ...

? ? ? callback();

? ? }

對于 JavaScript 開發(fā)人員墓塌,這個模式已經(jīng)沒什么新鮮的了。而且通常我們在大量的使用這種模式奥额,因為其非常的有用苫幢。例如我們可以使用時間處理器作為參數(shù)來處理 AJAX 相關(guān)的事件等。在 LINQ 中垫挨,我們也使用了這個模式韩肝,例如 LINQ 中的 Where 會在每次迭代中回調(diào)查詢函數(shù)。這些僅是些能夠說明回調(diào)模式非常有用的簡單的示例九榔。在 .NET 中哀峻,通常推薦使用事件回調(diào)機制。原因有兩點哲泊,一是已經(jīng)提供了特殊的關(guān)鍵字和類型模式(有兩個參數(shù)剩蟀,一個是發(fā)送者,一個數(shù)事件參數(shù)切威,而發(fā)送者通常是 object 類型喻旷,而事件參數(shù)通常從 EventArgs 繼承),同時通過使用 += 和 -+ 操作符牢屋,也提供了調(diào)用多個方法的機會且预。

函數(shù)作為返回值 (Returning Functions)

就像常見的函數(shù)一樣槽袄,Lambda 表達式可以返回一個函數(shù)指針(委托實例)。這就意味著我們能夠使用一個 Lambda 表達式來創(chuàng)建并返回另一個 Lambda 表達式锋谐。這種行為在很多場景下都是非常有用的遍尺。我們先來看下面這個例子:

Func SayMyName(string language)

? ? {

? ? ? switch (language.ToLower())

? ? ? {

? ? ? ? case "fr":

? ? ? ? ? return name =>

? ? ? ? ? {

? ? ? ? ? ? return "Je m'appelle " + name + ".";

? ? ? ? ? };

? ? ? ? case "de":

? ? ? ? ? return name =>

? ? ? ? ? {

? ? ? ? ? ? return "Mein Name ist " + name + ".";

? ? ? ? ? };

? ? ? ? default:

? ? ? ? ? return name =>

? ? ? ? ? {

? ? ? ? ? ? return "My name is " + name + ".";

? ? ? ? ? };

? ? ? }

? ? }

? ? void Main()

? ? {

? ? ? var lang = "de";

? ? ? //Get language - e.g. by current OS settings

? ? ? var smn = SayMyName(lang);

? ? ? var name = Console.ReadLine();

? ? ? var sentence = smn(name);

? ? ? Console.WriteLine(sentence);

? ? }

這段代碼可以寫的更簡潔些。如果請求的語言類型未找到涮拗,我們可以直接拋出一個異常乾戏,以此來避免返回一個默認值。當然三热,出于演示的目的鼓择,這個例子展示了類似于一種函數(shù)工廠。另外一種方式是引入 Hashtable 就漾,或者更好的 Dictionary 類型呐能。

static class Translations { static readonly Dictionary> smnFunctions = new Dictionary>(); static Translations() { smnFunctions.Add("fr", name => "Je m'appelle " + name + "."); smnFunctions.Add("de", name => "Mein Name ist " + name + "."); smnFunctions.Add("en", name => "My name is " + name + "."); } public static Func GetSayMyName(string language)

? ? ? {

? ? ? ? //Check if the language is available has been omitted on purpose

? ? ? ? return smnFunctions[language];

? ? ? }

? ? }

? ? // Now it is sufficient to call Translations.GetSayMyName("de")

? ? // to get the function with the German translation.

盡管這看起來有點過度設(shè)計之嫌,但畢竟這種方式很容易擴展抑堡,并且可以應(yīng)用到很多場景下摆出。如果結(jié)合反射一起使用,可以使程序變得更靈活首妖,易于維護偎漫,并且更健壯。下面展示了這個模式如何工作:

自定義函數(shù) (Self-Defining Functions)

在 JavaScript 中有缆,自定義函數(shù)是一種極其常見的設(shè)計技巧象踊,并且在某些代碼中可以獲得更好的性能。這個模式的主要思想就是棚壁,將一個函數(shù)作為一個屬性杯矩,而此屬性可以被其他函數(shù)很容易的更改。

class SomeClass { public Func NextPrime

? ? ? {

? ? ? ? ? get;

? ? ? ? ? private set;

? ? ? }

? ? ? int prime;

? ? ? public SomeClass()

? ? ? {

? ? ? NextPrime = () =>

? ? ? {

? ? ? ? prime = 2;

? ? ? ? NextPrime = () =>

? ? ? ? {

? ? ? ? ? //Algorithm to determine next - starting at prime

? ? ? ? ? //Set prime

? ? ? ? ? return prime;

? ? ? ? };

? ? ? ? return prime;

? ? ? };

? ? ? }

? }

在這里做了什么呢灌曙?首先我們得到了第一個質(zhì)數(shù)菊碟,值為 2。這不是重點在刺,重點在于我們可以調(diào)整算法來排除所有偶數(shù)逆害。這在一定程度上會加快我們的算法,但我們?nèi)匀辉O(shè)置 2 為質(zhì)數(shù)的起點蚣驼。我們無需看到是否已經(jīng)調(diào)用了 NextPrime() 函數(shù)魄幕,因為根據(jù)函數(shù)內(nèi)的定義會直接返回 2。通過這種方式颖杏,我們節(jié)省了資源纯陨,并且能夠優(yōu)化算法。

同樣,我們也看到了這么做可以性能會更好翼抠。讓我們來看下下面這個例子:

Action loopBody = i => {

? ? ? ? ? if(i == 1000)

? ? ? ? ? ? ? loopBody = /* set to the body for the rest of the operations */;

? ? ? ? ? /* body for the first 1000 iterations */

? ? ? };

? ? ? for(int j = 0; j < 10000000; j++)

? ? ? ? ? loopBody(j);

這里我們有兩個截然不同的區(qū)域咙轩,一個是前 1000 次迭代,另一個是剩下的 9999000 次迭代阴颖。通常我們需要一個條件來區(qū)分這兩種情況活喊。大部分情況下會引起不必要的開銷,這也就是我們?yōu)槭裁匆褂米远x函數(shù)在執(zhí)行一小段代碼后來改變其自身量愧。

立即調(diào)用的函數(shù)表達式 (Immediately-Invoked Function Expression)

在 JavaScript 中钾菊,立即調(diào)用函數(shù)表達式(簡寫為 IIFE)是非常常見的用法殴泰。原因是 JavaScript 并沒有使用類似 C# 中的大括號方式來組織變量的作用域寂纪,而是根據(jù)函數(shù)塊來劃分的励堡。因此變量會污染全局對象鄙信,通常是 window 對象,這并不是我們期待的效果狐榔。

解決辦法也很簡單潦刃,盡管大括號并沒有給定作用域变隔,但函數(shù)給定了喘落,因為在函數(shù)體內(nèi)定義的變量的作用域均被限制在函數(shù)內(nèi)部茵宪。而 JavaScript 的使用者通常認為如果那些函數(shù)只是為了直接執(zhí)行最冰,為其中的變量和語句指定名稱然后再執(zhí)行就變成了一種浪費瘦棋。還有一個原因就是這些函數(shù)僅需要執(zhí)行一次。

在 C# 中暖哨,我們可以簡單編寫如下的函數(shù)達到同樣的功能赌朋。在這里我們同樣也得到了一個全新的作用域,但這并不是我們的主要目的篇裁,因為如果需要的話沛慢,我們可以在任何地方創(chuàng)建新的作用域。

(() => {2// Do Something here!3})();

代碼看起來很簡單达布。如果我們需要傳遞一些參數(shù)团甲,則需要指定參數(shù)的類型。

((strings,intno) => {2// Do Something here!3})("Example",8);

看起來寫了這么多行代碼并沒有給我們帶來什么好處黍聂。盡管如此躺苦,我們可以將這個模式和 async 關(guān)鍵字結(jié)合使用。

await(async(strings,intno) => {// Do Something here async using Tasks!})("Example",8);

//Continue here after the task has been finished

這樣产还,類似于異步包裝器的用法就形成了匹厘。

對象即時初始化 (Immediate Object Initialization)

將這個模式包含在這篇文章當中的原因是,匿名對象這個功能太強大了脐区,而且其不僅能包含簡單的類型愈诚,而且還能包含 Lambda 表達式。

//Create anonymous object

? ? ? var person = new

? ? ? {

? ? ? ? Name = "Florian",

? ? ? ? Age = 28,

? ? ? ? Ask = (string question) =>

? ? ? ? {

? ? ? ? ? Console.WriteLine("The answer to `" + question + "` is certainly 42!");

? ? ? ? }

? ? ? };

? ? ? //Execute function

? ? ? person.Ask("Why are you doing this?");

如果你運行了上面這段代碼,可能你會看到一個異常(至少我看到了)炕柔。原因是酌泰,Lambda 表達式不能被直接賦予匿名對象。如果你覺得不可思議匕累,那我們的感覺就一樣了宫莱。幸運的是,編譯器告訴了我們“老兄哩罪,我不知道我應(yīng)該為這個 Lambda 表達式創(chuàng)建什么樣的委托類型”授霸。既然這樣,我們就幫下編譯器际插。

var person = new { Name = "Florian", Age = 28, Ask = (Action)((string question) =>

? ? ? ? {

? ? ? ? ? Console.WriteLine("The answer to `" + question + "` is certainly 42!");

? ? ? ? })

? ? ? };

一個問題就出現(xiàn)了:這里的函數(shù)(Ask 方法)的作用域是什么碘耳?答案是,它就存活在創(chuàng)建這個匿名對象的類中框弛,或者如果它使用了被捕獲變量則存在于其自己的作用域中辛辨。所以,編譯器仍然創(chuàng)建了一個匿名對象瑟枫,然后將指向所創(chuàng)建的 Lambda 表達式的委托對象賦值給屬性 Ask斗搞。

注意:當我們想在匿名對象中直接設(shè)定的 Lambda 表達式中訪問匿名對象的任一屬性時,則盡量避免使用這個模式慷妙。原因是:C# 編譯器要求每個對象在被使用前需要先被聲明僻焚。在這種情況下,使用肯定在聲明之后膝擂,但是編譯器是怎么知道的虑啤?從編譯器的角度來看,在這種情況下聲明與使用是同時發(fā)生的架馋,因此變量 person 還沒有被聲明狞山。

有一個辦法可以幫助我們解決這個問題(實際上辦法有很多,但依我的觀點叉寂,這種方式是最優(yōu)雅的)萍启。

dynamic person = null; person = new { Name = "Florian", Age = 28, Ask = (Action)((string question) =>

? ? ? ? {

? ? ? ? ? Console.WriteLine("The answer to `" + question + "` is certainly 42! My age is " + person.Age + ".");

? ? ? ? })

? ? ? };

? ? ? //Execute function

? ? ? person.Ask("Why are you doing this?");

看,現(xiàn)在我們先聲明了它屏鳍。當然我們也可以直接將 person 聲明為 object 類型勘纯,但通過這種方式我們可以使用反射來訪問匿名對象中的屬性。此處我們依托于 DLR (Dynamic Language Runtime)來實現(xiàn)孕蝉,這應(yīng)該是最好的包裝方式了÷怕桑現(xiàn)在,這代碼看起來很有 JavaScript 范兒了降淮,但實際上我不知道這東西到底有什么用超埋。

初始化時間分支(Init-Time Branching)

這個模式與自定義函數(shù)模式密切相關(guān)搏讶。唯一的不同就是,函數(shù)不再定義其自身霍殴,而是通過其他函數(shù)定義媒惕。當然,其他函數(shù)也可能沒有通過傳統(tǒng)的方式去定義来庭,而是通過覆蓋屬性妒蔚。

這個模式通常也稱為加載時分支(Load-Time Branching),本質(zhì)上是一種優(yōu)化模式月弛。該模式被用于避免恒定的 switch-case 和 if-else 等控制結(jié)構(gòu)的使用肴盏。所以在某種程度上可以說,這種模式為某些恒定代碼分支之間建立了聯(lián)系帽衙。

public Action AutoSave { get; private set; }

? ? public void ReadSettings(Settings settings)

? ? {

? ? ? /* Read some settings of the user */

? ? ? if (settings.EnableAutoSave)

? ? ? ? AutoSave = () => { /* Perform Auto Save */ };

? ? ? else

? ? ? ? AutoSave = () => { }; //Just do nothing!

? ? }

這里我們做了兩件事菜皂。首先,我們有一個方法讀取了用戶設(shè)置信息厉萝。如果我們發(fā)現(xiàn)用于已經(jīng)打開了自動保存功能恍飘,則我們將保存代碼賦予該屬性。否則我們僅是指定一個空方法谴垫。然后章母,我們就可以一直調(diào)用 AutoSave 屬性在執(zhí)行操作。而且在此之后我們不再需要檢查用戶設(shè)置信息了翩剪。我們也不需要將這個特定的設(shè)置保存到一個 boolean 變量中乳怎,因為響應(yīng)的函數(shù)已經(jīng)被動態(tài)的設(shè)定了。

你可能說這并沒有太大的性能改善肢专,但這只是一個簡單的例子舞肆。在一些復(fù)雜的代碼中焦辅,這種方法確實可以節(jié)省很多時間博杖,尤其是在大循環(huán)中調(diào)用那個動態(tài)設(shè)置的方法時。

同時筷登,這樣的代碼可能更易于維護剃根,并非常易讀。在省去了很多不必要的控制過程之后前方,我們能夠直達重點:調(diào)用 AutoSave 函數(shù)狈醉。

在 JavaScript 中,這種模式常用于檢測瀏覽器的功能集惠险。瀏覽器功能的檢測對于任何網(wǎng)站來說都是噩夢一樣苗傅,而這個模式在實現(xiàn)中就顯得非常有用。同樣 jQuery 也使用了同樣的模式來檢測正確的對象班巩,以便使用 AJAX 功能渣慕。一旦它識別出瀏覽器支持 XMLHttpRequest ,則因為瀏覽器不會在腳本執(zhí)行期間變化,所以無需在考慮處理 ActiveX 對象了逊桦。

延遲加載 (Lazy Loading)

我們想要創(chuàng)建一個對象眨猎,它能夠執(zhí)行某種延遲加載操作。也就是說强经,盡管對象已經(jīng)被正確地初始化了睡陪,但我們并沒有加載所有需要的資源。一個原因是想避免在獲取需要的數(shù)據(jù)時引發(fā)的大量的 IO 操作匿情。同時兰迫,我們也想在準備使用數(shù)據(jù)時,數(shù)據(jù)盡可能是最新的炬称。有多種方式可以實現(xiàn)這個功能逮矛,而在 Entity Framework 中使用了效率極高的 LINQ 來解決延遲加載的情況。其中转砖,IQueryable 僅存儲了查詢而沒有存儲基礎(chǔ)的數(shù)據(jù)须鼎。一旦我們需要這些數(shù)據(jù),不僅已構(gòu)造的查詢會被執(zhí)行府蔗,而且查詢也是以最高效的形式來執(zhí)行晋控,例如在遠端數(shù)據(jù)庫服務(wù)器上執(zhí)行 SQL 查詢語句。

在我們想要的場景中姓赤,我們需要區(qū)別兩種狀況赡译。首先我們進行查詢,然后后續(xù)的操作將在已經(jīng)獲取到的結(jié)果上進行不铆。

class LazyLoad {?

?public LazyLoad()

?{ Search = query => {?

?var source = Database.SearchQuery(query);

?Search = subquery => {?

?var filtered = source.Filter(subquery);?

?foreach (var result in filtered) yield return result; };?

?foreach (var result in source) yield return result; };?

?}?

?public Func> Search { get; private set; }

? }

那么蝌焚,在這里基本上我們需要設(shè)置兩個不同的方法。一個是從數(shù)據(jù)庫拉數(shù)據(jù)誓斥,另一個是從已獲取到的數(shù)據(jù)中進行過濾只洒。當然你可能會想我們也可以在類中創(chuàng)建另一個方法來設(shè)置這些行為或者使用其他方式可能更有效。

屬性多態(tài)模式 (Lambda Property Polymorphism Pattern)

Lambda表達式可以被用于實現(xiàn)多態(tài)(override)劳坑,而不需要使用 abstract 和 virtual 等關(guān)鍵字毕谴。

class MyBaseClass

? {

? ? public Action SomeAction { get; protected set; }

? ? public MyBaseClass()

? ? {

? ? ? SomeAction = () =>

? ? ? {

? ? ? ? //Do something!

? ? ? };

? ? }

? }

這里沒什么特別的。我們創(chuàng)建了一個類距芬,通過一個屬性來暴露一個函數(shù)涝开。這有點像 JavaScript 風格。有趣的地方不僅在于可以在這個類中控制和更改這個函數(shù)屬性框仔,而且可以在它的衍生類中更改舀武。

class MyInheritedClass : MyBaseClass

? {

? ? ? public MyInheritedClass

? ? ? {

? ? ? ? ? SomeAction = () => {

? ? ? ? ? ? ? //Do something different!

? ? ? ? ? };

? ? ? }

? }

看!實際上這里我們能夠更改這個屬性完全是依賴于 protected 的應(yīng)用离斩。這種方式的缺點是我們無法直接訪問父類的實現(xiàn)银舱。這里我們丟失了 base 的強大能力衷旅,因為 base 中的屬性具有相同的值。如果你確實還需要這樣的功能纵朋,我建議使用下面這種“模式”:

class MyBaseClass {?

?public Action SomeAction { get; private set; }?

?StackpreviousActions;?

?protected void AddSomeAction(Action newMethod)?

?{?

?previousActions.Push(SomeAction);?

?SomeAction = newMethod;?

?}?

?protected void RemoveSomeAction() {?

?if (previousActions.Count == 0)?

?return;?

?SomeAction = previousActions.Pop();

?}?

?public MyBaseClass() {

?previousActions = new Stack();

? ? ? SomeAction = () =>

? ? ? {

? ? ? ? //Do something!

? ? ? };

? ? }

? }

這樣柿顶,在子類中只能調(diào)用 AddSomeAction() 來覆寫當前已設(shè)置的方法。這個方法將被直接放入一個棧內(nèi)操软,這使我們能夠記錄之前的狀態(tài)嘁锯。

我給這個模式起的名字是 Lambda屬性多態(tài)模式(Lambda Property Polymorphism Pattern)。它主要描述將函數(shù)封裝為屬性的可能性聂薪,然后能夠在衍生類中覆寫父類的屬性家乘。上面代碼中的棧只是一個額外的功能,并不會改變這個模式的目標藏澳。

為什么需要這個模式仁锯?坦白的說,有多種原因翔悠。首先就是因為我們能這么做业崖。但要注意,實際上如果我們要使用多個不同的屬性時蓄愁,這個模式會變得更靈活双炕。“多態(tài)”這個詞也就有了全新的含義撮抓,但那就是另一個模式了妇斤。所以這里我主要是想強調(diào)這個模式可以實現(xiàn)一些以前曾認為不可能的功能。

例如:你想覆寫一個靜態(tài)方法(不推薦這么做丹拯,但或許這么做是能解決你的問題的最優(yōu)雅的方法)站超。那么,繼承是不可能改變靜態(tài)方法的乖酬。原因很簡單:繼承僅應(yīng)用于類的實例死相,而靜態(tài)方法卻沒有被綁定到類的實例上。靜態(tài)方法對所有的類的實例都是相同的剑刑。這里也蘊含著一個警告媳纬,下面的這個模式可能不沒有達到你想要的結(jié)果,所以一定要明確你為什么要這么用施掏。

void Main() {?

?var mother = HotDaughter.Activator().Message;

//mother = "I am the mother"?

?var create = new HotDaughter();?

?var daughter = HotDaughter.Activator().Message;?

?//daughter = "I am the daughter"?

?}

?class CoolMother {?

?public static Func Activator { get; protected set; }

? ? ? //We are only doing this to avoid NULL references!

? ? ? static CoolMother()

? ? ? {

? ? ? ? ? Activator = () => new CoolMother();

? ? ? }

? ? ? public CoolMother()

? ? ? {

? ? ? ? ? //Message of every mother

? ? ? ? ? Message = "I am the mother";

? ? ? }


? ? ? public string Message { get; protected set; }

? }

? class HotDaughter : CoolMother

? {

? ? ? public HotDaughter()

? ? ? {

? ? ? ? ? //Once this constructor has been "touched" we set the Activator ...

? ? ? ? ? Activator = () => new HotDaughter();

? ? ? ? ? //Message of every daughter

? ? ? ? ? Message = "I am the daughter";

? ? ? }

? }

這是一個極其簡單的示例,并且希望不要引起誤導(dǎo)茅糜。如果這么用可能會導(dǎo)致事情變的更復(fù)雜七芭,所以我一直說為什么我們需要避免這么用,只是描述了其可行性蔑赘。關(guān)于靜態(tài)多態(tài)的較好的方案總不是易于實現(xiàn)的狸驳,并且需要很多的代碼预明,所以除非它真能幫你解決實際的問題,而不是讓你更頭痛耙箍。

函數(shù)字典模式 (Function Dictionary Pattern)

之前我已經(jīng)介紹了這個模式撰糠,只是還沒有指定名字,它就是函數(shù)字典模式(Function Dictionary Pattern)辩昆。這個模式的基本成分包括:一個哈希表或字典用于包含一些鍵值對阅酪,鍵可能是任意類型,值是某些類型的函數(shù)汁针。這個模式也指定了一個特殊的字典構(gòu)造方式术辐。這在這個模式中是必須的,否則只能使用 switch-case 來達到相同的目的了施无。

public Action GetFinalizer(string input)

? {

? ? ? switch

? ? ? {

? ? ? ? ? case "random":

? ? ? ? ? ? ? return () => { /* ... */ };

? ? ? ? ? case "dynamic":

? ? ? ? ? ? ? return () => { /* ... */ };

? ? ? ? ? default:

? ? ? ? ? ? ? return () => { /* ... */ };

? ? ? }

? }

上面代碼中我們需要一個字典類型嗎辉词?當然。我們可以這么做:

Dictionaryfinalizers; public void BuildFinalizers() { finalizers = new Dictionary();

? ? ? finalizers.Add("random", () => { /* ... */ });

? ? ? finalizers.Add("dynamic", () => { /* ... */ });

? }

? public Action GetFinalizer(string input)

? {

? ? ? if(finalizers.ContainsKey(input))

? ? ? ? ? return finalizers[input];

? ? ? return () => { /* ... */ };

? }

但要注意猾骡,在這里使用這個模式并沒有帶來任何好處瑞躺。實際上,這個模式的效率更低兴想,并且需要更多格外的代碼隘蝎。但是我們能做的事情是,通過反射來是函數(shù)字典的構(gòu)造過程自動化襟企。同樣還是沒有使用 switch-case 語句的效率高嘱么,但代碼更健壯,可維護性更高顽悼。實際上這個操作也很方便曼振,比如我們有大量的代碼,我們甚至不知道在哪個方法內(nèi)加入 switch-case 代碼塊蔚龙。

我們來看一個可能的實現(xiàn)冰评。通常我會建議在代碼中增加一些約定,以便能夠得到字典的鍵木羹。當然甲雅,我們也可以通過選擇類中某個屬性的名稱,或者直接使用方法的名稱來滿足需求坑填。在下面的示例中抛人,我們僅選擇一種約定:

static Dictionaryfinalizers; //The method should be called by a static constructor or something similar //The only requirement is that we built public static void BuildFinalizers() { finalizers = new Dictionary();

? ? ? //Get all types of the current (= where the code is contained) assembly

? ? ? var types = Assembly.GetExecutingAssembly().GetTypes();

? ? ? foreach (var type in types)

? ? ? {

? ? ? ? //We check if the class is of a certain type

? ? ? ? if (type.IsSubclassOf(typeof(MyMotherClass)))

? ? ? ? {

? ? ? ? ? //Get the constructor

? ? ? ? ? var m = type.GetConstructor(Type.EmptyTypes);

? ? ? ? ? //If there is an empty constructor invoke it

? ? ? ? ? if (m != null)

? ? ? ? ? {

? ? ? ? ? ? var instance = m.Invoke(null) as MyMotherClass;

? ? ? ? ? ? //Apply the convention to get the name - in this case just we pretend it is as simple as

? ? ? ? ? ? var name = type.Name.Remove("Mother");

? ? ? ? ? ? //Name could be different, but let's just pretend the method is named MyMethod

? ? ? ? ? ? var method = instance.MyMethod;

? ? ? ? ? ? finalizers.Add(name, method);

? ? ? ? ? }

? ? ? ? }

? ? ? }

? ? }

? ? public Action GetFinalizer(string input)

? ? {

? ? ? if (finalizers.ContainsKey(input))

? ? ? ? return finalizers[input];

? ? ? return () => { /* ... */ };

? ? }

現(xiàn)在這段代碼是不是更好些呢。事實上脐瑰,這個模式可以節(jié)省很多工作妖枚。而其中最好的就是:它允許你實現(xiàn)類似插件的模式,并且使此功能跨程序集應(yīng)用苍在。為什么這么說呢绝页?比如我們可以掃描指定模式的類庫荠商,并將其加入到代碼中。通過這種方式也可以將其他類庫中的功能添加到當前代碼中续誉。

//The start is the same internal static void BuildInitialFinalizers() { finalizers = new Dictionary();

? ? ? LoadPlugin(Assembly.GetExecutingAssembly());

? ? }

? ? public static void LoadPlugin(Assembly assembly)

? ? {

? ? ? //This line has changed

? ? ? var types = assembly.GetTypes();

? ? ? //The rest is identical! Perfectly refactored and obtained a new useful method

? ? ? foreach (var type in types)

? ? ? {

? ? ? ? if (type.IsSubclassOf(typeof(MyMotherClass)))

? ? ? ? {

? ? ? ? ? var m = type.GetConstructor(Type.EmptyTypes);

? ? ? ? ? if (m != null)

? ? ? ? ? {

? ? ? ? ? ? var instance = m.Invoke(null) as MyMotherClass;

? ? ? ? ? ? var name = type.Name.Remove("Mother");

? ? ? ? ? ? var method = instance.MyMethod;

? ? ? ? ? ? finalizers.Add(name, method);

? ? ? ? ? }

? ? ? ? }

? ? ? }

? ? }

? ? //The call is the same

現(xiàn)在我們僅需要通過一個點來指定插件莱没。最后將會從某路徑中讀取類庫,嘗試創(chuàng)建程序集對象酷鸦,然后調(diào)用 LoadPlugin() 來加載程序集饰躲。

函數(shù)式特性 (Functional Attribute Pattern)

Attribute 是 C# 語言中最棒的功能之一。借助 Attribute井佑,曾在 C/C++ 中不太容易實現(xiàn)的功能属铁,在C#中僅需少量的代碼即可實現(xiàn)。 這個模式將 Attribute 與 Lambda 表達式結(jié)合到一起躬翁。在最后焦蘑,函數(shù)式特性模式(Functional Attribute Pattern)將會提高 Attribute 應(yīng)用的可能性和生產(chǎn)力。

可以說盒发,將 Lambda 表達式和 Attribute 結(jié)合到也一起相當?shù)挠袔椭觯驗槲覀儾辉傩枰帉懱囟ǖ念悺W屛覀儊砜磦€例子來具體解釋是什么意思宁舰。

class MyClass

? {

? ? public bool MyProperty

? ? {

? ? ? get;

? ? ? set;

? ? }

? }

現(xiàn)在針對這個類的實例拼卵,我們想要能夠根據(jù)一些領(lǐng)域特性語言或腳本語言來改變這個屬性。然后我們還想能夠在不寫任何額外代碼的條件下來改變屬性的值蛮艰。當然腋腮,我們還是需要一些反射機制。同時也需要一些 attribute 來指定是否這個屬性值能夠被用戶更改壤蚜。

class MyClass

? {

? ? [NumberToBooleanConverter]

? ? [StringToBooleanConverter]

? ? public bool MyProperty

? ? {

? ? ? get;

? ? ? set;

? ? }

? }

我們定義兩種轉(zhuǎn)換器即寡。雖然使用一個即可標示這個屬性可以被任何用于更改。我們使用兩個來為使用者提供更多的可能性袜刷。在這個場景下聪富,一個使用者可能實際上使用一個字符串來設(shè)置這個值(將字符串轉(zhuǎn)換成布爾值)或者用一個數(shù)字(比如0或1)。

那么這些轉(zhuǎn)換器如何實現(xiàn)呢著蟹?我們來看下 StringToBooleanConverterAttribute 的實現(xiàn)墩蔓。

public class StringToBooleanConverterAttribute : ValueConverterAttribute { public StringToBooleanConverterAttribute() : base(typeof(string), v => { var str = (v as string ?? string.Empty).ToLower(); if (str == "on") return true; else if (str == "off") return false; throw new Exception("The only valid input arguments are [ on, off ]. You entered " + str + "."); }) { /* Nothing here on purpose */ } } public abstract class ValueConverterAttribute : Attribute { public ValueConverterAttribute(Type expected, Funcconverter) { Converter = converter; Expected = expected; } public ValueConverterAttribute(Type expected) { Expected = expected; } public Func Converter { get; set; }

? ? ? public object Convert(object argument)

? ? ? {

? ? ? ? ? return Converter.Invoke(argument);

? ? ? }

? ? ? public bool CanConvertFrom(object argument)

? ? ? {

? ? ? ? ? return Expected.IsInstanceOfType(argument);

? ? ? }

? ? ? public Type Expected

? ? ? {

? ? ? ? ? get;

? ? ? ? ? set;

? ? ? }

? ? ? public string Type

? ? ? {

? ? ? ? ? get { return Expected.Name; }

? ? ? }

? }

使用這個模式我們得到了什么好處呢?如果 Attribute 能夠接受非常量表達式作為參數(shù)(比如委托萧豆、Lambda 表達式等都有可能)奸披,則我們得到的好處會更多。通過這種方式炕横,我們僅需使用 Lambda 表達式來替換抽象方法源内,然后將其傳遞給父類的構(gòu)造函數(shù)。

你可能有些意見份殿,這和 abstract 函數(shù)比并沒什么新鮮的膜钓,但有趣的地方在于不能像使用函數(shù)一樣來用,而是作為一個屬性能夠被外部進行設(shè)置卿嘲。這可以被用于一些動態(tài)代碼中颂斜,來重寫一些轉(zhuǎn)換器,盡管其已經(jīng)被實例化了拾枣。

避免循環(huán)引用 (Avoiding cyclic references)

在 C#中沃疮,循環(huán)引用并不是一個大問題。實際上僅在一種方式下會使循環(huán)引用帶來問題梅肤,那就是在 struct 結(jié)構(gòu)體中司蔬。因為類是引用類型,循環(huán)引用并沒有什么壞處姨蝴。在源對象上持有目標對象的一個引用指針俊啼,而在目標對象上持有一個源對象的引用指針,這不會有任何問題左医。

但是如果是結(jié)構(gòu)體授帕,我們沒法使用指針,其在棧上創(chuàng)建對象浮梢。因為在這種情況下跛十,若源對象包含一個目標對象,實際上是包含了一個目標對象的拷貝秕硝,而不是真正的目標對象芥映,而反過來也一樣。

大部分情況下远豺,編譯器會檢測到這種循環(huán)引用奈偏,然后拋出一個編譯錯誤,這個功能其實很棒憋飞。我們來看個能引起錯誤的例子:

struct FirstStruct

? {

? ? public SecondStruct Target;

? }

? struct SecondStruct

? {

? ? public FirstStruct Source;

? }

這上面的代碼中霎苗,使用結(jié)構(gòu)體變量。這與類有巨大的不同:盡管我們沒初始化變量榛做,但變量其實已經(jīng)被初始化為默認值唁盏。

所以說,編程是件復(fù)雜的事检眯,編譯器也不是萬能的神厘擂。通過一些方式可以騙過編譯器。如果我們欺騙了編譯器锰瘸,編譯器就會告訴我們一個運行時錯誤刽严,無法創(chuàng)建這個對象。一種欺騙方式是使用自動屬性:

struct FirstStruct

? {

? ? public SecondStruct Target { get; set; }

? }

? struct SecondStruct

? {

? ? public FirstStruct Source { get; set; }

? }

這不會阻止問題的發(fā)生避凝,其只是將問題從編譯時錯誤延遲到了運行時錯誤舞萄。我們腦中立即會產(chǎn)生一個方案眨补,就是使用可空結(jié)構(gòu)(nullable struct)。

struct FirstStruct

? {

? ? public SecondStruct? Target { get; set; }

? }

? struct SecondStruct

? {

? ? public FirstStruct Source { get; set; }

? }

這里的問題是倒脓,那些可空結(jié)構(gòu)也同樣是結(jié)構(gòu)體撑螺,他們繼承自 System.Nullable ,實際上也是一個結(jié)構(gòu)體類型崎弃。

終于甘晤,Lambda表達式來拯救我們了。

struct FirstStruct { readonly Func f;

? ? public FirstStruct(SecondStruct target)

? ? {

? ? ? f = () => target;

? ? }

? ? public SecondStruct Target

? ? {

? ? ? get

? ? ? {

? ? ? ? return f();

? ? ? }

? ? }

? }

? struct SecondStruct

? {

? ? public FirstStruct Source { get; set; }

? }

這里我們做了什么呢饲做?我們使用了一個對函數(shù)的引用线婚,而該函數(shù)會返回給我們結(jié)構(gòu)體。編譯器會生成一個類來包含這個結(jié)構(gòu)體盆均,這樣這個結(jié)構(gòu)體就作為一個全局變量存在了塞弊。因為結(jié)構(gòu)體總是會包含一個默認的構(gòu)造函數(shù),會保持 f 的未引用狀態(tài)缀踪,我們加了另一個構(gòu)造函數(shù)居砖,并且將目標結(jié)構(gòu)體作為參數(shù)傳入。

最后驴娃,我們創(chuàng)建了一個閉包奏候,在其中返回被捕獲的結(jié)構(gòu)體實例。重點的強調(diào)下唇敞,可能會有其他可能性蔗草。如果使用一個引用類型作為值類型的容器,可能循環(huán)引用的情況更糟疆柔。Lambda 表達式只是能完成這個功能的一種方式咒精,但在某些條件下,其是能處理這種場景的最具表達性和最直接的方式旷档。

完整代碼

class TestPatterns { public static void SelfDefining() { Console.WriteLine(":: Pattern: Self-definining function"); Action foo = () => { Console.WriteLine("Hi there!"); foo = () => { Console.WriteLine("Hi again!"); }; }; Console.WriteLine("First call (initilization)."); foo(); Console.WriteLine("Second call - use different one now!"); foo(); Console.WriteLine("Third call - still the same."); foo(); } public static void Callback() { Console.WriteLine(":: Pattern: Callback pattern"); Console.WriteLine("Calling the function with lambda expression."); CallMe(() => "The boss."); Console.WriteLine("Back at the starting point."); } static void CallMe(Funccaller) { Console.WriteLine("Received function as parameter - Who called?!"); Console.WriteLine(caller()); } public static void Returning() { Console.WriteLine(":: Pattern: Returning function"); Console.WriteLine("Calling to obtain the method ..."); Funcmethod = GetProperMethod("sin"); Console.WriteLine("Doing something with the method ..."); Console.WriteLine("f(pi / 4) = {0}", method(Math.PI / 4)); } static FuncGetProperMethod(string what) { switch (what) { case "sin": return Math.Sin; case "cos": return Math.Cos; case "exp": return Math.Exp; default: return x => x; } } public static void IIFE() { Console.WriteLine(":: Pattern: IIFE"); ((Action)((x) => { Console.WriteLine(2.0 * x * x - 0.5 * x); }))(1.0); ((Action)((x, y) => { Console.WriteLine(2.0 * x * y - 1.5 * x); }))(2.0, 3.0); } public static void ImmediateObject() { Console.WriteLine(":: Pattern: Immediate object initialization"); var terminator = new { Typ = "T1000", Health = 100, Hit = (Func)((x) => { return 100.0 * Math.Exp(-x); }) }; Console.WriteLine("Terminator with type {0} has been created.", terminator.Typ); Console.WriteLine("Let's hit the terminator with 0.5. Rest health would be {0}!", terminator.Hit(0.5)); } public static void InitTimeBranching() { Console.WriteLine(":: Pattern: Init-time branching"); Action loopBody = null;

? ? ? Console.WriteLine("Select a proper loop body method ...");

? ? ? Random r = new Random();

? ? ? int sum = 0;

? ? ? if (r.NextDouble() < 0.5)

? ? ? {

? ? ? ? Console.WriteLine("Selected random choice ...");

? ? ? ? loopBody = index =>

? ? ? ? {

? ? ? ? ? sum += r.Next(0, 10000);

? ? ? ? };

? ? ? }

? ? ? else

? ? ? {

? ? ? ? Console.WriteLine("Selected little gauss ...");

? ? ? ? loopBody = index =>

? ? ? ? {

? ? ? ? ? sum += index;

? ? ? ? };

? ? ? }

? ? ? Console.WriteLine("Execute the loop ...");

? ? ? for (var i = 0; i < 10000; i++)

? ? ? ? loopBody(i);

? ? ? Console.WriteLine("Loop has finished with result sum = {0}.", sum);

? ? }

? }

文章內(nèi)容翻譯并改編自Way to Lambda模叙,章節(jié)和代碼有很大的改動,未包含全部內(nèi)容鞋屈。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末范咨,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子厂庇,更是在濱河造成了極大的恐慌渠啊,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,542評論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件权旷,死亡現(xiàn)場離奇詭異替蛉,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,822評論 3 394
  • 文/潘曉璐 我一進店門躲查,熙熙樓的掌柜王于貴愁眉苦臉地迎上來热某,“玉大人铅辞,你說我怎么就攤上這事挪捕「盍福” “怎么了艇纺?”我有些...
    開封第一講書人閱讀 163,912評論 0 354
  • 文/不壞的土叔 我叫張陵怎静,是天一觀的道長。 經(jīng)常有香客問我黔衡,道長蚓聘,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,449評論 1 293
  • 正文 為了忘掉前任盟劫,我火速辦了婚禮夜牡,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘侣签。我一直安慰自己塘装,他們只是感情好,可當我...
    茶點故事閱讀 67,500評論 6 392
  • 文/花漫 我一把揭開白布影所。 她就那樣靜靜地躺著蹦肴,像睡著了一般。 火紅的嫁衣襯著肌膚如雪猴娩。 梳的紋絲不亂的頭發(fā)上阴幌,一...
    開封第一講書人閱讀 51,370評論 1 302
  • 那天,我揣著相機與錄音卷中,去河邊找鬼矛双。 笑死,一個胖子當著我的面吹牛蟆豫,可吹牛的內(nèi)容都是我干的议忽。 我是一名探鬼主播,決...
    沈念sama閱讀 40,193評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼十减,長吁一口氣:“原來是場噩夢啊……” “哼栈幸!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起嫉称,我...
    開封第一講書人閱讀 39,074評論 0 276
  • 序言:老撾萬榮一對情侶失蹤侦镇,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后织阅,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體壳繁,經(jīng)...
    沈念sama閱讀 45,505評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,722評論 3 335
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了闹炉。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蒿赢。...
    茶點故事閱讀 39,841評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖渣触,靈堂內(nèi)的尸體忽然破棺而出羡棵,到底是詐尸還是另有隱情,我是刑警寧澤嗅钻,帶...
    沈念sama閱讀 35,569評論 5 345
  • 正文 年R本政府宣布皂冰,位于F島的核電站,受9級特大地震影響养篓,放射性物質(zhì)發(fā)生泄漏秃流。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,168評論 3 328
  • 文/蒙蒙 一柳弄、第九天 我趴在偏房一處隱蔽的房頂上張望舶胀。 院中可真熱鬧,春花似錦碧注、人聲如沸嚣伐。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,783評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽轩端。三九已至,卻和暖如春碉纺,著一層夾襖步出監(jiān)牢的瞬間船万,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,918評論 1 269
  • 我被黑心中介騙來泰國打工骨田, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留耿导,地道東北人。 一個月前我還...
    沈念sama閱讀 47,962評論 2 370
  • 正文 我出身青樓态贤,卻偏偏與公主長得像舱呻,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子悠汽,可洞房花燭夜當晚...
    茶點故事閱讀 44,781評論 2 354

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理箱吕,服務(wù)發(fā)現(xiàn),斷路器柿冲,智...
    卡卡羅2017閱讀 134,656評論 18 139
  • 工廠模式類似于現(xiàn)實生活中的工廠可以產(chǎn)生大量相似的商品茬高,去做同樣的事情,實現(xiàn)同樣的效果;這時候需要使用工廠模式假抄。簡單...
    舟漁行舟閱讀 7,758評論 2 17
  • 單例模式 適用場景:可能會在場景中使用到對象怎栽,但只有一個實例丽猬,加載時并不主動創(chuàng)建,需要時才創(chuàng)建 最常見的單例模式熏瞄,...
    Obeing閱讀 2,067評論 1 10
  • 第2章 基本語法 2.1 概述 基本句法和變量 語句 JavaScript程序的執(zhí)行單位為行(line)脚祟,也就是一...
    悟名先生閱讀 4,149評論 0 13
  • 喝酒打架的故事永遠缺不了你們 青春的色彩如你們的笑臉 如果你們不在 我不知道什么叫青春 總是很喜歡吹牛 漂亮的女孩...
    尋風追風閱讀 163評論 0 1