WeakEvent パターンを VB で学んでみる
先月のわんくま勉強会で尾上さんのセッションを見て、WeakEvent パターンなるもの始めて知りました。
それ以来 MSDN やいろんなブログを廻っては WeakEvent パターンについて研究しているわけですが、頭がおっさん化し始めてるせいかイマイチ頭に入ってこない。(つかすでにおっさんなんですがw)
そこで、公開されてるサンプルをコーディングしながら学習するという、いつものパターンによる勉強法とあいなる訳ですが、実装してて一番面白いと思ったのが id:zecl さんの以下のエントリ。
C# でガリガリコード写して書いてるうちに思いついたのが、この WeakEvent パターン、C# しか実装例が見あたんない(見つけらんないだけかも知れませんけどね)。ジェネリック使い倒してるこのコード、普通の VB ユーザーには到底理解不能な代物であろう。だったら VB ユーザーさんにも試せるよう、ここは一つ VB 移植にチャレンジしてみようではないか!ということで、id:zecl さんのお許しを頂き 早速 VB プロジェクトを新規に起こし移植させて頂きました。各クラス・インターフェイスの詳細に関しては元記事をご覧くださいませ。
EventHandler[TEventArgs}を持っていることを表すインターフェイス
Option Explicit On Option Strict On Public Interface IHaveEventHandler(Of TEventArgs As EventArgs) Event EventTriggered As EventHandler(Of TEventArgs) End Interface
IWeakEventListenerを拡張する
Option Explicit On Option Strict On Imports System.Windows Public Interface IWeakEventListenerEx(Of TEventArgs As EventArgs) Inherits IWeakEventListener Property EventSource As IHaveEventHandler(Of TEventArgs) End Interface <|| **WeakEventTriggeredManager{TSource, TEventArgs}クラス >|vb| Option Explicit On Option Strict On Imports System.Windows Imports System.Diagnostics Public NotInheritable Class WeakEventTriggeredManager(Of TSource As {Class, IHaveEventHandler(Of TEventArgs)}, TEventArgs As EventArgs) Inherits WeakEventManager Protected Overrides Sub StartListening(source As Object) Dim src = TryCast(source, TSource) Debug.Assert(src IsNot Nothing) AddHandler src.EventTriggered, New EventHandler(Of TEventArgs)(AddressOf OnEventTriggered) #If DEBUG Then Console.WriteLine("AddHandler - [{0}]", GetType(TEventArgs).ToString()) #End If End Sub Protected Overrides Sub StopListening(source As Object) Dim src = TryCast(source, TSource) Debug.Assert(src IsNot Nothing) RemoveHandler src.EventTriggered, New EventHandler(Of TEventArgs)(AddressOf OnEventTriggered) #If DEBUG Then Console.WriteLine("RemoveHandler - [{0}]", GetType(TEventArgs).ToString()) #End If End Sub Public Shared Sub AddListener(listener As IWeakEventListenerEx(Of TEventArgs)) WeakEventTriggeredManager(Of TSource, TEventArgs).CurrentManager.ProtectedAddListener(listener.EventSource, listener) End Sub Public Shared Sub RemoveListener(listener As IWeakEventListenerEx(Of TEventArgs)) WeakEventTriggeredManager(Of TSource, TEventArgs).CurrentManager.ProtectedRemoveListener(listener.EventSource, listener) End Sub Public Shared Sub RemoveNullReferenceListener(source As IHaveEventHandler(Of TEventArgs)) WeakEventTriggeredManager(Of TSource, TEventArgs).CurrentManager.StopListening(source) End Sub Private Sub OnEventTriggered(sender As Object, e As EventArgs) MyBase.DeliverEvent(sender, e) End Sub Private Shared _manager As WeakEventTriggeredManager(Of TSource, TEventArgs) = Nothing Private Shared ReadOnly Property CurrentManager As WeakEventTriggeredManager(Of TSource, TEventArgs) Get If (_manager IsNot Nothing) Then Return _manager Dim type As Type = GetType(WeakEventTriggeredManager(Of TSource, TEventArgs)) _manager = New WeakEventTriggeredManager(Of TSource, TEventArgs)() WeakEventManager.SetCurrentManager(type, _manager) Return _manager End Get End Property End Class
AbstractWeakEventListenerクラス
Option Explicit On Option Strict On Imports System.Diagnostics Imports System.Windows Public MustInherit Class AbstractWeakEventListener(Of TSource As {Class, IHaveEventHandler(Of TEventArgs)}, TEventArgs As EventArgs) Implements IWeakEventListenerEx(Of TEventArgs), IDisposable Public Sub New(source As TSource) Debug.Assert(source IsNot Nothing) _disposed = False EventSource = source End Sub Protected Overrides Sub Finalize() Me.Dispose(False) WeakEventTriggeredManager(Of TSource, TEventArgs).RemoveNullReferenceListener(EventSource) #If DEBUG Then Console.WriteLine("デストラクタに来たよ") #End If End Sub Public MustOverride Function ReceiveWeakEvent( managerType As System.Type, sender As Object, e As EventArgs) As Boolean Implements IWeakEventListener.ReceiveWeakEvent Private _disposed As Boolean Protected Overridable Sub Dispose(disposing As Boolean) If (_disposed) Then Return SyncLock Me _disposed = True If disposing Then WeakEventTriggeredManager(Of TSource, TEventArgs).RemoveListener(Me) End If End SyncLock End Sub Public Sub Dispose() Implements IDisposable.Dispose Dispose(True) GC.SuppressFinalize(Me) End Sub Private _eventSource As IHaveEventHandler(Of TEventArgs) = Nothing Public Property EventSource As IHaveEventHandler(Of TEventArgs) Implements IWeakEventListenerEx(Of TEventArgs).EventSource Get Return _eventSource End Get Set(value As IHaveEventHandler(Of TEventArgs)) _eventSource = value End Set End Property End Class
イベントソースの実装サンプル
Option Explicit On Option Strict On Imports System.ComponentModel Public Class SampleEventSource Implements IHaveEventHandler(Of EventArgs), IHaveEventHandler(Of CancelEventArgs) Public Sub Raise() RaiseEvent _event(Me, New EventArgs()) RaiseEvent _cancelEvent(Me, New CancelEventArgs()) End Sub Public Event _event(sender As Object, e As EventArgs) Implements IHaveEventHandler(Of EventArgs).EventTriggered Public Event _cancelEvent(sender As Object, e As CancelEventArgs) Implements IHaveEventHandler(Of CancelEventArgs).EventTriggered End Class
イベントリスナの実装サンプル
サンプルその1
Option Explicit On Option Strict On Imports System.ComponentModel Public Class HogeEventListener(Of TSource As {Class, IHaveEventHandler(Of TEventArgs)}, TEventArgs As EventArgs) Inherits AbstractWeakEventListener(Of TSource, TEventArgs) Public Sub New(source As TSource) MyBase.New(source) End Sub Private Sub source_EventTriggered1(sender As Object, e As TEventArgs) Console.WriteLine("Hoge::EventTriggered1が発砲されたよ") End Sub Private Sub source_EventTriggered2(sender As Object, e As TEventArgs) Console.WriteLine("Hoge::EventTriggered2が発砲されたよ") End Sub Private Sub source_EventTriggered3(sender As Object, e As TEventArgs) Console.WriteLine("Hoge::EventTriggered3が発砲されたよ") End Sub Public Overrides Function ReceiveWeakEvent(managerType As Type, sender As Object, e As EventArgs) As Boolean If (managerType = GetType(WeakEventTriggeredManager(Of TSource, TEventArgs))) Then source_EventTriggered3(sender, TryCast(e, TEventArgs)) ' source_EventTriggered2(sender, TryCast(e, TEventArgs)) source_EventTriggered1(sender, TryCast(e, TEventArgs)) Return True End If Return False End Function End Class
サンプルその2
Option Explicit On Option Strict On Imports System.ComponentModel Public Class PiyoEventListener(Of TSource As {Class, IHaveEventHandler(Of TEventArgs)}, TEventArgs As EventArgs) Inherits AbstractWeakEventListener(Of TSource, TEventArgs) Public Sub New(source As TSource) MyBase.New(source) End Sub Private Sub source_EventTriggered1(sender As Object, e As TEventArgs) Console.WriteLine("Piyo::EventTriggered1が発砲されたよ") End Sub Private Sub source_EventTriggered2(sender As Object, e As TEventArgs) Console.WriteLine("Piyo::EventTriggered2が発砲されたよ") End Sub Private Sub source_EventTriggered3(sender As Object, e As TEventArgs) Console.WriteLine("Piyo::EventTriggered3が発砲されたよ") End Sub Public Overrides Function ReceiveWeakEvent(managerType As Type, sender As Object, e As EventArgs) As Boolean If (managerType = GetType(WeakEventTriggeredManager(Of TSource, TEventArgs))) Then source_EventTriggered1(sender, TryCast(e, TEventArgs)) source_EventTriggered2(sender, TryCast(e, TEventArgs)) ' source_EventTriggered3(sender, TryCast(e, TEventArgs)) Return True End If Return False End Function End Class
使ってみる
Option Explicit On Option Strict On Imports System.ComponentModel Module Module1 Sub Main() Example1() Console.WriteLine() Example2() Console.WriteLine() Example3() Console.ReadKey() End Sub <Conditional("DEBUG")> Private Sub Example1() Console.WriteLine("*Example1*") Dim source = New SampleEventSource() Dim hoge = New HogeEventListener(Of SampleEventSource, EventArgs)(source) Dim piyo = New PiyoEventListener(Of SampleEventSource, CancelEventArgs)(source) ' リスナを追加 WeakEventTriggeredManager(Of SampleEventSource, EventArgs).AddListener(hoge) WeakEventTriggeredManager(Of SampleEventSource, CancelEventArgs).AddListener(piyo) source.Raise() ' hogeリスナには、Disposeせずに強制的にガベコレしてリスナにお亡くなりになって頂く hoge = Nothing GC.Collect() GC.WaitForPendingFinalizers() ' piyoリスナは生きているので発砲される source.Raise() End Sub <Conditional("DEBUG")> Private Sub Example2() Console.WriteLine("*Example2*") Dim source = New SampleEventSource() Using hoge = New HogeEventListener(Of SampleEventSource, EventArgs)(source), piyo = New PiyoEventListener(Of SampleEventSource, CancelEventArgs)(source) ' リスナを追加 WeakEventTriggeredManager(Of SampleEventSource, EventArgs).AddListener(hoge) WeakEventTriggeredManager(Of SampleEventSource, CancelEventArgs).AddListener(piyo) source.Raise() End Using ' いずれのリスナもお亡くなりになっているので発砲されない source.Raise() End Sub <Conditional("DEBUG")> Private Sub Example3() Console.WriteLine("*Example3*") Dim source = New SampleEventSource() Using piyo = New PiyoEventListener(Of SampleEventSource, CancelEventArgs)(source) Dim hoge = New HogeEventListener(Of SampleEventSource, EventArgs)(source) ' リスナを追加 WeakEventTriggeredManager(Of SampleEventSource, EventArgs).AddListener(hoge) WeakEventTriggeredManager(Of SampleEventSource, CancelEventArgs).AddListener(piyo) source.Raise() ' リスナを明示的に削除してもかまわない WeakEventTriggeredManager(Of SampleEventSource, CancelEventArgs).RemoveListener(piyo) End Using ' hogeリスナは生きているので発砲される source.Raise() End Sub End Module