委託和事件

庖丁解牛——深入解析委託和事件

這篇博文我不講委託和事件的概念,因爲大段的文字概念沒有任何意義。

具體想了解,委託和事件的概念可以MSDN查閱。

我這篇文章的主題思路是委託如何一步步進化成事件:

何爲委託--->委託來實現事件--->用方法對委託的封裝--->Event的

add,remove方法的引入--->標準事件寫法--->反編譯探究竟。

用幾個例子以及Reflector反編譯探究委託和事件的關係。不足之處,還望多多指教...

何爲委託:

首先,委託是一種類型,是一種定義了方法簽名的類型。

委託可以理解爲函數指針(安全),並且委託約束了方法的簽名(由返回類型和參數組成),

所以實例化委託時,可以將其實例與任何具有相同簽名(由返回類型和參數組成)得方法相關聯,

如果按照C語言函數指針理解,即委託實例指向某個方法。

爲什麼要用委託:

舉個簡單的例子:

例如,需要判斷一個數是爲奇數還是偶數?

可能我們會這樣實現:

     static void Main(string[] args)
        {
            Console.WriteLine((4 % 2 == 0) ? "偶數" : "奇數");
            Console.WriteLine((5 % 2 == 0) ? "偶數" : "奇數");
            Console.ReadKey();
        }

上面例子很簡單,但是很不靈活,我們稍加改進:

static void Main(string[] args)
{
     Console.WriteLine("請輸入一個數字:");
     int i = int.Parse(Console.ReadLine());
     Console.WriteLine((i%2==0)?"偶數":"奇數");
     Console.ReadKey();
}

上面這個簡單的例子,也是挺有玄機的。對於程序員,我們不關心客戶端用戶傳過來是奇數還是偶數,況且我們也不知道傳過來的參數到底是多少,

我們只關心怎樣來實現功能。對於用戶來說,他們不必關心底層到底是怎樣實現功能的,他們只負責輸入數字即可“坐享其成”。這個例子,

可以理解成一個最簡單的解耦。

  看了上面這個例子,我們再舉一個例子來演示委託怎麼替做什麼:

  

複製代碼
    //委託是一種定義方法簽名的類型。 當實例化委託時,可以將其實例與任何具有兼容簽名的方法相關聯。 可以通過委託實例調用方法。
    //一個委託聲明:
    public delegate void ChangeDelegate(int i);
    class Program
    {
        static void Main(string[] args)
        {
            ChangeDelegate c = null;
            if (DateTime.Now.Second%2==0)
            {
                c = Even;//委託可以理解爲函數指針(安全),並且委託類型ChangeDelegate約束了參數類型,所以c可以指向Even方法
                c(DateTime.Now.Second);
            }
            else
            {
                //c = Odd;
                c = new ChangeDelegate(Odd);//標準寫法
                c(DateTime.Now.Second);
            }
            Console.ReadKey();
           
        }
        static void Even(int i)
        {
            Console.WriteLine("{0}是偶數",i);
        }
        static void Odd(int i)
        {
            Console.WriteLine("{0}是奇數",i);
         
        }
    }
複製代碼

上面代碼可以看出,程序員並不知道委託實例到底指向那個函數,但他可以確定,指向的那個方法必定是受ChangeDelegate約束的。

再看一個例子,我們要對數組進行操作:

複製代碼
 class Program
    {
        static void Main(string[] args)
        {
            int[] arr = { 1,2,3,4,5,6,7,8,9};
            List<int> newList=Filter(arr,IsOdd);
            Console.WriteLine(string.Join("-",newList.ToArray()));
            Console.ReadKey();

        }
        /// <summary>
        /// 判斷是否爲偶數
        /// </summary>
        /// <param name="i"></param>
        /// <returns></returns>
        static bool IsEven(int i)
        { 
            return i%2==0;
        }
        /// <summary>
        /// 判斷是否爲奇數
        /// </summary>
        /// <param name="i"></param>
        /// <returns></returns>
        static bool IsOdd(int i)
        { 
            return i%2==1;
            
        }
        
        static List<int> Filter(int[] value, FilterDelegate f)
        {
            List<int> list=new List<int> ();
            foreach (var item in value)
            {
                if (f(item))
                {
                    list.Add(item);
                }
            }
            return list;
        }
    }
    delegate bool FilterDelegate(int i);
複製代碼

所以,static List<int> Filter(int[] value, FilterDelegate f),這兒只需要傳過來一個方法,

而我們程序員並不關心這是個什麼方法,能替我們做什麼,這就是委託的好處。

  事件和委託的聯繫:學習事件之前,先來用委託來模擬實現事件:

 

複製代碼
    class Program
    {
        static void Main(string[] args)
        {
            Counter c = new Counter();
            c.onCount = Count;//相當於訂閱了一個事件
            c.onCount += Count_2;//可以多個人同時監聽了
            int j = 0;
            while (j<=100)
            {
                j++;
                c.Next();
            }
            Console.ReadKey();
        }
        static void Count(int value)
        {
            Console.WriteLine("Count監聽了{0}是偶數", value);
        }
        static void Count_2(int value)
        {
            Console.WriteLine("Count_2監聽了{0}是偶數", value);
        }
    }
    class Counter
    {
        public OnCountDelegate onCount;//把委託對象聲明爲一個字段
        private int i = 0;
        public void Next()
        {
            i++;
            if (i%2==0)
            {
                if (onCount!=null)
                {
                    //解耦:解除耦合。
                    //不用關心到底指向誰,調用就行
                    onCount(i);//觸發事件,調用onCount指向的函數,相當於把事件通知出去
                }
            }
        }
    }
    public delegate void OnCountDelegate(int value);
複製代碼
  c.onCount = Count;
  c.onCount += Count_2;
 相當於監聽事件,觸發事件時,只要哦調用onCount指向的函數,這樣相當於把事件通知出去。所以上面這個Demo之後,我們再可以對委託來實現事件進行擴展:
 我們自定義一個雙擊火箭手按鈕[用戶控件]:
 
複製代碼
namespace 雙擊火箭手按鈕
{
    //聲明一個委託
    public delegate void DoubleClickDelegate();
    public partial class DoubleClickButton : UserControl
    {
        public DoubleClickButton()
        {
            InitializeComponent();
        }
       
        private int i;
        //把一個的委託對象定義爲Public字段
        public DoubleClickDelegate OnDoubleClick;

        private void button1_Click(object sender, EventArgs e)
        {

            i++;
            if (i==2)
            {
                i = 0;
                if (OnDoubleClick!=null)
                {
                    OnDoubleClick();//調用事件的響應函數
                }
            }
        }
    }
}
複製代碼

然後我們在Form1中託入一個我們自定義的DoubleClickButton:

這兒我們可以仿照普通Button按鈕監聽Click事件:  this.button1.Click += new System.EventHandler(this.button1_Click);來那樣做:對DoubleClickButton的

OnDoubleClick事件進行監聽:
複製代碼
        private void Form1_Load(object sender, EventArgs e)
        {
            doubleClickButton1.OnDoubleClick = doFire;
            doubleClickButton1.OnDoubleClick += doIce;        }
        void doFire()
        {
            MessageBox.Show("雙擊火槍手開火");
        }
           void doIce()
        {
            MessageBox.Show("雙擊火槍手下冰雨");
        }
複製代碼

這樣一個簡單的用戶控件就完成了,雙擊兩下觸發了OnDoubleClick事件,並且去執行相關聯的響應函數(doFire,doIce)。

接着我們在對這個程序進行修改,來干擾我們的雙擊火槍手按鈕:

我們在搗亂這個按鈕的中寫入:

        private void button1_Click(object sender, EventArgs e)
        {
            doubleClickButton1.OnDoubleClick = null;
        }
上面的操作意味着我們把委託變量指向了NULL,這就破壞了之前我們的監聽事件。
再比如:在模擬執行這個按鈕中寫入:
        private void button2_Click(object sender, EventArgs e)
        {
            doubleClickButton1.OnDoubleClick();
        }

上面代碼模擬執行了雙擊火槍後按鈕,本來需要雙擊兩下才能觸發事件,而這兒可以直接去執行事件的響應函數。

所以爲了防止外界對我的事件的干擾,我們把

public OnCountDelegate onCount;//把委託對象聲明爲一個字段

改爲:

   //把委託申明爲Private,防止外界直接=NULL或者OnDoubleClick()仿照事件
   private DoubleClickDelegate OnDoubleClick;

再對私有的委託用一個AddDoubleClick進行對外界的過濾,所以完整代碼應該是這樣的:

複製代碼
 //把委託申明爲Private,防止外界直接=NULL或者OnDoubleClick()仿照事件
        private DoubleClickDelegate _OnDoubleClick;
        public void AddDoubleClick(DoubleClickDelegate d)
        {
            _OnDoubleClick += d;
        }

        private void button1_Click(object sender, EventArgs e)
        {

            i++;
            if (i==2)
            {
                i = 0;
                if (_OnDoubleClick!=null)
                {
                    _OnDoubleClick();//調用事件的響應函數
                }
            }
        }
複製代碼

上面這樣處理之後,我們的控件就不會被外界干擾了。

但是這樣操作太複雜,所以微軟爲我們提供了更爲簡便的方式既 Event:

對上面的雙擊火槍手按鈕在做稍微修改:

複製代碼
namespace 三擊暴走按鈕
{
    public delegate void RampageDelegate();
    public partial class RampageThreeClickButton : UserControl
    {

        //定義一個私有的委託類型字段
        private RampageDelegate onRampage;
        //類似於屬性那樣對委託進行了封裝
        public event RampageDelegate OnRampage 
        {
            add { onRampage += value; }
            remove { onRampage -= value; }
            
        }
        private int count;
        public RampageThreeClickButton()
        {
            InitializeComponent();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            count++;
            if (count==3)
            {
                if (onRampage!=null)
                {
                    onRampage();
                }
                count = 0;
            }
        }
    }
}
複製代碼

再將定義好的暴走控件拖到Form1中:

後臺代碼是這樣的:

複製代碼
       public Form1()
        {
            InitializeComponent();
        }

        private void rampageThreeClickButton1_Load(object sender, EventArgs e)
        {
            //對事件進行註冊,即監聽事件,只能用+=,不用用=,這樣防止了外界=NULL干擾
            rampageThreeClickButton1.OnRampage += doFire;
            //同理
            rampageThreeClickButton1.OnRampage += new RampageDelegate(doThunder);
            rampageThreeClickButton1.OnRampage += new RampageDelegate(doIce);

        }
        void doFire()
        {
            MessageBox.Show("烈焰之怒");
        }
        void doThunder()
        {
            MessageBox.Show("上古雷霆");
        }
        void doIce()
        {
            MessageBox.Show("極地冰雨");
        }
複製代碼

現在還能干擾嗎?當然不行:

OK,到目前爲止,大家對委託和事件肯定有一個深刻的認識:

我們對事件進行反編譯之後,相信一切疑問皆是浮雲:

我們舉一個標準的事件案例:

複製代碼
namespace DoubleKissinUButton
{
    public delegate void KissinUDelegate();//委託別忘記
    public partial class KissinUButton : UserControl
    {
        public event KissinUDelegate onKissinU;//用Event關鍵字來申明一個事件
        public KissinUButton()
        {
            InitializeComponent();
        }
        private int i = 0;

        private void button1_Click(object sender, EventArgs e)
        {
            i++;
            if (i==2)
            {
                if (onKissinU!=null)
                {
                    onKissinU();
                }
            }
        }
    }
}
複製代碼

界面:

複製代碼
 public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
            kissinUButton1.onKissinU += new KissinUDelegate(kissinUButton1_onKissinU);
        }

        void kissinUButton1_onKissinU()
        {
            MessageBox.Show("點我我就吻吻你");
        }

        private void Form1_Load(object sender, EventArgs e)
        {

        }
    }
複製代碼

通過反編譯看看:

點擊進入看看:

委託和事件沒有可比性,因爲委託是類型,事件是對象,上面說的是委託的對象(用委託方式實現的事件)和(標準的event方式實現)事件的區別。事件的內部是用委託實現的。 因爲對於事件來講,外部只能“註冊自己+=、註銷自己-=”,外界不可以註銷其他的註冊者,外界不可以主動觸發事件,因此如果用Delegate就沒法進行上面的控制,因此誕生了事件這種語法。add、remove。

事件是用來閹割委託實例的。事件只能add、remove自己,不能賦值。事件只能+=、-=,不能=、不能外部觸發事件。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章