C# シリーズの記事イベント

PHPz
リリース: 2017-03-12 15:43:26
オリジナル
1136 人が閲覧しました

ファイルに含まれる内容:

  • 公開するための設計 イベント

  • コンパイラーによるイベントの実装方法

  • リスニングイベントの型を設計

  • イベントを明示的に実装

イベント: イベントメンバーを使用して タイプを定義すると、そのタイプが他の オブジェクトに特定の何かが起こったことを通知できるようになります。

CLR イベント モデル は、オブジェクトがサブスクライブする通知を受け取るためのコールバック メソッドを呼び出す 安全な 方法の一種であるデリゲートに基づいています。

イベントメンバーの型要件は、次の機能を提供できるように定義されています:

  1. メソッドはイベントへのアテンションを登録できます

  2. メソッドはイベントへのアテンションの登録を解除できます

  3. いつイベントが発生すると、登録されたメソッドが通知を受け取る

この記事では例としてメールアプリケーションプログラムを使用します。電子メールが到着すると、ユーザーはそれを処理するために FAX 機またはポケベルに転送することを希望します。まず、受信メールを受信するための MainlManager タイプを設計し、NewMain イベントを公開します。他のタイプのオブジェクト (ファックスまたはポケベル) は、イベントへの関心を登録します。このイベントは、MailManager が新しい電子メールを受信すると発生し、電子メールが登録された各オブジェクトに配信されます。各オブジェクトは電子メールを処理する独自の方法を持っています。

1.1 イベントを公開するタイプを設計する

ステップ 1: イベント通知受信者に送信する必要があるすべての追加情報を保持するタイプを定義する

このタイプには通常、一連のプライベート フィールドといくつかの読み取り専用フィールドが含まれますこれらのフィールドを公開するためのフィールド Public Properties


 1 class NewMailEventArgs:EventArgs 2     { 3         private readonly string m_from, m_to, m_subject; 4         public NewMailEventArgs(string from,string to,string subject) 5         { 6             m_from = from; 7             m_to = to; 8             m_subject = subject; 9         }10         public string From { get { return m_from; } }11         public string To { get{ return m_to; } }12         public string Subject { get { return m_subject; } }13     }
ログイン後にコピー

ステップ 2: イベントメンバーを定義します


class MailManager
    {        public event EventHandler<NewMailEventArgs> NewMail;
    }
ログイン後にコピー

ここで、NewMail はイベント名です。イベント メンバーのタイプは EventHandler であり、イベント通知のすべての受信者がそのデリゲート タイプと一致するプロトタイプを持つコールバック メソッドを提供する必要があることを示します。ジェネリック System.EventHandler デリゲート型は次のように定義されているため:

public delegate void EventHandler(Object sender,TEventArgs e);

したがって、メソッド プロトタイプは次の形式である必要があります: void MethodName (Object sender , NewMailEventArgs e); イベント モード がすべての イベント処理 プログラムの戻り値の型を void にする必要がある理由は、イベントが発生した後にいくつかのコールバック メソッドが呼び出される可能性があるためです。すべてのメソッドの戻り値を取得して void を返す コールバック メソッドは戻り値を持つことができません。

ステップ 3: イベントをトリガーして、登録されたオブジェクトにイベントを通知するメソッドを定義します


 1         /// <summary> 2         /// 定义负责引发事件的方法来通知事件的登记对象,该方法定义在MailManager中 3         /// 如果类是密封的,该方法要声明为私有和非虚 4         /// </summary> 5         /// <param name="e"></param> 6         protected virtual void OnNewMail(NewMailEventArgs e) 7         { 8             //出于线程安全考虑,现在将委托字段的引用复制到一个临时变量中 9             EventHandler<NewMailEventArgs> temp = Volatile.Read(ref NewMail);10             if(temp!=null)11             {12                 temp(this, e);13             }14         }
ログイン後にコピー

上記のメソッドは、主に次の 2 つの状況を考慮して、スレッドの安全性を確保するために Volatile.Read() メソッドを使用します。

1. NewMail!=null を直接判断しますが、NewMail を呼び出す前に、別のスレッドがデリゲート チェーンからデリゲートを削除して空にし、(NullReferenceException) 例外が発生した可能性があります。

2. Volatile を使用せずに一時変数に保存することも理論的には可能ですが、コンパイラーがコードを最適化して一時変数を削除すると、最初のケースと同じになります。

Volatile.Read を使用すると、この呼び出しが発生したときに NewMail に強制的に読み取りを行わせるため、参照を temp 変数にコピーする必要があります。これは完璧な解決策です。しかし、これは単一のスレッドでは起こりません

ステップ 4 入力を予期されるイベントに変換するメソッドを定義します


1 public void SimulateNewMail(string from,string to,string subject)2         {3             //构造一个对象来容纳想传给通知接收者的信息4             NewMailEventArgs e = new NewMailEventArgs(from, to, subject);5             //调用虚方法通知对象事件已反生6             //如果没有类型重写该方法7             //我们的对象将通知事件的所有登记对象8             OnNewMail(e);9         }
ログイン後にコピー

このメソッドは、新しい電子メールが MailManager に到着したことを示します。

1.2 コンパイラによるイベントの実装方法

MailManager クラスでは、イベント メンバー自体を 1 つの文で定義します。 public event EventHandler この例では、イベント NewMail が次のように宣言されているため、追加メソッドと削除メソッドのアクセシビリティは両方とも public です。イベントのアクセシビリティによって、どのコードがイベントを登録し、イベントへの注意を取り消すことができるかが決まります。ただし、いずれの場合も、型自体のみが上記のデリゲート フィールド NewMail にアクセスできます。上記の生成されたコードに加えて、コンパイラはマネージド アセンブリのメタデータにイベント定義エントリも生成します。いくつかのフラグと基本的なデリゲート タイプが含まれています。 CLR 自体はこのメタデータ情報を使用しません。ランタイムにはアクセサー メソッドのみが必要です。

1.3 设计侦听事件的类型

如何定义一个类型来使用另一个类型提供的事件。以Fax类型为例:


internal class Fax
    {        public Fax(MailManager mm)
        {            //向MailManager的NewMail事件登记我们的回调方法
            mm.NewMail += FaxMsg;
        }        //新邮件到达,MailManager将调用这个方法        //sender表示MailManager对象,便于将信息回传给它        //e表示MailManager对象想传给我们的附加事件信息
        private void FaxMsg(object sender, NewMailEventArgs e)
        {
            Console.WriteLine("Fax 的消息from:{0} to:{1} subject:{2}", e.From, e.To, e.Subject);
        }        /// <summary>
        /// 注销        /// </summary>
        /// <param name="mm"></param>
        public void Unregister(MailManager mm)
        {
            mm.NewMail -= FaxMsg;
        }
    }
ログイン後にコピー

 电子邮件应用程序初始化时首先构造MailManager对象,并将对该对象的引用保存到变量中。然后构造Fax对象,并将MailManager对象引用作为实参传递。在Fax构造器中,使用+=登记对NewMail事件的关注。

1.4 显式实现事件

对于System.Windows.Forms.Control类型定义了大约70个事件。每个从Control派生类型创建对象都要浪费大量内存,而大多数我们只关心少数几个事件。如何通过显式实现事件来高效的实现提供了大量事件的类思路如下:

定义事件时:公开事件的每个对象都要维护一个集合(如字典)。集合将某种事件标识符作为健,将委托列表作为值。新对象构造时集合也是空白。登记对一个事件的关注会在集合中查找事件的标识符。如果事件标识符存在,新委托就和这个事件的委托列表合并,否则就添加事件标识符和委托。   

引发事件时:对象引发事件会在集合中查找事件的标识符,如果没有说明没有对象登记对这个事件的关注,所以也没委托需要回调。否则就调用与它关联的委托列表。


 1  public sealed class EventKey { } 2     public sealed class EventSet 3     { 4         //定义私有字典 5         private readonly Dictionary<EventKey, Delegate> m_events = 6             new Dictionary<EventKey, Delegate>(); 7         /// <summary> 8         /// 不存在添加,存在则和现有EventKey合并 9         /// </summary>       10         public void Add(EventKey eventKey,Delegate handler)11         {12             //确保操作唯一13             Monitor.Enter(m_events);14             Delegate d;15             //根据健获取值16             m_events.TryGetValue(eventKey, out d);17             //添加或合并18             m_events[eventKey] = Delegate.Combine(d, handler);19             Monitor.Exit(m_events);20         }21         /// <summary>22         /// 删除委托,在删除最后一个委托时还需删除字典中EventKey->Delegate23         /// </summary>       24         public void Remove(EventKey eventKey,Delegate handler)25         {26             Monitor.Enter(m_events);27             Delegate d;28             //TryGetValue确保在尝试从集合中删除不存在的EventKey时不会抛出异常29             if (m_events.TryGetValue(eventKey,out d))30             {31                 d = Delegate.Remove(d, handler);32                 if(d!=null)33                 {34                     //如果还有委托,就设置新的头部35                     m_events[eventKey] = d;36                 }37                 else38                 {39                     m_events.Remove(eventKey);40                 }41             }42             Monitor.Exit(m_events);43         }44         /// <summary>45         /// 为指定的EventKey引发事件46         /// </summary>       47         public void Raise(EventKey eventKey,Object sender,EventArgs e)48         {49             Delegate d;50             Monitor.Enter(m_events);51             m_events.TryGetValue(eventKey, out d);52             Monitor.Exit(m_events);53             if(d!=null)54             {55                 //利用DynamicInvoke,会向调用的回调方法查证参数的类型安全,56                 //并调用方法,如果存在类型不匹配,就抛异常57                 d.DynamicInvoke(new Object[] { sender, e });58             }59         }60     }
ログイン後にコピー

接下来定义类来使用EventSet


 1 public class FooEventArgs : EventArgs { } 2     public class TypeWithLotsOfEvents 3     { 4         //用于管理一组"事件/委托" 5         private readonly EventSet m_eventSet = new EventSet(); 6         //受保护的属性使派生类型能访问集合 7         protected EventSet EventSet { get { return m_eventSet; } } 8         //构造一个静态只读对象来标识这个事件 9         //每个对象都有自己的哈希码,以便在对象的集合中查找这个事件的委托链表10         protected static readonly EventKey s_fooEventKey = new EventKey();11         //定义事件访问器方法,用于在集合中增删委托12         public event EventHandler<FooEventArgs> Foo13         {14             add { m_eventSet.Add(s_fooEventKey, value); }15             remove { m_eventSet.Remove(s_fooEventKey, value); }16         }17         //为这个事件定义受保护的虚方法18         protected virtual void OnFoo(FooEventArgs e)19         {20             m_eventSet.Raise(s_fooEventKey, this, e);21         }22         //定义将输入转换成这个事件的方法23         public void SimulateFoo() { OnFoo(new FooEventArgs()); }24     }
ログイン後にコピー

如何使用TypeWithLotsOfEvent,只需按照标准的语法向事件登记即可


 1 static void Main(string[] args) 2         {           
 3             TypeWithLotsOfEvents twle = new TypeWithLotsOfEvents(); 4             twle.Foo += HandleFooEvent; 5             twle.SimulateFoo(); 6             Console.Read(); 7         } 8  9         private static void HandleFooEvent(object sender, FooEventArgs e)10         {11             Console.WriteLine("成功");12         }
ログイン後にコピー

 

以上がC# シリーズの記事イベントの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

関連ラベル:
ソース:php.cn
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
最新の問題
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート