說說委托那些事兒

来源:http://www.cnblogs.com/eyu/archive/2016/08/12/all_those_delegate_things.html
-Advertisement-
Play Games

挖一挖委托那些事兒,匿名方法,委托的逆變與協變,委托與閉包,C#自執行函數 委托基礎 委托是個啥? 很多人第一反映可能是"函數指針",個人覺得"函數指針"是委托實例 委托的定義類似interface,是一種方法的"規範"或者說"模版",用來規範方法的"行為",以便將方法作為參數傳遞 public d ...


挖一挖委托那些事兒,匿名方法,委托的逆變與協變,委托與閉包,C#自執行函數

委托基礎

委托是個啥?

很多人第一反映可能是"函數指針",個人覺得"函數指針"是委托實例

委托的定義類似interface,是一種方法的"規範"或者說"模版",用來規範方法的"行為",以便將方法作為參數傳遞

public delegate void MyDelegate();

這樣便定義了一個無參無返回值的委托,要求此委托的實例必須是無參無返回值的方法

public class MyClass
{
  public static void MyMethod1() }
  public static void MyMethod2() } }
MyDelegate myDelegate new MyDelegate(MyClass.MyMethod1);//定義了委托實例,並添加了相應的操作方法 //MyDelegate myDelegate = MyClass.MyMethod;//<--簡寫就是這樣 myDelegate += MyClass.MyMethod2;//多播委托

上面的代碼展示了委托的基本用法,多播委托也可以用Delegate.Combin()方法來實現

多播委托可以美化成下麵的代碼

MyDelegate myDelegate null;
myDelegate += MyClass.MyMethod1;
myDelegate += MyClass.MyMethod2;

是不是漂亮多了!

在C#3以後常用委托都可以用Action跟Func來替代了(C#3還是2忘記了- -)

委托存在的意義:方法傳遞

真實案例:

在controller的自定義基類中有一個protected void CreateCookie(string name, string value) 方法

在獲取到微信openid後,進行一些資料庫處理,同時保存此openid的登錄信息到cookies


public static void SetOpenId(string openId, Action<string, string> setCookie)
WeixinTool.SetOpenId(openid, CreateCookie);

這樣便將CreateCookie傳遞給了SetOpenId方法

匿名方法

不需要定義方法名,直接書寫方法體賦值給委托

在lambda表達式出來後用的不多了, 實際上lambda表達式就是匿名方法

MyDelegate anonymous1 delegate() Console.WriteLine("this is a test 1"); };//匿名方法
MyDelegate anonymous2 () => Console.WriteLine("this is a test 2"); };//lambda表達式
 
anonymous1();
anonymous2();

上面的代碼編譯後使用IlSpy查看直接就是倆匿名委托

使用ildasm查看il也是一致的

說了委托,是不是該說事件了

大家應該都寫過winform啦,點擊按鈕觸發click事件,相關事件處理程式影響該事件

很同學都知道有事件,但並不能準確描述事件是什麼 (前文的多播委托的優化版是不是看著像事件)

public event MyDelegate ChangeSomething;

首先事件是"屬性",是類的一個"屬性",所以只能定義在一個類裡面(或者結構體裡面)

但是event關鍵字讓你不能直接對這個屬性賦值,所以只能用"+="或者"-="來操作這個"屬性"

事件存在的目的是為了實現"發佈/訂閱模式",也就是大家常說的pub/sub

為啥不能讓你直接給這個屬性賦值呢,因為"訂閱者"並不知道有多少人訂閱了這個事件,如果大家都用"="來操作,後面的"訂閱者"就會覆蓋前面的"訂閱者",容易造成bug,故而event關鍵字封裝了委托,關閉了直接賦值通道

 

委托的逆變與協變

用過泛型的很多同學都知道,泛型有逆變跟協變,其實委托也有逆變跟協變(介面,數組也有此特性)

那麼啥是逆變與協變呢

簡單來說

逆變:

基類變子類 -> 逆了天了,這都可以,所以叫逆變

逆變實際是編譯器根據執行上下文推斷類型是可以轉換,才編譯通過的

看似逆天實際也屬於"is-a"關係正常轉換

 

協變:

子類變基類->CLR協助變形,所以叫協變

大家在編程中常用到,"is-a"關係,所以可以正常轉換

 

對於委托,逆變與協變可以是返回值變化,也可以是參數變化,亦可以是二者同時變化

 

來來來,我們來看一些具體的慄子:

定義類型與繼承

class Person {}
 
class Employee : Person {}

定義委托

delegate Person EmployeeInPersonOut(Employee employee);

定義一些適合委托的方法

class Methods
{
    public static Person EmployeeInPersonOut(Employee employee)
    {
        return new Person();
    }
 
    public static Employee EmployeeInEmployeeOut(Employee employee)
    {
        return new Employee();
    }
 
    public static Person PersonInPersonOout(Person person)
    {
        return new Person();
    }
 
    public static Employee PersonInEmployeeOut(Person person)
    {
        return new Employee();
    }
}

常規使用

//常規使用
EmployeeInPersonOut employeeInPersonOut Methods.EmployeeInPersonOut;
Person person = employeeInPersonOut(new Employee());

協變

//協變使用
/*
 * 返回值Employee跟Person屬於"is-a"關係,所以是常規轉換
 */
EmployeeInPersonOut employeeInPersonOut Methods.EmployeeInEmployeeOut;
Person person = employeeInPersonOut(new Employee());

逆變

//逆變使用
/*
* 對於委托聲明:委托方法的參數Person竟然可以變成Employee!
* 實際是編譯器根據上下文推斷,對象可以成功轉換
* 在執行的時候, 委托聲明EmployeeInPersonOut只能輸入Employee
* Employee對於Methods.PersonInPersonOout的參數peron是"is-a關係",所以可以正常轉換成方法參數
*/
EmployeeInPersonOut employeeInPersonOut Methods.PersonInPersonOout;
Person person = employeeInPersonOut(new Employee());

協變與逆變一起使用

//這段就不解釋了,仔細看前兩段就能明白其中原理
EmployeeInPersonOut employeeInPersonOut Methods.PersonInEmployeeOut;
Person person = employeeInPersonOut(new Employee());

協變在winform中的應用

class Program
{
    static void Main(string[] args)
    {
        var button =  new Button(){Text "click me!"};
        button.Click += HandleEvent;
        button.KeyPress += HandleEvent;
 
        var form new Form();
        form.Controls.Add(button);
 
        Application.Run(form);
    }
 
    static void HandleEvent(object sender, EventArgs args)
    {
        MessageBox.Show(args.GetType().FullName);
    }
}

用匿名無參委托忽略事件參數也是可以的

button.Click += delegate {/*do something.*/};

 

委托與閉包

什麼是閉包

class Program
{
    static void Main(string[] args)
    {
        var action = ClosureMethod();
 
        action();
        action();
        action();
 
        Console.ReadKey();
 
    }
 
    static Action ClosureMethod()
    {
        int localCounter 0;
 
        Action x delegate
        {
            localCounter++;
            Console.WriteLine(localCounter);
        };
 
        return x;
    }
}

這段代碼依次輸出1,2,3

這就是閉包

可以參考javascript中的閉包,猜測一下:匿名方法使用了局部變數"localCounter",使得在方法執行完後無法釋放變數,從而形成了一個"範圍內的全局變數"

下麵我們來驗證一下這個猜測

祭出神器:IL DASM

為了看著簡單點,我把代碼稍微做了點修改

static Action ClosureMethod()
{
    string local "零";
 
    Action x delegate
    {
        local += "壹";
        Console.WriteLine(local);
    };
 
    return x;
}

漢字在il中更容易找到位置

 

從il中可以看出

C#閉包並不是與js一樣是由於垃圾回收機制的原因

由於匿名方法捕獲了一個"外部方法"的局部變數"local"

使得編譯器生成了一個"內部類"(<>c_DisplayClass1)

而"外部方法"直接使用了這個"內部類"的實例中的變數(il中的<>c_DisplayClass1::local)

委托"Aciton x"也使用了該實例

這樣變完成了"閉包", 所以C#中的閉包完全是編譯器的功勞

 

閉包的作用

1.局部變數實例化,使得外部可以使用該變數

static  IList<string> StringFilter(List<string> list, int length)
        {
            return list.FindAll(delegate(string str)
            {
                return str.Length > length;
            });
 
        }

當然也可以使用lambda表達式

static IList<string> StringFilter(List<string> list, int length)
        {
            return list.FindAll(str => str.Length > length);
 
        }

前面說過lambda表達式實際就是匿名方法

上面的代碼都捕獲了外部變數length

 

2.延長變數生命周期,委托不死,變數不亡(var action = ClosureMethod();這有在action釋放後,"ClosureMethod"的變數"local"才會被釋放)

就像閉包部分第一段代碼的計數器,在"ClosureMethod"方法執行完畢後,變數"localCounter"的生命周期延長了

 

說一說閉包中的坑

在for中使用閉包

坑1:

static void Main(string[] args)
{
    var actions = LoopClosure();
 
    actions[0]();
    actions[0]();
    actions[0]();
 
    actions[1]();
    actions[2]();
 
    Console.ReadKey();
 
}
 
static IList<Action> LoopClosure()
{
    var list new List<Action>();
 
    for (int i 0; i 3; i++)
    {
        int val = i*10;
 
        list.Add(delegate
        {
            val++;
            Console.WriteLine(val);
        });   
    }
 
    return list;
}

輸出結果是1,2,3,11,21

此迴圈雖然只有生成了一個"內部類",但是每次迴圈都產生了一個"內部類"的實例,所以會有上述結果

坑2:

var actions new List<Action>();
for (int i 0; i 3; i++)
    actions.Add(() => Console.WriteLine(i));//access to modified closure 'i'
foreach (var action in actions)
    action();

輸出結果是3,3,3

因為使用了變化/修改過的閉包變數

但是在foreach中是沒有這個坑的

var actions Enumerable.Range(0, 3).Select(i => (Action)(() => Console.WriteLine(i))).ToList();

這樣的在foreach中的閉包就能正常輸出0,1,2

 

趣味編程:

能不能在C#中像javascript一樣寫一個自執行方法,答案顯然是可以的 ^_^

 //無參自執行
((Action)(delegate
{
    Console.WriteLine("I'm a IIFE method.");
}))();

//有參自執行
((Action<int>)(delegate(int i) {
    Console.WriteLine("I'm a IIFE method with parameter:{0}", i);
}))(2);

 

參考資料:

https://msdn.microsoft.com/zh-cn/library/ee207183.aspx
https://msdn.microsoft.com/zh-cn/library/dd233060.aspx
https://msdn.microsoft.com/zh-cn/library/dd465122.aspx
http://csharpindepth.com/articles/chapter5/closures.aspx

 

歡迎以任何形式的轉載本文,轉載請註明出處,尊重他人勞動成果
轉載請註明:文章轉載自:博客園[http://www.cnblogs.com]
本文標題:說說委托那些事兒
本文地址:http://www.cnblogs.com/eyu/p/all_those_delegate_things.html

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 實習筆記1 2016年8月1日 14:12 Option Explicit 預設情況下,如果使用一個沒有聲明的變數,它將繼承“Variant”類型。在模塊、窗體和類的通用聲明區使用“OptionExplicit”能強制我們必須聲明變數後才能使用變數 Sample: 在通用聲明區聲明瞭“Option ...
  • 功能: 單擊選中行,雙擊打開詳細頁面 說明:單擊事件(onclick)使用了 setTimeout 延遲,根據實際需要修改延遲時間 ;當雙擊時,通過全局變數 dbl_click 來取消單擊事件的響應 常見處理行方式會選擇在 RowDataBound/ItemDataBound 中處理,這裡我選擇 P ...
  • 1.EF是什麼? EF是.net封裝的一個用於資料庫交互的實體層框架,它的全稱是Entity Framework。 2.EF搭建: 新建之後,我們就可以看到裡面的內容: 我們可以分別看一下它裡面有些什麼? 2.1.創建ef之後,我們EF裡面[打開方式-XML文本編輯器]看到它裡面最主要的有SSDL, ...
  • 首先,本人大學剛畢業,想把自己學習的一些東西記錄下來,也是和大家分享,如有不對之處還請多加指正。聲明:但凡是我博客里的文章均是本人實際操作遇到的例子,不會隨便從網上拷貝或者轉載,本著對自己和觀眾負責的態度。 什麼是WebAPI?我的理解是WebAPI+JQuery(前端)基本上能完成Web MVC的 ...
  • 關聯映射就是將關聯關係映射到資料庫里,在對象模型中就是一個或多個引用。 一:配置單向多對一關聯 在Emp類中定義一個Dept屬性,而在Dept類中無須定義用於存放Emp對象的集合屬性 01.Dept.java 02.Emp.java 03.Dept.hbm.xml 04.Emp.hbm.xml 05 ...
  • 最近碼牆時發現了一個很有意思的問題,定義一個引用對象,如果在迴圈外面定義對象,在迴圈里list.add(對象),最後的結果卻是所有的對象值都是一樣的,即每add一次,都會把之前所有的數據覆蓋掉,蠻有趣的,在網上輕鬆的搜到了答案,把對象在迴圈里new就行了,問題雖然解決了,但感覺這裡麵包含了一些.ne ...
  • 之前在.net平臺下操作Oracle都是用的oracle.dataaccell.dll引用,但是伺服器升級為II8後,發佈的新服務有關Oracle資料庫部分都無法運行,調試了好久發現是IIS8不支持低版本的Oracle.dataAccess.dll文件,需更新為新版本的Oracle.ManagedD ...
  • $.getJSON( )的使用方法簡介 $.getJSON( url [, data ] [, success(data, textStatus, jqXHR) ] ) url是必選參數,表示json數據的地址;data是可選參數,用於請求數據時發送數據參數;success是可參數,這是一個回調函數 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...