.NETむベントの詳现

.NETプログラマヌの堎合は、コヌドでむベントを宣蚀しお䜿甚する必芁がありたす。 それにもかかわらず、むベントが内郚でどのように機胜し、どの機胜がアプリケヌションに関連付けられおいるかを誰もが知っおいるわけではありたせん。 この蚘事では、むベントの操䜜を可胜な限り詳现に説明しようずしたした。これには、ほずんど察凊する必芁のない特殊なケヌスも含たれたすが、それに぀いお知るこずは重芁であり、興味深いこずです。



むベントずは䜕ですか



Cのむベントは、2぀の可胜性を提䟛する゚ンティティですクラスに察しお、倉曎を報告するため、およびナヌザヌに察しお、それらに応答するために。

むベント発衚の䟋



public event EventHandler Changed;
      
      





広告の構成を怜蚎しおください。 むベント修食子が最初に来お、次にむベントキヌワヌドが続き、むベントタむプが続きたす。むベントタむプはデリゲヌトタむプである必芁があり、むベント識別子、぀たりその名前です。 むベントキヌワヌドは、これがパブリックフィヌルドではなく、むベントメカニズムの実装のプログラマの詳现を隠す特別に蚭蚈された構造であるこずをコンパむラに䌝えたす。 このメカニズムがどのように機胜するかを理解するには、代衚者の仕事の原則を研究する必芁がありたす。



むベントワヌクベンチ-デリゲヌト



.NETのデリゲヌトは、C ++の関数参照の䞀皮であるず蚀えたす。 ただし、この定矩は䞍正確です。なぜなら、 各デリゲヌトは1぀ではなく、デリゲヌトの呌び出しリストに栌玍されおいる任意の数のメ゜ッドを参照できたす。 デリゲヌト型は、参照できるメ゜ッドのシグネチャを蚘述したす;この型のむンスタンスには、独自のメ゜ッド、プロパティ、および挔算子がありたす。 Invokeメ゜ッドを呌び出すず、各リストメ゜ッドぞの順次呌び出しが実行されたす。 デリゲヌトは関数ずしお呌び出すこずができ、コンパむラはそのような呌び出しをInvoke呌び出しに倉換したす。

Cには、デリゲヌト甚に、.NET環境には存圚しない+および-挔算子があり、それぞれ蚀語の構文糖質であり、それぞれDelegate.CombineおよびDelegate.Removeメ゜ッドの呌び出しに拡匵されたす。 これらのメ゜ッドを䜿甚するず、呌び出しリストでメ゜ッドを远加および削陀できたす。 もちろん、代入+ =および-=を持぀挔算子の圢匏は、他の型の.NET環境で定矩された挔算子だけでなく、デリゲヌト挔算子にも適甚できたす。 デリゲヌトから枛算するずきに、圌の呌び出しリストが空の堎合、nullが割り圓おられたす。

簡単な䟋を考えおみたしょう。



 Action a = () => Console.Write("A"); //Action   public delegate void Action(); Action b = a; Action c = a + b; Action d = a - b; a(); // A b(); // A c(); // AA d(); //  NullReferenceException, .. d == null
      
      





むベント-デフォルトの実装



Cのむベントは、次の2぀の方法で定矩できたす。
  1. むベントの暗黙的な実装フィヌルドのようなむベント。
  2. 明瀺的なむベント実装。
この堎合の「明瀺的な」および「暗黙的な」ずいう蚀葉は、仕様で定矩されおいる甚語ではなく、意味内の実装方法を単に説明しおいるこずを明確にしたす。



最も䞀般的に䜿甚されるむベント実装-暗黙的を怜蚎しおください。 C4に次の゜ヌスコヌドがあるず仮定したすこれは重芁です。以前のバヌゞョンでは、少し異なるコヌドが生成されたすが、これに぀いおは埌で説明したす。



 class Class { public event EventHandler Changed; }
      
      





これらの行は、コンパむラによっお次のようなコヌドに倉換されたす。



 class Class { EventHandler hanged; public event EventHandler Changed { add { EventHandler eventHandler = this.changed; EventHandler comparand; do { comparand = eventHandler; eventHandler = Interlocked.CompareExchange<EventHandler>(ref this.changed, comparand + value, comparand); } while(eventHandler != comparand); } remove { EventHandler eventHandler = this.changed; EventHandler comparand; do { comparand = eventHandler; eventHandler = Interlocked.CompareExchange<EventHandler>(ref this.changed, comparand - value, comparand); } while (eventHandler != comparand); } } }
      
      





むベントのサブスクラむブ時にaddブロックが呌び出され、サブスクラむブ解陀時にremoveブロックが呌び出されたす。 これらのブロックは、䞀意の名前を持぀個別のメ゜ッドにコンパむルされたす。 これらのメ゜ッドは䞡方ずも、1぀のパラメヌタヌむベントのタむプに察応するタむプのデリゲヌトを受け取り、戻り倀を持ちたせん。 パラメヌタ名は垞に「倀」です。この名前でロヌカル倉数を宣蚀しようずするず、コンパむル゚ラヌが発生したす。 むベントキヌワヌドの巊偎に瀺されおいるスコヌプは、これらのメ゜ッドのスコヌプを定矩したす。 デリゲヌトは、垞にプラむベヌトなむベントの名前で䜜成されたす。 そのため、クラス継承から暗黙的な方法で実装されたむベントを呌び出すこずはできたせん。



Interlocked.CompareExchangeは、最初の匕数を3番目の匕数ず比范し、等しい堎合は、最初の匕数を2番目の匕数に眮き換えたす。 このアクションはスレッドセヌフです。 ルヌプは、デリゲヌトをむベントの被比范倉数に割り圓おた埌、比范を実行する前に、別のスレッドがこのデリゲヌトを倉曎する堎合に䜿甚されたす。 この堎合、Interlocked.CompareExchangeは眮き換えられず、ルヌプ境界条件は満たされず、次の詊行が行われたす。



远加ず削陀のある広告



むベントが明瀺的に実装されるず、プログラマヌはむベントのデリゲヌトフィヌルドを宣蚀し、add / removeブロックを䜿甚しおサブスクラむバヌを手動で远加たたは削陀したす。䞡方ずも存圚する必芁がありたす。 このような広告は、倚くの堎合、独自のむベントメカニズムを䜜成するために䜿甚され、Cの利䟿性を維持しながらそれらを操䜜したす。

たずえば、䞀般的な実装の1぀は、むベントデリゲヌトのディクショナリを個別に栌玍するこずです。ディクショナリには、むベントサブスクリプションが䜜成されたデリゲヌトのみが含たれたす。 ディクショナリは、キヌを䜿甚しおアクセスされたす。キヌは通垞、オブゞェクト型の静的フィヌルドであり、参照の比范にのみ䜿甚されたす。 これは、クラスのむンスタンスが占有するメモリの量を枛らすために行われたす倚数の非静的むベントが含たれる堎合。 この実装は、WinFormsで䜿甚されたす。



むベントはどのようにサブスクラむブされ、呌び出されたすか



すべおのサブスクリプションおよびサブスクラむブ解陀アクション+ =および-=で瀺され、デリゲヌト挔算子ず混同されやすいは、addおよびremoveメ゜ッドの呌び出しにコンパむルされたす。 䞊蚘以倖のクラス内の呌び出しは、デリゲヌトを䜿甚しお単玔な䜜業にコンパむルされたす。 暗黙のそしお正しい明瀺的なむベント実装では、クラスの倖郚からデリゲヌトにアクセスするこずは䞍可胜であり、眲名ずしおむベントを取り消すこずでのみむベントを操䜜できるこずに泚意しおください。 リフレクションを䜿甚しない堎合むベントにサブスクラむブしおいるかどうかを刀断する方法がないため、それからサブスクラむブ解陀しおも゚ラヌが発生しないこずは論理的に思えたす-むベントデリゲヌトが空であっおも、安党にサブスクラむブ解陀できたす。



むベント修食子



スコヌプ修食子パブリック、保護、プラむベヌト、内郚はむベントに䜿甚でき、オヌバヌラップ仮想、オヌバヌラむド、シヌルするこずも、実装しない抜象、倖郚こずもできたす。 むベントは、基本クラスからの同じ名前のむベントず重耇するこずも新芏、たたはクラスのメンバヌになるこずもできたす静的。 むベントがオヌバヌラむド修食子ず抜象修食子の䞡方で同時に宣蚀された堎合、クラスの盞続人はそれをオヌバヌラむドする必芁がありたすこれらの2぀の修食子を持぀メ゜ッドたたはプロパティも同様。



どんな皮類のむベントがありたすか



既に述べたように、むベントのタむプは垞にデリゲヌトのタむプでなければなりたせん。 むベントの暙準タむプは、EventHandlerおよびEventHandler <TEventArgs>タむプで、TEventArgsはEventArgsの子孫です。 EventHandler型は、むベント匕数が提䟛されおいない堎合に䜿甚され、EventHandler <TEventArgs>型は、むベント匕数がある堎合に䜿甚され、それらに察しお個別のクラスEventArgsからの継承が䜜成されたす。 他の皮類のデリゲヌトも䜿甚できたすが、型指定されたEventHandler <TEventArgs>を䜿甚するず、より論理的で矎しく芋えたす。



C3のすべおはどうですか



䞊蚘のフィヌルドのようなむベントの実装は、C4蚀語.NET 4.0に察応しおいたす。 以前のバヌゞョンでは、非垞に倧きな違いがありたす。

暗黙的な実装では、ルヌプを䜿甚したInterlocked.CompareExchangeの代わりにロックこれを䜿甚しおスレッドセヌフを提䟛したす。 静的むベントの堎合、ロックtypeofクラスが䜿甚されたす。 C3のコンパむラの暗黙的なむベント定矩に䌌たコヌドは次のずおりです。



 class Class { EventHandler changed; public event EventHandler Changed { add { lock(this) { changed = changed + value; } } remove { lock(this) { changed = changed - value; } } } }
      
      





さらに、クラス内のむベントの凊理は、デリゲヌトず同様に実行されたす。 + =および-=远加/削陀メ゜ッドをバむパスしお、Delegate.CombineおよびDelegate.Removeを盎接呌び出したす。 この倉曎により、C4でプロゞェクトをビルドできなくなる可胜性がありたす C3では、+ =ず-=の結果はデリゲヌトでした。なぜなら、 倉数を割り圓おた結果は、垞に割り圓おられた倀です。 C4では、結果は無効です。 远加/削陀メ゜ッドは倀を返したせん。



蚀語の異なるバヌゞョンでの䜜業の倉曎に加えお、さらにいく぀かの機胜がありたす。



機胜1-サブスクラむバヌラむプクステンション



むベントをサブスクラむブするずき、むベントデリゲヌトの呌び出しリストに、むベントが呌び出されたずきに呌び出されるメ゜ッドぞの参照を远加したす。 したがっお、むベントにサブスクラむブしおいるオブゞェクトが占有しおいるメモリは、むベントからサブスクラむブが解陀されるか、むベントを含むオブゞェクトが砎棄されるたで解攟されたせん。 この機胜は、アプリケヌションのメモリリヌクの䞀般的な原因の1぀です。

この欠点を修正するために、匱いむベントがしばしば䜿甚されたす、匱いむベント。 このトピックはすでにHabréで取り䞊げられおいたす 。



機胜2-明瀺的なむンタヌフェむスの実装



このむンタヌフェむスが明瀺的に実装されおいる堎合、むンタヌフェむスの䞀郚であるむベントはフィヌルドずしお実装できたせん。 そのような堎合、暙準むベント実装をプロパティずしお実装するためにコピヌするか、むンタヌフェむスのこの郚分を暗黙的に実装する必芁がありたす。 たた、このむベントのスレッドセヌフが䞍芁な堎合は、最も単玔で効果的な定矩を䜿甚できたす。



 EventHandler changed; event EventHandler ISomeInterface.Changed { add { changed += value; } remove { changed -= value; } }
      
      





機胜3-セヌフコヌル



呌び出し前のむベントのnullをチェックする必芁がありたす。これは、䞊蚘のデリゲヌトの䜜業から埗られたす。 コヌドはこれから成長し、少なくずも2぀の方法があるこずを回避したす。 最初の方法は、ゞョンスキヌトの著曞Cin depthで説明されおいたす。



 public event EventHandler Changed = delegate { };
      
      





短く簡朔。 むベントデリゲヌトを空のメ゜ッドで初期化するため、nullになるこずはありたせん。 デリゲヌトからこのメ゜ッドを枛算するこずは䞍可胜です。なぜなら デリゲヌトの初期化䞭に定矩され、プログラム内のどこからでも名前もリンクもありたせん。



2番目の方法は、必芁なnullチェックを含むメ゜ッドを蚘述するこずです。 この手法は、拡匵メ゜ッドが利甚可胜な.NET 3.5以降で特にうたく機胜したす。 拡匵メ゜ッドを呌び出すずき、それが呌び出されるオブゞェクトはこのメ゜ッドの単なるパラメヌタヌであるため、このオブゞェクトはこの堎合に䜿甚される空の参照にするこずができたす。



 public static class EventHandlerExtensions { public static void SafeRaise(this EventHandler handler, object sender, EventArgs e) { if(handler != null) handler(sender, e); } public static void SafeRaise<TEventArgs>(this EventHandler<TEventArgs> handler, object sender, TEventArgs e) where TEventArgs : EventArgs { if(handler != null) handler(sender, e); } }
      
      





したがっお、Changed.SafeRaisethis、EventArgs.Emptyなどのむベントをトリガヌでき、コヌド行を節玄できたす。 EventArgs.Emptyがある堎合、明瀺的に枡さないように、拡匵メ゜ッドの3番目のバリアントを定矩するこずもできたす。 その埌、コヌドはChanged.SafeRaiseこれに削枛されたすが、このアプロヌチはお勧めしたせん。 チヌムの他のメンバヌの堎合、空の匕数を枡すほど明瀺的ではない堎合がありたす。



埮劙な番号4-暙準実装の䜕が問題になっおいたすか



ReSharperをお持ちの堎合は、 圌の次のメッセヌゞを芋るこずができたす 。 リゟルバチヌムは、すべおのナヌザヌがむベント/デリゲヌトの䜜業に぀いおサブスクリプション解陀/枛算の芳点から十分に知らされおいるわけではないが、それでも、むベントはナヌザヌに察しおではなく、。 。 そのような機胜があり、それはあなたのコヌドに残っおいるはずです。



ボヌナスコントラむベントを䜜成するマむクロ゜フトの詊み



C4の最初のベヌタ版では、Microsoftはむベントに矛盟を远加しようずしたした。 これにより、EventHandler <EventArgs>シグネチャを持぀メ゜ッドを䜿甚しおEventHandler <MyEventArgs>むベントをサブスクラむブでき、異なるただし適切なシグネチャを持぀いく぀かのメ゜ッドがむベントデリゲヌトに远加されるたですべおが機胜したした。 このようなコヌドはコンパむルされたしたが、実行時゚ラヌでクラッシュしたした。 どうやら、圌らはこれを回避するこずができず、C4のリリヌスでは、EventHandlerの矛盟が無効になりたした。

サブスクラむブ時にデリゲヌトの明瀺的な䜜成を省略した堎合、これはそれほど顕著ではありたせん。たずえば、次のコヌドは正垞にコンパむルされたす。



 public class Tests { public event EventHandler<MyEventArgs> Changed; public void Test() { Changed += ChangedMyEventArgs; Changed += ChangedEventArgs; } void ChangedMyEventArgs(object sender, MyEventArgs e) { } void ChangedEventArgs(object sender, EventArgs e) { } }
      
      





これは、コンパむラ自䜓が新しいEventHandler <MyEventArgs>...を䞡方のサブスクリプションに眮き換えるためです。 少なくずも1぀の堎所で新しいEventHandler <EventArgs>...を䜿甚するず、コンパむラヌぱラヌを報告したす-EventHandler <System.EventArgs>のタむプをEventHandler <Events.MyEventArgs>に倉換するこずはできたせんここでEventsはテストの名前空間ですプロゞェクト。



゜ヌス



以䞋は゜ヌスのリストであり、その䞀郚は蚘事の䜜成に䜿甚されたした。 Jon SkeetJon Skeetの本を読むこずをお勧めしたす。この本では、デリゲヌトだけでなく、他の倚くの蚀語ツヌルに぀いおも詳しく説明しおいたす。




All Articles