挖一挖委托那些事兒,匿名方法,委托的逆變與協變,委托與閉包,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