C#代理與事件


C#中的代理與事件
2008年09月02日 星期二 22:15

 

(原創:http://hi.baidu.com/grayworm
代理與事件是DotNet的兩個重要概念,但好多朋友感覺沒有這兩個概念照樣能夠進行常規的DotNet開發。其實深入理解這兩個概念對DotNet研究非常重要,尤其在WCSF的開發時,如果不理解這兩個概念那你就很難把View層和Presenter層的代碼進行分離。
以前從網上看過幾篇這方面的文章,總感覺有點晦澀難懂,希望這篇文章能對大家理解代理與事件有所幫助。
一、代理
首先我們要弄清代理是個什么東西。別讓一串翻譯過來的概念把大家搞暈了頭。
有的文章把代理稱委托、代表等,其實它們是一個東西,英文表述都是“Delegate”。由於沒有一本權威的書來規范這個概念,所以現在網上對它的稱謂不一。本文我將以“代理”來稱謂Delegate。
代理是什么呢?我認為“代理就是用來定義指向方法的引用”。下面我們就通過類來理解代理。

如:
Ren r = new Ren("車延祿");
上面的代碼,就是使用Ren這個類定義了一個指“車延祿”這個對象實例的一個引用。
也可以這樣理解:用Ren類定義的變量r,指向一個“車延祿”對象的實例。
類所定義的變量指向的是一個對象,代理所定義的變量指向的是個方法,當然這個方法可以是靜態方法也可以是實例方法。對代理引用的調用就是對代理所指向方法的調用。

1.代理聲明的語法:
   [public/private] delegate <返回值類型> <代理名稱>(<參數列表>);

  
  [public/private]:訪問修飾符。
   delegate:代理聲明關鍵定,相當於類聲明的Class關鍵定
   <返回值類型>:代理所指向的方法的返回值類型
   <代理名稱>:代理類型的名稱
   <參數列表>:代理所的指向的方法的參數列表。
  
   要想使代理對象能夠指向一個方法,那這個方法的要滿足兩個條件
  a.方法返回類型要與delegate聲明中的“返回值類型”一致。
   b.方法的形參形表要與delegate聲明中的“參數列表”一致。
  
   如:
  delegate void MyDelegate(string str,int index);
   該代理聲明表示:該代理指向的方法必須是返回空類型,並且擁有兩個參數,第一個是字符串類型,第二個是整型。

2.代理“實例化”:
   代理聲明相當於類的定義。有了類的定義后我們要還需生成這個類的對象;同樣有了代理的聲明我們還需要“實例化”代理
  
   如:MyDelegate md = new MyDelegate(Show);
  
   這里的md就是代理變量。在代理的“實例化”的時候必須在構造函數中傳入一個方法名。這個方法名就是該代理指向的方法,當然該方法的返回值類型與參數類型一定要與代理的聲明一致。
  
   Show方法定義如下:
  public static void Show(string str, int index)
    {
        Console.WriteLine("Show"+str+index.ToString());
    }

3.代理的調用:
  md("hello world",22);
  此時調用的就是md這個代理變量所指向的Show方法。

4.例子:
delegate void MyDelegate(string str,int index);
    class Test
    {
        public static void Show(string str, int index)
        {
            Console.WriteLine("Show"+str+index.ToString());
        }
        public static void Main(string[] args)
        {
            MyDelegate md = new MyDelegate(Show);
            md("hello world",22);
        }
    }

5.代理的應用:
  代理的主要應用就是在DotNet中的事件處理,所以要想研究事件我們必須要理解代理的概念。有的文章使用代理進行冒泡排序,我感覺這沒必要,因為不用代理我也可以排序,更況且在C#語法中也不需要我們手動編寫冒泡排序代碼。
   關於代理,大家要理解代理是個什么東西,並且能夠寫一個簡單的代理示例就可以了。

二、多播代理
上面我們講的代理是一個代理對象指向一個方法,在調用該代理對象的時候就會調用它所指向的方法。多播代理就是為一個代理掛接上多個方法,當執行該代理的時候就會依次執行該代理上掛接的方法。

1.多播代理的聲明與上面講得基本上一樣:
  
[public/private] delegate void <代理名稱>(<參數列表>);
  
   只有一點不一樣的就是,多播代理所指向的方法應當是void類型

2.多播代理“實例化”
   多播代理“實例化”與上面講得一樣,在此不多說了。
  
   如:MyDelegate md = new MyDelegate(Show);
  
3.多播代理掛接多個方法。
  多播代理可以使用 += 運算符掛接多個方法,也可以使用 -= 運算符從掛接列表中刪除相應的掛接方法。
  
   如:
delegate void MyDelegate(string str,int index);
    class Test
    {
        public static void Show(string str, int index)
        {
            Console.WriteLine("Show"+str+index.ToString());
        }
        public static void TestInt(string str, int index)
        {
            Console.WriteLine("Testint");
        }
        public static void Main2(string[] args)
        {
            MyDelegate md = new MyDelegate(Show);
            md += new MyDelegate(TestInt);
            md("hello world",22);
        }
    }
    在上面這個例子當中有兩個方法(Show和TestInt)符合MyDelegate代理的簽名,如果要把這兩個方法掛接到我們一個代理變量上去的話,就得用 += 運算符了。
     MyDelegate md = new MyDelegate(Show);
    md += new MyDelegate(TestInt);
   這里的md代理變量上先掛接了Show方法,再掛接TestInt方法。當執行md("hello world",22)的時候會先調用Show方法,再調用TestInt方法。
   事件本身就是一種多播代理
  
三、事件
C#中的事件就是代理的一個變量。它和屬性、方法一樣,都是類的成員。只不過事件是指向一個方法,當事件被觸發時,就會執行對象的相關方法。
事件的這種對方法的引用並不是寫死在代碼里面的,而是可以進行更改的。辟如:我們在DotNet中按鈕的OnClick事件,它可以指向符合OnClick事件簽名的任何一個方法。
1.事件的定義使用event關鍵字:
  public event CryHandler DuckCryEvent;
  
   其中的CryHandler是一個delegate。從上面的代碼我們可以看出來:事件就是一個代理類型的變量。
  private delegate void CryHandler();

2.指定事件處理程序:
   指定事件處理程序就是為事件掛接方法的過程。
  DuckCryEvent +=new CryHandler(Cry);
  public void Cry()
    {
        Console.WriteLine("我是一只小鴨,呀依呀依呀....");
    }
   
3.執行事件
  執行事件就是調用事件所指向方法的過程。一般對事的執行代碼寫在相應的方法或屬性中,如果方法或屬性被調用時就觸發事件。
  public void BeShaked()
    {
        DuckCryEvent();
    }
   
4.完整的例子:
  //事件用到的代理,以般以×××Handler的格式進行命名
   private delegate void CryHandler();
   //玩具小鴨的類
    class Duck
    {
       //定義小鴨的唱歌事件
        public event CryHandler DuckCryEvent;
        public Duck()
        {
           //把小鴨唱歌的事件掛接到Cry方法上
            DuckCryEvent +=new CryHandler(Cry);
        }
        //小鴨唱歌事件對應的處理方法
        public void Cry()
        {
            Console.WriteLine("我是一只小鴨,呀呀呀....");
        }
        //小鴨被搖動
        public void BeShaked()
        {
           //執行事件
            DuckCryEvent();
        }
    }
    class Class2
    {
        public static void Main3(string[] args)
        {
           //買一只小鴨
            Duck d = new Duck();
            //搖一搖小鴨,它就會調觸發小鴨的Cry事件,小鴨就會唱歌
            d.BeShaked();
        }
    }
   
四、事件在ASP.NET中的舉例
頁面源視圖代碼
  <form id="form1" runat="server">
    <asp:Button ID="Button1" runat="server" onclick="Haha" Text="Button" />
   </form>
這個按鈕默認執行的是頁面對象HaHa方法。
頁面后置代碼
  protected void XiXi(object sender, EventArgs e)
    {
        Response.Write("XiXi<br>");
    }
    protected void Button1_Click(object sender, EventArgs e)
    {
        Response.Write("Button1Click<br>");
    }
    protected void Haha(object sender, EventArgs e)
    {
        Response.Write("HaHa<br>");
    }
上面的這三個方法都是符合按鈕的OnClick事件簽名格式
因此我們可以讓按鈕點擊的時候,把這三個方法都執行一遍。實現方式當然就是多播代理了。
  protected override void OnInit(EventArgs e)
    {
        base.OnInit(e);
        Button1.Click += new EventHandler(Button1_Click);
        Button1.Click += new EventHandler(XiXi);
    }
   
五、綜合案例分析
1.面試題:晚上貓大叫一聲,主人被驚醒,老鼠被下跑。用C#實現這個過程。
   這個題目中一共有三個對象:貓、主人和老鼠。這三個對象之間即不屬於Is a...的關系也不屬於Has a...的關系,但三者又有相互聯系。
  
  不能把主人和老鼠作為貓的成員變量出現,因為貓對象並不擁有主人和老鼠這兩個對象。
   不能在貓任何方法中實例化主人和老鼠。因為貓的任何動作都不會動態產生主人和老鼠。
   不能在貓的Cry方法中傳入主人對象和老鼠對象,因為貓大叫不是為了叫醒主人或嚇跑老鼠,可能是貓正在說夢話。所以把主人和老鼠對象傳遞給貓的Cry()方法也不合情理。
  
   通過上面分析我們看到,即不能把主人和老鼠作為貓的成員變量,又不能動態產生或接收主人、老鼠對象,那這三者之間如何建立關系呢?
   當然是是我們剛講過的事件。
  
  主人類中有一個“驚醒”的方法;貓的類中有一個“大叫”的方法;老鼠類中有一個“逃跑”方法。並且貓中還有一個“大叫”事件,這個事件是多播代理,它依次調用主人的“驚醒”方法和老鼠的“逃跑”方法。
   下面是具體代碼:
  //定義貓大叫事件的代理
   public delegate void AlertHandler();
   //主人類
    class Human
    {
       //主人被驚醒的方法
        public void Wake()
        {
            Console.WriteLine("主人:死貓別叫");
        }
    }
    //老鼠類
    class Mouse
    {
       //老鼠被嚇包的方法
        public void Run()
        {
            Console.WriteLine("老鼠:有危險,快撤!");
        }
    }
    //貓類
    class Cat
    {
       //貓大叫事件
        public event AlertHandler AlertEvent;
        public Cat()
        {
           //貓大叫時執行Cry方法
            AlertEvent +=new AlertHandler(Cry);
        }
        //貓大叫事件執行的處理程序
        public void Alert()
        {
            Console.WriteLine("貓:嗚哇...嗚哇...");
        }
        //貓大叫的方法
        public void Cry()
        {
           //觸發貓大叫的事件
            AlertEvent();
        }
    }
    //房子類
    class House
    {
       //房子里有一只老鼠、一只貓和主人
        public Mouse mouse = new Mouse();
        public Cat cat = new Cat();
        public Human human = new Human();
        //由於在一個房子里,貓大叫的事件會引發老鼠“逃跑”和主人“驚醒”
        //所以在這里把老鼠“逃跑”和主人“驚醒”兩個方法掛接到貓大叫的事件上。
        public House()
        {
            cat.AlertEvent +=new AlertHandler(mouse.Run);
            cat.AlertEvent +=new AlertHandler(human.Wake);
        }

    }
    //客戶程序
    class Program
    {
        static void Main(string[] args)
        {
           //有一間房子
            House h = new House();
            //貓大叫
            h.cat.Cry();
        }
    }
    運行效果:


    《圖1》(http://hi.baidu.com/grayworm)
   
2.鬧鍾:
   //鬧鍾事件數據
  class TimeArgs : EventArgs
    {
       //鬧鍾聲明
        private string _Message;
        //鬧鈴時間
        private int _RingTime;
        //默認鬧鈴
        public TimeArgs()
        {
            _Message = "滴滴...滴滴...";
            _RingTime = 0;
        }
        public TimeArgs(string message, int ringtime)
        {
            this._Message = message;
            this._RingTime = ringtime;
        }
        //設置鬧鈴聲音
        public string Message
        {
            get
            {
                return _Message;
            }
            set
            {
                _Message = value;
            }
        }
        //設置鬧鈴時間
        public int RingTime
        {
            get
            {
                return _RingTime;
            }
            set
            {
                _RingTime = value;
            }
        }
    }
   
    //鬧鈴代理
    delegate void RingHandler(object sender,TimeArgs e);
    class Clock
    {
       //鬧鈴事件
        private event RingHandler RingEvent;
        public Clock()
        {
           //把鬧鈴事件掛接到方法上
            RingEvent += new RingHandler(Clock_RingEvent);
        }
     //鬧鈴事件處理程序
        private void Clock_RingEvent(object sender, TimeArgs e)
        {
            Console.WriteLine(e.Message);
        }
        //鬧鈴時間監測
        private void ReadyToRing(TimeArgs e)
        {
            while (true)
            {
                System.Threading.Thread.Sleep(1000);
                if (DateTime.Now.Hour == e.RingTime)
                {
                    RingEvent(this, e);
                }
            }
        }
        //設置鬧鈴
        public void SetTime(TimeArgs e)
        {
            ReadyToRing(e);
        }
    }
    class Class3
    {
        public static void Main(string[] args)
        {
            Clock clock = new Clock();
            TimeArgs arg = new TimeArgs("賴蟲起床......", 21);
            clock.SetTime(arg);
        }
    }


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM