委派跟事件,一直是蠻難真正搞懂的一個概念,但是對UI寫作的人或是開發控制項的人來說,卻又經常用到,我想寫系統的人或許也有機會碰到吧,過去一段時間,我一直沒辦法完全搞懂,甚至只是很皮毛的瞭解就拿來用了。為了讓自己更有概念一點,這次花了些時間學習,記錄如下。
◆什麼時候會用到委派?
有兩種狀況:
1.你要寫一個Class或Interface,其中有個函式,會讓別的Class呼叫(注意,是Class,不是Instance喔)。也就是在撰寫類別的時候,就能夠呼叫非實體的函式。
2.你要寫一個Class或Interface,其中有個函式,會達成某種特別功能,但是,寫的時候,你並不知道這個功能會怎麼完成,好比說,你這個Class是個登入的介面或Model,但是密碼驗證的部分,是你提供方法出來,給要使用你這個Class的人,到時候要實作函式內容的(將委派綁定函式)。
當你撰寫一個Class裡面需要呼叫該Class無法呼叫到的函式(當然,就不是這個Class宣告的函式囉)的時候,就會需要用到委派,而什麼情況下Class無法呼叫某個函式呢?我們先看看,什麼情況下,Class可以呼叫非Class自己的函式呢?比方有兩個Class,一個是ClassA一個是ClassB,如果在ClassA中宣告產生了ClassB,那麼ClassB的函式就可以被ClassA呼叫(如果宣告成Public或是有封裝出來),這是物件導向的基本概念,相信大家都懂,但是,如果ClassB想呼叫ClassA的函式呢?那就做不到了。程式如下:
Class ClassA
{
public void RunClassA()
{
ClassB MyClassB = new ClassB();
MyClassB.MethodB();
}
public void MethodA()
{
Console.WriteLine( "MethodA被呼叫了" );
}
}
Class ClassB
{
public void RunClassB()
{
ClassA.MethodA();
}
public void MethodB()
{
Console.WriteLine( "MethodB被呼叫了" );
}
}
上述紅底的那一行,就是無法做到的,因為ClassB沒有宣告ClassA,對ClassB來說,他不曉得ClassA是什麼,那麼,如果在ClassB也宣告一個ClassA呢?那麼A中有B,B中又有A,A中又有B,無限循環下去記憶體會吃光光,這種怪程式應該無法跑不下去,有解決方法嗎?有啊,除了用Static來做,或是把ClassA傳進來給ClassB......但是,有時候ClassA不是你寫的,你沒辦法預期人家怎麼做,但是卻又有這種需求,這時候就需要委派了。
◆委派怎麼做呢?
先不要講程式,免得把人搞昏了,概念上,就是ClassB這邊,宣告一個叫做「委派」的東西,這個委派,可以讓ClassB被別人宣告使用的時候,也同時可以使用這個委派,透過特殊的語法,在ClassA把委派跟某個函式綁在一起,這樣只要有人(不論ClassB或ClassA都可以)叫這個委派做事,這個委派就會去找到這個函式,然後執行,這樣ClassB就可以呼叫到ClassA了,聽起來似乎落落長,實際上不難,我們來看看怎麼做。
再稍微細節一點來看,這個作法,就很像是把ClassA的函式的記憶體位址告訴ClassB的委派,而委派大概這麼使用的,這個程式語法跟C#原本語法概念不太相同,不好理解,當成公式吧,要用的時候查就好了。
在ClassB裡頭,先宣告委派
public delegate int DelegateDefine( String MyPara );
好複雜難懂得一行,不急,這邊指示先宣告委派型別,目的是定義好你這個委派會呼叫什麼樣的函式,那個函式有怎樣的參數,又是怎樣的回傳型態。比方說,這裡是宣告說,我有一個委派叫做DelegateDefine,他到時候要呼叫一個「會傳入一個String的函式,然後這函式的回傳值,是int,就這樣而已。
接下來,把這個委派產生成物件
public DelegateDefine DelegateObj;
接下來ClassA就可以開始把委派跟函式綁在一起,當然,先決條件就是這個委派宣告的函式型態,可得跟ClassA要綁定的函式一樣喔,比方,函式如下
public int ClassA_Method( String Paras )
{
return 1;
}
然後開始綁定,可以把綁定寫在ClassA的建構子,也可以寫在某個函式裡面,只要能寫在某件事情之前,什麼事情?就是要叫用那個ClassB的委派之前就行了。綁定的語法也是比較特殊,就是把委派物件跟函式綁在一起就是了。
My_ClassB.DelegateObj = new ClassB.DelegateDefine( ClassA_Method );
看起來有點複雜,有沒有簡化的寫法?有,有,有,經過簡化過後你只需要這樣寫就行了:
My_ClassB.DelegateObj = ClassA_Method;
之後,不論ClassA或ClassB都可以用DelegateObj.Invoke( "xxx" );來呼叫函式ClassA_Method 了(xxx就是看你到時候要傳什麼當參數囉),就這樣,很簡單吧?
我寫了一個實際的小範例,大概是這樣的,有一個Class是使用者控制項,用來設計UI放元件的,叫做UC_Child,另外,有兩個Class是Form,也是用來設計UI的,分別叫做Form_Mother與Form_Father,UC_Child設計好了之後,會被Form宣告使用,很顯然的,Form就可以呼叫並使用UC_Child寫好的函式。但,這裡我需要讓UC_Child反向來呼叫Form這邊的函式呢?比方UC_Child這邊按下某個按鈕,或是完成收送之後,要通知Form來更新畫面呢?這個更新畫面,當然是只能寫在Form這邊,而無法寫在UC_Child,這時候,就要透過委派了。
這個範例,除了很簡單的做出委派之外,也包含了幾個委派的特性
1.委派可以同時綁兩個以上的函式
2.委派可以讓很多Parent宣告使用
3.委派綁定了函式之後,當呼叫函式時,並不會在遇到Return的時候就走回去,而是會把整個函式跑完的。
4.委派產生之後,不但Child自己可以Invoke,連Parent也可以呼叫Invoke,只不過,Parent用起來就沒什麼太大的實質意義了。
5.委派同時綁兩個以上的函式之後,會每個函式都跑過了,才會回到委派的下一行來。所以在Child裡面的Btn_Delegate_Click()裡,呼叫了DelegateObj.Invoke()之後,即便他綁了兩個函式,也不會回到這邊兩次,只會最後回到這邊來,然後帶著最後一個回傳值喔。
範例放在這邊 跟這邊
那麼,什麼是事件呢?其實大家對事件的接觸跟瞭解應該反而比委派多,就我目前瞭解,委派跟事件幾乎一樣,用法差不多,反而事件還比較複雜一點,據說,事件有多一些保護機制,但是在我瞭解之前,我也不亂多說,在上面的Link當中有Delegate跟Event兩個範例,差異實在很小。
1.原本在Child要把委派產生個實體出來,要用 事件 的方式的話,就不用,變成用以下的特殊語法,產生一個符合那個委派的事件,然後在Father跟Mother改成用事件跟函式綁定
public event DelegateDefine On_Env_Obj;
2.在Father跟Mother原本也可以叫用的Child的委派,現在改用事件的話,無法叫用Child的 事件 喔,也就是程式裡遮掉的那行
//My_Child.On_Env_Obj.Invoke 不可以
精華文章!!! 講解的十分清楚,初學者必看
回覆刪除謝謝你^^,如果我有更多的瞭解,一定會再做補充^^
回覆刪除請問,我已經正確的在form1裡把委派物件跟函式綁在一起了,
回覆刪除可是在form2要使用DelegateObj.Invoke("xxx");時,
卻還總是出現DelegateObj是null的狀況呢?
我猜你的「委派與函式綁定」這段沒做好
回覆刪除[ 我已經正確的在form1裡把委派物件跟函式綁在一起了 ] 我猜你這段做的有問題,把你程式碼貼上來看看?
先謝謝你了~~
刪除我弄了一個很簡單的程式,如下:
Form1:
namespace Delegate
{
public partial class Form1 : Form
{
Form2 tt = new Form2();
public Form1()
{
InitializeComponent();
tt.DelegateObj = new Form2.DelegateDefine(showmessage);
}
private void button1_Click(object sender, EventArgs e)
{
Form2 F = new Form2();
F.Show();
}
public void showmessage(string aa)
{
label1.Text = aa;
}
}
}
Form2:
namespace Delegate
{
public partial class Form2 : Form
{
public delegate void DelegateDefine(String MyPara);
public DelegateDefine DelegateObj;
public Form2()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
DelegateObj.Invoke("Message from Form2");
}
}
}
以上,
我基本上就是照著您的作法。
可是要執行Form2的button時,
就會出現錯誤。
我去trace程式,
在Form1的tt.DelegateObj = new Form2.DelegateDefine(showmessage);這一行過後,
可以看到DelegateObj有內容,
可是執行到Form2的DelegateObj.Invoke("Message from Form2");這一行時,
卻發現DelegateObj已經變成null了。
我解了!!!
刪除原來是我自己笨,
在 Form1 的 button 1 中又 new 了 form2,
所以這個form2 裡的DelegateObj當然沒有被綁定啦!!
還是非常謝謝你的網頁說明,
真的是非常清楚又易懂~~
不好意思,我假日行程滿檔,所以沒能幫你看看
刪除一早就看到你有好消息
沒錯,你把Form2 產生在兩個地方,所以按鈕內的就沒有綁定到
^_^
以你的form1跟form2來說
回覆刪除form1裡頭要產生form2物件,然後讓委派綁定事件
form2 My_form2 = new form2( );
// 委派與函式綁定
My_form2 .DelegateObj = new form2.DelegateDefine( form1_Method );
簡化一下用法
回覆刪除form2 My_form2 = new form2( );
// 委派與函式綁定
My_form2 .DelegateObj = form1_Method;
謝謝您的分享,很受用...
回覆刪除我有點困惑了,我寫出了一個看似A中有B,B中有A的程式
回覆刪除但結果竟然可以編譯
class ClassA
{
ClassB classb;
public void MethodA()
{
Console.WriteLine("this is method A");
}
public void MethodA2()
{
classb = new ClassB();
classb.MethodB();
Console.WriteLine("this is the end");
}
}
class ClassB
{
public ClassA classa;
public void MethodB()
{
classa = new ClassA();
classa.MethodA();
Console.WriteLine("this is Method B");
}
}
這樣呼叫A的methodA2是可運作的
但是methodA2要實體化B
而B內又有實體化A
理論上會不能編譯
可實際上運作順利,請問何解??
因為你不是把產生物件寫在建構子當中
刪除所以編譯會過,只要不要呼叫那個函式,就不會產生實體
如果你把產生實體都寫在建構子,那執行程式就會記憶體(資源)不足了
好懷念吶,最近要重新熟悉c#委派的用法,找著找著,就找到這裡來了
回覆刪除不知道仙人有沒有聽過Lambda
刪除哈哈哈
刪除歡迎回來
Lambda 不會很難,那個只是在程式語言中,加上一些特殊寫法而已
回覆刪除只是沒有人講解,不好懂
不好意思仙人你的範例link已經失效了
回覆刪除可以重新再po一次嗎><
最近在學delegate 覺得您的文章淺顯易懂
刪除原來範例LINK失效了,我這幾天弄回來
LINE恢復了喔,可以下載範例了
刪除oh~~我最近也在學delegate 也想看看範例 真心期待範例重出江湖
回覆刪除看完後我對照我手邊的程式碼真的有點理解
原來範例LINK失效了,我這幾天弄回來
刪除LINE恢復了喔,可以下載範例了
刪除範例的連結無法使用,希望可以修復
回覆刪除範例的連結無法使用,希望可以修復
回覆刪除另外希望C#文章能再多一點救救新手
這比微軟教的委派簡單多了=_= 非常感激
回覆刪除另外範例的連結無法使用,希望可以修復
不好意思想詢問一下
回覆刪除{
.......什麼事情?就是要叫用那個ClassB的委派之前就行了。綁定的語法也是比較特殊,就是把委派物件跟函式綁在一起就是了。
My_ClassB.DelegateObj = new ClassB.DelegateDefine( ClassA_Method );
}
理論上如果我要在CLASS A可以看到 CLASS B宣告的 DelegateObj,我必須要將DelegateObj 宣告成 static嗎?
範例的連結無法使用,希望可以修復
回覆刪除範例的連結無法使用,希望可以修復
回覆刪除受益良多,多謝講解
回覆刪除受益良多,但是範例的連結無法使用了,希望您有空時能幫忙修復,以利造福更多人們,感謝您的教學與分享!
回覆刪除感謝
回覆刪除