程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> 關於.NET >> CLR筆記:10.事件

CLR筆記:10.事件

編輯:關於.NET

事件也是方法。

定義一個事件成員意味著類型具有三種能力:

*類型的靜態方法/實例方法可以訂閱類型事件

*類型的靜態方法/實例方法可以注銷類型事件

*事件發生時通知已訂閱事件的方法

.NET2.0的事件仍然是基於Win32的,只不過使用了Observer模式來實現,同時建立在Delegate機制之 上。

事件的設計步驟如下(基本上是Observer的實現步驟):

10.1 設計一個對外提供事件的類型

1.定義EventArgs或子類,用於存放附加信息:

定義一個類,繼承於EventArgs,以EventArgs結束,包含一組私有字段以及相應的只讀公共屬性。

    public class NewMailEventArgs : EventArgs

    {

        private string from;


        public string From

        {

            get { return from; }

        }

    }

這裡,EventArgs基類在FCL中是這個樣子的:

     [Serializable]

    [ComVisible(true)]

    public class EventArgs

    {

        // Summary:

        //     表示沒有事件數據的事件。

        public static readonly EventArgs Empty;


        public EventArgs();

    }

大多數事件沒有附加數據,那麼就不用定義任何私有字段和屬性,直接使用EventArgs基類作為參數。

2.定義事件成員:

    class MailManager

    {

        public event EventHandler<NewMailEventArgs> NewMail;

    }

這條語句等價於:

  public delegate void EventHandler<TVEventArgs>(Object sender, TVEventArgs e) 

where TVEventArgs: NewMailEventArgs;

所以方法原型相應為 void MethodName(Object sender, NewMailEventArgs e)

這裡,第一個參數sender類型是Object,因為要兼容所有類型,所以提供一個最廣泛的基類型。

第二個參數名始終是e,而且派生於EventArgs,保持了對Observer模式的一致性,所有人(包括 VS2005)都會調用這個e

事件方法要求都為void,即不允許有回調值,從而事件鏈易於操作。

3.定義引發事件的方法——負責通知訂閱事件的對象:

這是一個protected的虛方法,並接受EventArgs或其子類的參數。

這個虛方法可以由派生類重寫,以添加新的功能;不重寫也可以,因為基本上已經可以使用了

    class MailManager

    {

        protected virtual void OnNewMail(NewMailEventArgs e)

        {

            EventHandler<NewMailEventArgs> temp = NewMail;


            if (temp != null)

                temp(this, e);

        }

    }

這裡,使用臨時變量temp,是為了防止可能存在的線程同步問題。

4.定義一個激發事件的方法

將輸入轉換成EventArgs或其子類的對象,然後激發事件

    internal class MailManager

    {

        public void SimulateNewMail(String from, String to, String subject)

        { 

            NewMailEventArgs e = new NewMailEventArgs(from, to, subject);

            OnNewMail(e);

        }

    }

10.3    設計訂閱者的類,使用事件

在ctor中訂閱事件,綁定FaxMsg回調方法,在Unregister方法中注銷事件

提供回調方法FaxMsg,當事件激發時自動調用

    internal sealed class Fax

    {

        public Fax(MailManager mm)

        {

            mm.NewMail += FaxMsg;

        }


        private void FaxMsg(Object sender, NewMailEventArgs e)

        {

            Console.WriteLine("Fax: {0}, {1}, {2}", e.From, e.To, e.Subject);

        }


        public void Unregister(MailManager mm)

        {

            mm.NewMail -= FaxMsg;

        }

    }

注意:使用+=和-=操作符,而不能顯示使用add/remove方法

事件注銷的意義:只要有一個對象還有一個方法仍然訂閱事件,該對象就不會被垃圾收集

IDispose接口的Dispose方法,注銷所有事件。

FaxMsg方法的sender參數為MailMessager對象,可以使用sender訪問MailMessager的對象成員,

補充:在Main函數中實現:

   public static void Main() {


      MailManager mm = new MailManager();


      //注冊pager和fax

      Fax fax = new Fax(mm);

      Pager pager = new Pager(mm);


      //通知pager和fax

      mm.SimulateNewMail("Jeffrey", "Kristin", "I Love You!");


      //注銷fax,只剩下pager

      fax.Unregister(mm);

    

      //只通知pager

      mm.SimulateNewMail("Jeffrey", "Mom & Dad", "Happy Birthday.");

   }

10.2    事件機制

對於public event EventHandler<NewMailEventArgs> NewMail;

C#編譯時,相應為

            //一個初始化為null的私有委托字段:    

            private EventHandler<NewMailEventArgs> NewMail = null;


            //一個訂閱事件的公共方法:

            [MethodImpl(MethodImplOptions.Synchronized)]

            public void add_NewMail(EventHandler<NewMailEventArgs> value)

            {

                NewMail = (EventHandler<NewMailEventArgs>)

Delegate.Combine(NewMail, value);

            }


            //一個注銷事件的公共方法:

            [MethodImpl(MethodImplOptions.Synchronized)]

            public void remove_NewMail(EventHandler<NewMailEventArgs> 

value)

            {

                NewMail = (EventHandler<NewMailEventArgs>)

Delegate.Remove(NewMail, value);

            }

注:在IL中也是3個成員:一個私有字段,兩個公有方法

如果將event聲明為protected,則兩個方法也相應為protected

event也可以是static或virtual,則兩個方法也相應為static或virtual

10.4 事件與線程安全

在上面的實例中,System.Runtime.CompilerServices命名空間下,自定義屬性[MethodImpl (MethodImplOptions.Synchronized)]保證了事件的線程同步。

但是這樣的同步會有問題。

對於實例事件,CLR使用自身對象作為線程同步鎖;

對於靜態事件,CLR使用類型對象作為線程同步鎖。

但是線程同步指導方針指出,方法永遠不要在對象本身或類型對象上加鎖,否則這個鎖對外公開,會 導致其它線程死鎖

沒有好的辦法保證值類型的實例事件成員是線程安全的,因為C#不會為其add/remove生成[MethodImpl

(MethodImplOptions.Synchronized)];

值類型的靜態事件成員肯定是線程安全的。

10.5 顯示控制事件的訂閱與注銷

即顯示的實現add和remove訪問器方法:

建立一個臨時委托變量m_NewMail與相應的屬性,代替原先的事件成員NewMail,

新建一個作為線程同步鎖的私有實例字段m_eventLock

主要改動如下:

    class MailManager

    {

        private EventHandler<NewMailEventArgs> m_NewMail;
        public event EventHandler<NewMailEventArgs> NewMail

        {
            add 
            {

                lock (m_eventLock)
                {

                    m_NewMail += value;
                }
            }
            remove
            {

                lock (m_eventLock)
                {
                    m_NewMail -= value;
                }
            }
        }

    }

注意,C#不能分辨add/remove方法是由編譯器自動創建的,還是程序員顯示實現的,所以仍可以使用 +=和-=這兩個操作符處理事件。

10.6 多事件模型

System.Windows.Forms.Control類型有70多個事件,不可能用上述方法實現,會造成未使用事件對內 存的浪費。

解決辦法:使用注冊工廠,建立事件池。具體見設計模式。

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