程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> C# >> 關於C# >> 我眼中委托的真正面貌(一)

我眼中委托的真正面貌(一)

編輯:關於C#

首先我們以書中所介紹的委托的由來為起始點展開話題:

在C或C++中有時為了開啟一個子線程,我們會用到如下的方法:

HANDLE hThread;

DWORD ThreadID;

hThread = CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)ThreadFunc,NULL,0,&ThreadID);

以上語句中的ThreadFunc()為C++中的一個全局方法。因為C++或者C語言都是允許面向過程的,因此全局函數是可以存在的。但大家不妨試想一下,如果是C#或者Java這種純面向對象的語言,我們要怎麼做呢?

思考一下,在C#中你可以用如下的代碼來實現相同的效果嗎?

Tread subTread = new Tread();

subTread.Start(EntryPoint);

這樣做是明顯不對的!在C++中函數名EntryPoint實際上就是一個全局函數指針,它表明了子線程的入口地址。然而,在C#中這樣的情況是絕對不存在的!原因很簡單——作為一門面向對象的語言,C#中很少有方法是獨立於對象而存在的。

在C#中調用方法時,這些方法通常都要與一個類的實例相關聯。也就是說,如果要傳遞一個方法,那麼就必須把該方法的相關細節包裝在一種新型的對象中。於是,伴隨著這種需要,委托隨之誕生。

以上只是《C#高級編程》一書中所提到的委托的產生過程,在這裡我並不想深究其中的機制,而是以此為線索,深層次的挖掘C#中委托的實質用途及其優勢所在。

這樣一來,首先單純根據委托的定義可知,委托可以簡單的包裝方法的細節:

1.簡單的包裝方法

namespace SimpleDelegate

{

    public partial class Form1 : Form

    {

        public Form1()

        {

            InitializeComponent();

        }

       //定義委托

        private delegate void MyDelegete();

       //聲明委托的實例變量,並為其掛載相應的方法

        private MyDelegete MyObjdelegate = new MyDelegete(MyFun);

        private void button1_Click(object sender, EventArgs e)

        {

            MyObjdelegate();

        }

        static private void MyFun()

        {

            MessageBox.Show("委托掛載的事件調用成功!");

        }

    }

}

首先,我們定義委托類型void MyDelegete();

這裡有必要強調一下委托的聲明規則:委托使用關鍵字delegate來聲明,因其本身就是一種類型,故可以在類內聲明,亦可以獨立於類而聲明。關鍵字之後是此委托類型的實體部分。不難看出其形式與方法的聲明相近,但不包含函數體。

其實,我們大可理解為委托本身就是定義了一類方法,一類其返回值與參數表和委托實體定義形式完全相同的方法。只有符合委托規則的方法,才可以掛接於此委托的實例對象之上。這也是為什麼,我將整個void MyDelegete()稱作委托類型的原因。

接下來我們要在某一個類內部,運用先前定義的委托類型實例化一個委托對象MyObjdelegate,並為其掛載一個與之委托類型(無返回值,無參數)相符的自定義方法MyFun(),這也就是所謂“將方法MyFun()的相關細節包裝於委托實例MyObjdelegate中”。而後即可在相應的按鈕單擊事件中利用委托對象MyObjdelegate調用其已經包裝的方法MyFun()了。

以上即為由委托定義而得來的委托的簡單用法。不過,以上這個實例顯然沒有什麼實際用途,因為不涉及類間的相互調用,我們大可在事件中直接調用本類的方法MyFun(),而不必再多繞個彎依靠委托來實現。

不過,如果涉及到跨類的方法調用時,使用委托會怎麼樣呢?

2.使用本類的委托對象包裝其他類的方法

namespace MulClsDelegate

{

    delegate void MyDelegete();

    public partial class Form1 : Form

    {

        public Form1()

        {

            InitializeComponent();

        }

        private MyDelegete MyObjdelegate = new MyDelegete(ObjCls.MyFun);

        private void button1_Click(object sender, EventArgs e)

        {

            MyObjdelegate();

        }

    }

    public class ObjCls

    {

        static public void MyFun()

        {

            MessageBox.Show("委托掛載的事件調用成功!");

        }

    }

}

這段代碼和上一段代碼的意思大同小異,委托類型void MyDelegete()的聲明被我移到了類的外面,當然這不影響大局。主要區別只在於這段代碼涉及到了使用本類委托對象跨類包裝其他類的方法細節問題——我把原來主類中的MyFun()方法移到了新聲明的一個類ObjCls中,然後再靠主類中的委托對象去調用這個方法。

或許你可能會說,即使我不用委托,也可以通過在主類中聲明其他的類對象,然後通過這個類對象來調用其他類的public方法。這樣一來,使用委托依然沒有什麼實實在在的意義。

那麼,呵…… 我們不妨繼續耐心而深入的挖掘委托更深層次的用途

3.在本類中操控其他類的委托對象

namespace MulClsDelegate

{

    public partial class Form1 : Form

    {

        ObjCls objcls = new ObjCls();

        public Form1()

        {

            InitializeComponent();

        }

        private void button1_Click(object sender, EventArgs e)

        {

            objcls.MyObjdelegate += new ObjCls.MyDelegete(DelegateFun);

            objcls.MyFun();

        }

        public void DelegateFun()

        {

            MessageBox.Show("次類委托所掛載的本類事件調用成功!");

        }

    }

    public class ObjCls

    {

        public delegate void MyDelegete();

        public MyDelegete MyObjdelegate;

        public void MyFun()

        {

            MyObjdelegate();

        }

    }

}

這段代碼相對於上段代碼產生了部分改動,不過,意思上大體相同:這次的委托對象定義在次類中,不過僅僅是一個引用,我並沒有在次類中對其進行任何的初始化。相反,次類中委托對象方法掛載的工作是在主類中進行的:首先,我在主類中定義次類的實體對象,而後通過這個對象將主類的DelegateFun()方法掛接到次類的委托對象上。這樣當次類的方法MyFun()被調用(事實上,你也可以將這個方法看做一個事件,當事件被觸發時),就會調用主類中事先寫好的DelegateFun()方法。

寫到這裡,我不知道你有沒有看出使用委托的優勢所在。其實,將第二段、第三段代碼反復比對之後你就會發現二者有明顯的不同!我們暫且撇開委托這種機制不談,但看兩段代碼中本類與次類方法交互的手法便不難發現:二、三同為一個類調用另一個類中的方法,前者為一個類控制自身調用(另一個類的)一些方法;而後者卻是一個類在控制另一個類調用哪些方法。

呵呵……悟性好的人應該已經聯想到了,這種手法很像C#中的事件機制。而事實上,C#中的事件就是委托的一種特殊形式。

4.C#中事件的實現

首先,我先構建一個用戶控件工程,代碼如下:

namespace Ctrleven

{

    public partial class UserControl1 : UserControl

    {

        //定義委托

        public delegate void MyEventHander();

        //用委托聲明事件

        public event MyEventHander MyEvent;

        public UserControl1()

        {

            InitializeComponent();

        }

        private void button1_Click(object sender, EventArgs e)

        {

            MyEvent();

        }

    }

}

這裡,稍稍說明一下控件中事件的聲明。事件的聲明需使用關鍵字event,大致的形式是event +(某委托類型)+ 一個實例對象。

而後我建立一個Demo程序,來調用這個用戶控件,代碼如下:

namespace EventDemo

{

    public partial class Form1 : Form

    {

        public Form1()

        {

            InitializeComponent();

        }

        private void userControl11_MyEvent()

        {

            MessageBox.Show("委托事件調用成功!");

        }

    }

}

在工程中引入用戶控件,打開其事件窗口可以找到我們事先定義的事件MyEvent,雙擊就可以在其中寫入代碼。這裡,我用一個MessageBox來進行測試。

編譯通過後,單擊控件上的button就會彈出我們的MessageBox,事件調用成功!

呵呵……發現了嗎?這段代碼所隱含事件機制的雛形實際上就是第三段代碼中所實現的效果。只不過這裡我們是用一個具體的窗口類控制了其所引入控件類中的委托對象。

看到類構造方法中的InitializeComponent()方法了嗎?雙擊轉到定義,在Windows窗體設計器生成的代碼中,在你所引入的控件屬性初始化模塊下有這樣一句代碼:this.userControl11.MyEvent += new Ctrleven.UserControl1.MyEventHander(this.userControl11_MyEvent); 你可以拿來跟第三段代碼中的那句 objcls.MyObjdelegate += new ObjCls.MyDelegete(DelegateFun); 對比一下,發現了嗎?是一樣的!

所以,C#中的事件本身就是一種(多重)委托機制,憑借關鍵字event,使得我們在控件中定義好的事件專用委托實例對象在頂層的Demo程序的事件窗口中被自動識別。也就是說,即使不用事件,純用委托,事件的機制一樣可以實現,問題只是這樣的委托對象無法輕易被外界識別罷了。

5.純用委托仿真控件的事件機制

首先,我們依然建立一個控件項目,比之上一段代碼作如下調整:

namespace Ctrleven

{

    public partial class UserControl1 : UserControl

    {

        //定義委托

        public delegate void MyEventHander();

        //用委托聲明事件

        public MyEventHander MyEvent;//純用委托仿真event對象

        public UserControl1()

        {

            InitializeComponent();

        }

        private void button1_Click(object sender, EventArgs e)

        {

            MyEvent();

        }

    }

}

在這裡我們去掉了event關鍵字,改用純委托對象來實現。

而後,同樣建立一個上層的Demo程序來調用這個控件,同樣比之上一段代碼要作些許細微的調整:

namespace EventDemo

{

    public partial class Form1 : Form

    {

        public Form1()

        {

            InitializeComponent();

            this.userControl11.MyEvent += new Ctrleven.UserControl1.MyEventHander(this.VirtualEvent);

        }

        public void VirtualEvent()

        {

            MessageBox.Show("委托仿真事件調用成功!");

        }

    }

}

看到區別了嗎?呵呵…… 此時InitializeComponent()關於事件掛接的那句話已經不見了,因為這次的MyEvent並不是一個事件專用的委托對象,而是一個普通的委托實例對象,不會自動被C#的編輯器識別。所以,我在主窗口類的構造方法中人為添加了一句代碼:

this.userControl11.MyEvent += new Ctrleven.UserControl1.MyEventHander(this.VirtualEvent);

它的作用其實和上一段代碼中那句是相同的,就本質而言都是在一個類中操控另一個類的委托對象而已,只不過因為我們沒有嚴格按照C#中的事件聲明方式來實現事件機制,因此,這句代碼C#的編輯器不會很友好的替你生成,而是要靠你自己來寫。呵呵……

可見,程序運行的效果和上一段代碼大致相同。

在這裡,我想糾正大家的幾個誤區。平時,我經常聽到人們談論,說所謂委托就是將自己寫好的一段代碼作為參數傳入另一個方法中。沒錯,用委托的確可以實現類似的說法,比方說某一個方法的參數表中的一個參數是委托類型,這樣子的確是可以的。但是,可以實現這種效果的先決要素首先是我們可以利用委托來傳遞一段代碼(也就是一個方法)。所以說真正起到傳遞方法效果的不是方法中的參數,而是委托對象本身。使用委托對象本身來實現跨類、跨線程甚至跨空間傳遞方法,這種實現形式的使用頻率要比僅僅將委托作為參數,以實現將代碼傳入另一個方法這樣的實現形式來的高得多得多。

好,寫到這裡,我想應該可以稍微闡述一下我自己的觀點了。呵呵……

個人認為,委托最大的優勢所在就是——其兼有對象和方法二者的性質。我們既可以將一個委托實例對象作為對象來看待甚至操控,亦可以將其作為一個現成的方法來調用。(未完待續)

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved