標籤:
從大學就開始做C#這塊,也做C#幾年了,最近又從ios轉回.Net,繼續做C#,之前也沒有寫部落格的習慣,寫部落格也是從我做ios的時候開始的,現在既然又做回了.net,那就寫點關於.Net的部落格,可能在大牛眼裡這些都是簡單基礎的,不過回過頭看我當時初學的時候覺得委託事件是不容易理解的,我這裡也是想著聯絡著OC,兩者有比較的學習下。畢竟都是物件導向語言,思想是相通的。
委託在OC中類似block,都是指向一個函數,其實他沒和C++的函數指標類似。但委託還是和函數指標不太一樣,委託是完全物件導向的,是型別安全可靠的。C++的指標僅僅指向成員函數,而委託同時封裝了一個對象執行個體和方法。
委託聲明用於定義一個從System.Delegate類派生的類。
格式:屬性集 修飾符 delegate 傳回值類型(A) 標識符(C)(形參列表(B));
一、委託是什嗎?
看上面的紅字我們可以明白其實委託是一個類。其實類是什嗎?類也是一種資料類型,它了String類一樣,也是一個資料類型,所以呢委託其實也是一個資料類型,只是這個資料類型和其他的有點不同,它這個資料類型指向的是一個函數。一個傳回值為A,形參列表為B的名為標識符C的函數。其實這和OC中的block類似,block中也是用來定義函數。我們用typedef void(^myblock1)(int a,int b);來定義一個block,其實就是定義一個資料類型。上面的委託聲明也是定義了一個參考型別的資料類型。
二、委託怎麼用?
上面也說了,聲明一個委託其實就是聲明了一個資料類型,和Person、String一樣都是一個資料類型。我們在使用委託和使用Person、String類型的資料一樣。也是先聲明:public 類型(Person、String) 變數(或屬性)名。所以我們在使用委託時也是這樣。只是這個變數或屬性對應的是一個函數。
三、例子
using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading.Tasks;namespace Delegate{ //定義了一個從System.Delegate類派生的類 //也可以理解為一種資料類型 這種資料類型指向傳回值為void 參數為Person對象的函數 //我們也可以把Person類理解為一種資料類型 只是它包含的是Name和Age public delegate void EatFood(Person p); public class Person { public string Name { get; set; } public int Age { get; set; } public Person(string name, int age) { Name = name; Age = age; } //既然委託是一資料類型和String一樣,所以可以像聲明String對象一樣聲明代理變數 public EatFood eatFood; public void eating() { if (eatFood != null) { eatFood(this); } } }}
上面定義了一個Person類,也定義了一個定義了一個從System.Delegate類派生的類EatFood,同時在Person類中聲明了EatFood類類型的一個變數,在eating()函數中使用了這個變數。ps:請留意上面代碼中的注釋。下面的代碼中我們定義了兩個Person對象,一個chinesePerson一個englishPerson,而分別為兩個類的eatFood變數指定不同的函數。
using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading.Tasks;namespace Delegate{ class Program { static void Main(string[] args) { Person chinesePerson = new Person("小明",25); //通過建構函式執行個體化對象 chinesePerson.eatFood = new EatFood(chineseEat); chinesePerson.eating(); Console.WriteLine("--------------------------------------"); Person englishPerson = new Person("Ivan",25); //通過直接複製來執行個體化對象 englishPerson.eatFood = englishEat; englishPerson.eating(); Console.ReadLine(); } static void chineseEat(Person p) { Console.WriteLine("我是{0},我今年{1}歲了,我吃饅頭",p.Name,p.Age); } static void englishEat(Person p) { Console.WriteLine("I‘m {0},I am {1} , I eat MianBao",p.Name,p.Age); } }}
可以看到針對不同的對象指定不同的eatFood變數則執行的結果也不一樣。
四、委託和其他資料類型的區別
上面也說了可以把委託當做是一個資料類型,但它和普通的資料類型還是有區別的。這可能就是現在的個性吧,委託也有委託的個性。
委託執行個體化用於建立委託執行個體,和類執行個體建立文法相同。但委託可以封裝多個方法,這些方法的集合合稱為調用列表。委託使用+、+=、-、-=運算子向調用列表中增加或刪除方法。
我們對上面的代碼稍作改動,Person類不用改,只改Main方法中的。
using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading.Tasks;namespace Delegate{ class Program { static void Main(string[] args) { Person chinesePerson = new Person("小明",25); //通過建構函式執行個體化對象 chinesePerson.eatFood = new EatFood(chineseEat); chinesePerson.eatFood += englishEat; chinesePerson.eating(); Console.WriteLine("--------------------------------------"); Person englishPerson = new Person("Ivan",25); //通過直接複製來執行個體化對象 englishPerson.eatFood = englishEat; englishPerson.eatFood += chineseEat; englishPerson.eating(); Console.ReadLine(); } static void chineseEat(Person p) { Console.WriteLine("我是{0},我今年{1}歲了,我吃饅頭",p.Name,p.Age); } static void englishEat(Person p) { Console.WriteLine("I‘m {0},I am {1} , I eat MianBao",p.Name,p.Age); } }}
為了促進中西交流,好多人去留學也有好多來到中國的,所以在吃的方面彼此都會吃對方的。所以要增加方法來表示吃不同的食物。有了委託可以通過+=、-=來實現增加、刪除調用列表,這樣就方面很多。從下面的輸出結果能看到,每個Person對象都調用了chineseEat、englishEat函數。
五、好處
上面的demo也展示了委託的使用方法,通過上面的使用我們可以思考下使用它的好處。我們如果不使用委託來實現這個功能的話,我們可能會在Person類中做一個判斷,判斷下是Chinses還是English,可是這樣的話,如果哪天有了日本、法國等,那又要多好多個判斷。可擴充性不好。可能有的會說可以在Person裡面定義一個虛方法,分別聲明Chinese、English類繼承Person類重寫虛方法,這確實是一個方法,如果有新的要擴充的話可以直接建立一個新的類重寫虛方法就搞定了,不過這樣的話如果只是這個方法不同,就要寫一個類,這樣未免殺雞用牛刀了。所以說委託還是一個不錯的選擇。如果不僅要增加語言還要增加方法那這就更麻煩了。有了委託這些全解決。
六、事件
對象之間的互動是通過訊息傳遞來實現的,而事件就是對象發送的訊息,通過發訊號的形式通知操作的發生。引發事件的對象為事件發送方,捕獲事件並對其做出響應的對象為事件接收方。在事件通訊中,事件發送方不知哪個對象或方法將接收它引發的事件,所需要的是在發送方和接收方之間用一個紐帶來聯絡,在C#中使用委託為這個紐帶。
事件聲明的格式:屬性集 修飾符 event 委託類型 事件名。
其實說白了就是事件是對委託變數的封裝。請注意上面寫的,我一直寫的是委託類型的變數,物件導向的三大特徵之一就是封裝,例如變數和屬性。在上面直接使用委託來指定函數,其實這和直接使用變數一樣,但是在物件導向中一般不會直接存取變數,而是對變數進行封裝,例如屬性{get;set;}方法。事件是對委託的封裝。我們來看一下事件的使用,和上面使用委託一樣,我們在Person類中聲明一個事件。
using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading.Tasks;namespace Delegate{ //定義了一個從System.Delegate類派生的類 //也可以理解為一種資料類型 這種資料類型指向傳回值為void 參數為Person對象的函數 //我們也可以把Person類理解為一種資料類型 只是它包含的是Name和Age public delegate void EatFoodDelegate(Person p); public class Person { public string Name { get; set; } public int Age { get; set; } public Person(string name, int age) { Name = name; Age = age; } //既然委託是一資料類型和String一樣,所以可以像聲明String對象一樣聲明代理變數 //public EatFood eatFood; //之前是直接聲明委託,現在是聲明一個事件 public event EatFoodDelegate EatFoodEventHandler; public void eating() { if (EatFoodEventHandler != null) { EatFoodEventHandler(this); } } }}
using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading.Tasks;namespace Delegate{ class Program { static void Main(string[] args) { Person chinesePerson = new Person("小明",25); //通過建構函式執行個體化對象 chinesePerson.EatFoodEventHandler += new EatFoodDelegate(chineseEat); chinesePerson.EatFoodEventHandler += englishEat; chinesePerson.eating(); Console.WriteLine("--------------------------------------"); Person englishPerson = new Person("Ivan", 25); //在委託中 可以直接使用=來給委派物件複製 而在事件中就不能直接使用= 要使用+= englishPerson.EatFoodEventHandler += new EatFoodDelegate(englishEat); englishPerson.EatFoodEventHandler += chineseEat; englishPerson.eating(); Console.ReadLine(); } static void chineseEat(Person p) { Console.WriteLine("我是{0},我今年{1}歲了,我吃饅頭",p.Name,p.Age); } static void englishEat(Person p) { Console.WriteLine("I‘m {0},I am {1} , I eat MianBao",p.Name,p.Age); } }}
上面可以看到,使用事件來實現了同樣的功能。
七、委託和代理設計模式的區別
不管是使用委託或者事件其實它們都是在A對象(本例中的Person對象)中調用B對象中的方法,這與設計模式中有相似之處。具體代理設計模式這裡就省略了,委託和代理都是在A對象使用B對象中的方法。不過它們還是有區別的,委託中在A中直接使用的是B中的方法,是類與方法之間的,代理設計模式中是將A類中設定一個B類變數,然後通過B來使用B中的方法,是類與類之間的。這也是我的個人理解,不知道對不對,錯了的話也希望大牛指正,以免耽誤了其他的社會主義接班人。
C#文法之委託和事件