欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

C#?委托與?Lambda?表達(dá)式轉(zhuǎn)換機(jī)制及弱事件模式下的生命周期詳解

 更新時(shí)間:2025年02月27日 08:48:53   作者:陳百川  
本文介紹了C#委托和Lambda表達(dá)式的工作原理,包括委托的內(nèi)部結(jié)構(gòu)、Lambda表達(dá)式的轉(zhuǎn)換機(jī)制以及弱事件模式下的生命周期管理,感興趣的朋友一起看看吧

1. 委托內(nèi)部結(jié)構(gòu)

委托類型包含三個(gè)重要的非公共字段:

  • _target 字段

    • 靜態(tài)方法包裝:當(dāng)委托包裝一個(gè)靜態(tài)方法時(shí),該字段為 null。
    • 實(shí)例方法包裝:當(dāng)委托包裝實(shí)例方法時(shí),該字段引用回調(diào)方法所操作的對(duì)象。
  • _methodPtr 字段

    • 標(biāo)識(shí)委托要調(diào)用的方法。
  • _invocationList 字段

    • 存儲(chǔ)委托鏈(即內(nèi)部委托數(shù)組),用于實(shí)現(xiàn)多播委托。

2. Lambda 表達(dá)式轉(zhuǎn)換為委托實(shí)例

C# 編譯器會(huì)將 lambda 表達(dá)式轉(zhuǎn)換成相應(yīng)的委托實(shí)例,具體轉(zhuǎn)換方式依賴于 lambda 是否捕獲外部數(shù)據(jù)。

2.1 不捕獲任何外部數(shù)據(jù)

  • 轉(zhuǎn)換方式

    • 將 lambda 表達(dá)式生成為私有的靜態(tài)函數(shù)(編譯器自動(dòng)生成方法名)。
    • 同時(shí)生成一個(gè)委托類型的靜態(tài)字段用于緩存委托實(shí)例。
  • 委托實(shí)例創(chuàng)建與緩存

    • 當(dāng)調(diào)用包含 lambda 的方法時(shí),先檢查靜態(tài)字段是否為 null。
    • 若不為 null,則直接返回緩存的委托實(shí)例;若為 null,則創(chuàng)建新的委托實(shí)例,并賦值給靜態(tài)字段。
    • 這種方式確保委托實(shí)例只創(chuàng)建一次,被靜態(tài)字段引用后不會(huì)被回收。

2.2 捕獲實(shí)例成員(通過 this 訪問)

  • 轉(zhuǎn)換方式

    • 將 lambda 表達(dá)式生成為私有的實(shí)例函數(shù)(編譯器自動(dòng)生成方法名)。
  • 委托實(shí)例創(chuàng)建

    • 每次調(diào)用包含 lambda 的方法時(shí),都會(huì)實(shí)時(shí)創(chuàng)建一個(gè)新的委托實(shí)例,包裝該實(shí)例函數(shù)。

2.3 捕獲非實(shí)例成員(例如局部變量)

  • 轉(zhuǎn)換方式

    • 編譯器生成一個(gè)私有的輔助閉包類(通常命名為 “<>c__DisplayClassXXX”)。
    • 輔助類中包含公開字段,用于保存捕獲的局部變量(或其他非實(shí)例數(shù)據(jù))。
    • 在該輔助類中,將 lambda 表達(dá)式轉(zhuǎn)換為公開的實(shí)例函數(shù),該方法通過訪問輔助類字段來使用捕獲的數(shù)據(jù)。
  • 委托與閉包實(shí)例的創(chuàng)建

    • 每次調(diào)用包含 lambda 的方法時(shí),都會(huì)生成一個(gè)輔助類實(shí)例。
    • 然后創(chuàng)建一個(gè)委托實(shí)例,其 _target 字段指向該輔助類實(shí)例。
    • 注意:在循環(huán)中容易產(chǎn)生閉包陷阱——盡管每次迭代可能創(chuàng)建多個(gè)輔助類實(shí)例與委托實(shí)例,但這些輔助類實(shí)例中的捕獲字段指向同一塊內(nèi)存(即共享同一循環(huán)變量)。由于 lambda 表達(dá)式通常在循環(huán)結(jié)束后執(zhí)行,所有回調(diào)看到的循環(huán)變量值往往都是最后一次迭代的狀態(tài)。
    • 另外,不同版本的 C# 對(duì)于循環(huán)中輔助類實(shí)例的創(chuàng)建可能存在差異,有的版本可能只在進(jìn)入方法時(shí)創(chuàng)建一次,而有的版本則每次迭代都創(chuàng)建新的實(shí)例。至于委托實(shí)例,我猜測(cè)每次迭代都會(huì)創(chuàng)建一個(gè)新的委托實(shí)例(否則作為字典鍵時(shí)可能會(huì)出現(xiàn)重復(fù)的問題),但《CLR Via C# 第四版》中示例代碼(17.7.3節(jié),中文版365頁(yè))顯示委托實(shí)例只創(chuàng)建了一次,這里感覺有點(diǎn)問題,有興趣的朋友可以分析一下。

3. 委托實(shí)例的訂閱與生命周期

3.1 常規(guī)委托/事件訂閱

  • 當(dāng)委托實(shí)例訂閱到常規(guī)委托或事件時(shí),事件源對(duì)委托實(shí)例持有強(qiáng)引用,從而延長(zhǎng)委托實(shí)例的生命周期(直至取消訂閱或事件源回收)。

3.2 弱事件訂閱

  • 弱事件模式特點(diǎn)

    • 委托實(shí)例的生命周期至少大于其 _target 引用的對(duì)象的生命周期。
  • 實(shí)現(xiàn)機(jī)制

    • 利用 ConditionalWeakTable<TKey, TValue> 進(jìn)行關(guān)聯(lián):
      • 將 _target 引用的對(duì)象作為 key。
      • 將委托實(shí)例作為 value。
    • ConditionalWeakTable 對(duì) key 使用弱引用,但對(duì) value 使用強(qiáng)引用,保證只要 key 存在,對(duì)應(yīng)的 value 就不會(huì)被回收。
  • 訂閱流程

    • 當(dāng)委托實(shí)例通過 WeakEventManager<TEventSource, TEventArgs> 訂閱弱事件時(shí),內(nèi)部會(huì)通過 Delegate.Target 獲取 _target 引用的對(duì)象,并將該對(duì)象與委托實(shí)例關(guān)聯(lián)到 ConditionalWeakTable 中,從而確保委托實(shí)例的生命周期至少與 _target 對(duì)象一致。

上面用工具重新排版了下,下面是我編輯的原文:

委托類型包含三個(gè)重要的非公共字段:_target字段,當(dāng)委托實(shí)例包裝一個(gè)靜態(tài)方法時(shí),該字段為空;包裝實(shí)例方法時(shí),這個(gè)字段引用回調(diào)方法要操作的對(duì)象。_methodPtr字段標(biāo)識(shí)要回調(diào)的方法。_invocationList字段引用委托數(shù)組。

C#編譯器將lambda方法替換為對(duì)應(yīng)的委托實(shí)例。

當(dāng)lambda不獲取任何外部數(shù)據(jù)時(shí),調(diào)用只創(chuàng)建一次委托實(shí)例并緩存:C#編譯器將lambda表達(dá)式生成為私有的靜態(tài)函數(shù)(編譯器自動(dòng)取名的方法),并生成一個(gè)委托類型的靜態(tài)字段。當(dāng)調(diào)用使用lambda的方法時(shí),先判斷自動(dòng)生成的靜態(tài)字段是否為空,不為空則直接返回靜態(tài)字段引用的委托實(shí)例,為空則先創(chuàng)建一個(gè)包裝靜態(tài)函數(shù)的委托實(shí)例賦值給靜態(tài)委托字段。(這導(dǎo)致被靜態(tài)字段引用的委托實(shí)例不會(huì)被釋放,但委托實(shí)例只會(huì)被創(chuàng)建一次)。

當(dāng)lambda獲取實(shí)例成員時(shí)(通過this指針訪問),每次調(diào)用都創(chuàng)建新的委托實(shí)例:C#編譯器將lambda表達(dá)式生成為私有的實(shí)例函數(shù)(編譯器自動(dòng)取名的方法)。每次調(diào)用使用lambda的方法時(shí)都實(shí)時(shí)創(chuàng)建一個(gè)委托實(shí)例包裝該自動(dòng)生成的實(shí)例函數(shù)。

當(dāng)lambda獲取非實(shí)例成員時(shí)(不通過當(dāng)前實(shí)例的this指針訪問,比如局部變量),C#編譯器創(chuàng)建一個(gè)私有的輔助類,輔助類擁有對(duì)應(yīng)的公開字段引用非實(shí)例成員,在輔助類中將將lambda表達(dá)式生成為公開的實(shí)例函數(shù)。每次調(diào)用使用lambda的方法時(shí)都生成輔助類實(shí)例,引用相同的非實(shí)例成員,然后創(chuàng)建委托實(shí)例傳入輔助類實(shí)例。(循環(huán)中的閉包陷阱就在于循環(huán)中雖然創(chuàng)建了多個(gè)輔助類實(shí)例與委托實(shí)例,但不同輔助類實(shí)例引用的非實(shí)例成員是同一塊內(nèi)存。lambda 表達(dá)式是在循環(huán)中創(chuàng)建,但其執(zhí)行往往是在循環(huán)結(jié)束后才發(fā)生,所以所有回調(diào)看到的循環(huán)變量都是最終狀態(tài)。并且不同版本C#實(shí)現(xiàn)在循環(huán)中可能并沒有創(chuàng)建循環(huán)次數(shù)的輔助類實(shí)例,而是在進(jìn)入方法時(shí)只創(chuàng)建一次。我猜測(cè)創(chuàng)建了循環(huán)次數(shù)的委托實(shí)例,不然作為字典的鍵時(shí)就應(yīng)該出錯(cuò)了。但CLR Via C#第四版給的示例代碼中委托實(shí)例只創(chuàng)建了一次,這可能有點(diǎn)問題,有興趣的朋友可以分析一下。

lambda被轉(zhuǎn)換為委托實(shí)例后,當(dāng)將該委托實(shí)例訂閱到常規(guī)委托、事件時(shí),事件源對(duì)委托實(shí)例進(jìn)行強(qiáng)引用。

當(dāng)將該委托實(shí)例訂閱到弱事件時(shí),存在有意思的現(xiàn)象:委托實(shí)例的生命周期最起碼大于_target引用的對(duì)象的生命周期。這是通過ConditionalWeakTable<TKey, TValue>實(shí)現(xiàn)的,通過將_target引用的對(duì)象設(shè)置為key、將委托實(shí)例設(shè)置為value。該類負(fù)責(zé)數(shù)據(jù)間的關(guān)聯(lián),它對(duì)key是弱引用,但保證只要key在內(nèi)存中,value就一定在內(nèi)存中。

委托實(shí)例通過WeakEventManager<TEventSource, TEventArgs>訂閱弱事件時(shí),WeakEventManager<TEventSource, TEventArgs>內(nèi)部會(huì)通過Delegate.Target拿到委托實(shí)例中_target引用的對(duì)象,作為ConditionalWeakTable的key,委托實(shí)例作為ConditionalWeakTable的value進(jìn)行關(guān)聯(lián)。這樣就保證了弱事件模式下委托實(shí)例的生命周期至少大于_target引用的對(duì)象的生命周期。

public void AddHandler(Delegate handler)
{
    Invariant.Assert(_users == 0, "Cannot modify a ListenerList that is in use");
    object obj = handler.Target;
    if (obj == null)
    {
        obj = StaticSource;
    }
    _list.Add(new Listener(obj, handler));
    AddHandlerToCWT(obj, handler);
}
private void AddHandlerToCWT(object target, Delegate handler)
{
    if (!_cwt.TryGetValue(target, out var value))
    {
        _cwt.Add(target, handler);
        return;
    }
    List<Delegate> list = value as List<Delegate>;
    if (list == null)
    {
        Delegate item = value as Delegate;
        list = new List<Delegate>();
        list.Add(item);
        _cwt.Remove(target);
        _cwt.Add(target, list);
    }
    list.Add(handler);
}

到此這篇關(guān)于C# 委托與 Lambda 表達(dá)式轉(zhuǎn)換機(jī)制及弱事件模式下的生命周期分析的文章就介紹到這了,更多相關(guān)C# 委托與 Lambda 表達(dá)式內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • C# StreamReader類實(shí)現(xiàn)讀取文件的方法

    C# StreamReader類實(shí)現(xiàn)讀取文件的方法

    這篇文章主要介紹了C# StreamReader類實(shí)現(xiàn)讀取文件的方法,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2021-01-01
  • C#中改變DataGridView控件邊框顏色的方法

    C#中改變DataGridView控件邊框顏色的方法

    這篇文章主要介紹了C#中改變DataGridView控件邊框顏色的方法,默認(rèn)的DataGridView邊框顏色很丑,本文用編程方法實(shí)現(xiàn)修改DataGridView邊框顏色,需要的朋友可以參考下
    2014-08-08
  • C#正則表達(dá)式Regex類的用法

    C#正則表達(dá)式Regex類的用法

    這篇文章介紹了C#正則表達(dá)式Regex類的用法,對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2021-12-12
  • C#泛型類創(chuàng)建與使用的方法

    C#泛型類創(chuàng)建與使用的方法

    這篇文章主要為大家詳細(xì)介紹了C#泛型類創(chuàng)建與使用的方法,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2017-07-07
  • C#線性漸變畫刷LinearGradientBrush用法實(shí)例

    C#線性漸變畫刷LinearGradientBrush用法實(shí)例

    這篇文章主要介紹了C#線性漸變畫刷LinearGradientBrush用法,實(shí)例分析了線性漸變畫刷LinearGradientBrush的相關(guān)使用技巧,需要的朋友可以參考下
    2015-06-06
  • Unity向量按照某一點(diǎn)進(jìn)行旋轉(zhuǎn)

    Unity向量按照某一點(diǎn)進(jìn)行旋轉(zhuǎn)

    這篇文章主要為大家詳細(xì)介紹了Unity向量按照某一點(diǎn)進(jìn)行旋轉(zhuǎn),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2020-01-01
  • 詳解C# ConcurrentBag的實(shí)現(xiàn)原理

    詳解C# ConcurrentBag的實(shí)現(xiàn)原理

    ConcurrentBag<T>實(shí)現(xiàn)了IProducerConsumerCollection<T>接口,該接口主要用于生產(chǎn)者消費(fèi)者模式下,可見該類基本就是為生產(chǎn)消費(fèi)者模式定制的。然后還實(shí)現(xiàn)了常規(guī)的IReadOnlyCollection<T>類,實(shí)現(xiàn)了該類就需要實(shí)現(xiàn)IEnumerable<T>、IEnumerable、 ICollection類
    2021-06-06
  • 使用淘寶ip地址庫(kù)查ip的示例

    使用淘寶ip地址庫(kù)查ip的示例

    這篇文章主要介紹了使用淘寶ip地址庫(kù)查ip的示例,需要的朋友可以參考下
    2014-03-03
  • Visual Studio 未能加載各種Package包的解決方案

    Visual Studio 未能加載各種Package包的解決方案

    打開Visual Studio 的時(shí)候,總提示未能加載相應(yīng)的Package包,有時(shí)候還無法打開項(xiàng)目,各種錯(cuò)誤提示,怎么解決呢?下面小編給大家?guī)砹薞isual Studio 未能加載各種Package包的解決方案,一起看看吧
    2016-10-10
  • C#使用NPOI讀取excel轉(zhuǎn)為DataSet

    C#使用NPOI讀取excel轉(zhuǎn)為DataSet

    這篇文章主要為大家詳細(xì)介紹了C#使用NPOI讀取excel轉(zhuǎn)為DataSet,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2022-02-02

最新評(píng)論