トップ回答者
VB インターフェイスに定義されたイベントを拡張メソッドで発火させる方法

質問
-
お世話になります。
ただいまポリモーフィズムについて学習中です。例えば以下のようなタスクの完了を通知するための単純なインターフェイスがあるとします。
Public Interface INotifyTaskCompleted Event TaskCompleted As TaskCompletedEventHandler End Interface Public Delegate Sub TaskCompletedEventHandler(sender As Object, e As TaskCompleteEventArgs) Public Class TaskCompleteEventArgs Inherits EventArgs Public Sub New() End Sub End Class
このインターフェースをクラスに実装した場合は以下のようになると思います。
Public MustInherit Class SampleBase Implements INotifyTaskCompleted Public Event TaskCompleted As TaskCompletedEventHandler Implements INotifyTaskCompleted.TaskCompleted Protected Sub OnAsyncTaskCompleted() RaiseEvent TaskCompleted(Me, New TaskCompleteEventArgs) End Sub End Class
これを基底クラスとしても利用するのでもよいのですが、
他のクラスを継承しているために、この基底クラスを継承できない場合があります。
その際、インターフェース実装毎にイベントを発火させるためのロジックを書く必要があります。なので以下のように拡張メソッドで書ければよかったのですが、これはビルドエラーとなります。
Partial Module INotifyTaskCompletedExtension
<System.Runtime.CompilerServices.Extension>
Public Sub OnTaskCompleted(source As INotifyTaskCompleted, sender as Object)
RaiseEvent source.TaskCompleted(Nothing, New TaskCompleteEventArgs)
End Sub
End Moduleこのような記述はできないでしょうか?別の方法などありますか?
ちなみに、今回定義したインターフェースは別の拡張メソッドを持ち
Rxでイベントを購読するようにしています。こちらともうまく連携できればよいのですが。Partial Module INotifyTaskCompletedExtension <System.Runtime.CompilerServices.Extension> Public Function FromTaskCompletedEvent(source As INotifyTaskCompleted) As IObservable(Of TaskCompleteEventArgs) Return Observable.FromEvent(Of TaskCompletedEventHandler, TaskCompleteEventArgs)( Function(h) Sub(s, e) h(e), Sub(h) AddHandler source.TaskCompleted, h, Sub(h) RemoveHandler source.TaskCompleted, h) End Function End Module
<開発環境>
Visual Studio2015 Community(VB)
.NET Framework4.6.1
WPFアプリケーション
Windows7 Pro x64
- 編集済み mikupedia 2016年10月3日 10:10
回答
-
とても面倒な手順を踏めば外部からイベント発火はできないこともないです。
(外部から発火するための共通メソッドをインターフェースで定義するのがマシなてだと思いますが)複雑な処理をしなければいけないのであればインターフェースを実装する各クラスから、共通の処理を抜き出した新たなクラスもしくはモジュールを作り、各クラスがその共通外部クラスの関数を呼び出すのが正常だと思います。
単純な処理をたくさん記述しなければいけないのが面倒だという意味であれば、スニペットを作ってやるといいです。Module Module1 Sub Main() Dim sample1 As New Sample() Dim sample2 As New Sample2() Dim sample3 As New Sample3() AddHandler CType(sample1, INotifyTaskCompleted).TaskCompleted, _ Sub(s, e) Console.WriteLine("OK 1") End Sub AddHandler CType(sample2, INotifyTaskCompleted).TaskCompleted, _ Sub(s, e) Console.WriteLine("OK 2") End Sub AddHandler CType(sample2, INotifyTaskCompleted).TaskCompleted, _ Sub(s, e) Console.WriteLine("OK 3") End Sub sample1.OnTaskCompleted(New TaskCompleteEventArgs()) sample2.OnTaskCompleted(New TaskCompleteEventArgs()) CType(sample3, IRaiseEvent(Of TaskCompleteEventArgs)).Raise(sample3, New TaskCompleteEventArgs()) End Sub End Module Module INotifyTaskCompletedExtension <System.Runtime.CompilerServices.Extension> Public Sub OnTaskCompleted(source As INotifyTaskCompleted, e As TaskCompleteEventArgs) Dim ei As System.Reflection.EventInfo _ = source.GetType().GetEvent("TaskCompleted", Reflection.BindingFlags.NonPublic Or Reflection.BindingFlags.Public Or Reflection.BindingFlags.Instance) Dim rm As System.Reflection.MethodInfo = ei.GetRaiseMethod(True) If (rm IsNot Nothing) Then rm.Invoke(source, New Object() {source, New TaskCompleteEventArgs()}) Else '標準のイベント実装されている場合 Dim fi As System.Reflection.FieldInfo _ = ei.DeclaringType.GetFields(Reflection.BindingFlags.Instance Or Reflection.BindingFlags.NonPublic) _ .Where(Function(a) a.FieldType = GetType(TaskCompletedEventHandler)).FirstOrDefault() If (fi IsNot Nothing) Then Dim handler = CType(fi.GetValue(source), TaskCompletedEventHandler) handler.Invoke(source, New TaskCompleteEventArgs()) End If End If End Sub End Module Public Interface INotifyTaskCompleted Event TaskCompleted As TaskCompletedEventHandler End Interface Public Delegate Sub TaskCompletedEventHandler(sender As Object, e As TaskCompleteEventArgs) Public Class TaskCompleteEventArgs Inherits EventArgs Public Sub New() End Sub End Class ''' <summary>普通のイベント実装された場合</summary> Public MustInherit Class SampleBase Implements INotifyTaskCompleted Public Event TaskCompleted(sender As Object, e As TaskCompleteEventArgs) Implements INotifyTaskCompleted.TaskCompleted Protected Sub OnAsyncTaskCompleted() RaiseEvent TaskCompleted(Me, New TaskCompleteEventArgs) End Sub End Class Public Class Sample : Inherits SampleBase End Class ''' <summary>カスタムイベント実装された場合</summary> Public Class Sample2 Implements INotifyTaskCompleted Private Event _internalHandler As TaskCompletedEventHandler Public Custom Event TaskCompleted As TaskCompletedEventHandler Implements INotifyTaskCompleted.TaskCompleted AddHandler(value As TaskCompletedEventHandler) AddHandler _internalHandler, value End AddHandler RemoveHandler(value As TaskCompletedEventHandler) RemoveHandler _internalHandler, value End RemoveHandler RaiseEvent(source As Object, e As TaskCompleteEventArgs) RaiseEvent _internalHandler(source, e) End RaiseEvent End Event Protected Sub OnAsyncTaskCompleted() RaiseEvent TaskCompleted(Me, New TaskCompleteEventArgs()) End Sub End Class ''' <summary>イベントを外部発火させられるインターフェース</summary> Friend Interface IRaiseEvent(Of T) Sub Raise(sender As Object, e As T) End Interface ''' <summary>外部発火インターフェースを実装する場合</summary> ''' <remarks></remarks> Public Class Sample3 Implements INotifyTaskCompleted Implements IRaiseEvent(Of TaskCompleteEventArgs) Public Event TaskCompleted As TaskCompletedEventHandler Implements INotifyTaskCompleted.TaskCompleted 'インターフェース経由でしか許可しないためにprivate Private Sub Raise(sender As Object, e As TaskCompleteEventArgs) Implements IRaiseEvent(Of TaskCompleteEventArgs).Raise RaiseEvent TaskCompleted(sender, e) End Sub End Class
個別に明示されていない限りgekkaがフォーラムに投稿したコードにはフォーラム使用条件に基づき「MICROSOFT LIMITED PUBLIC LICENSE」が適用されます。(かなり自由に使ってOK!)
すべての返信
-
とても面倒な手順を踏めば外部からイベント発火はできないこともないです。
(外部から発火するための共通メソッドをインターフェースで定義するのがマシなてだと思いますが)複雑な処理をしなければいけないのであればインターフェースを実装する各クラスから、共通の処理を抜き出した新たなクラスもしくはモジュールを作り、各クラスがその共通外部クラスの関数を呼び出すのが正常だと思います。
単純な処理をたくさん記述しなければいけないのが面倒だという意味であれば、スニペットを作ってやるといいです。Module Module1 Sub Main() Dim sample1 As New Sample() Dim sample2 As New Sample2() Dim sample3 As New Sample3() AddHandler CType(sample1, INotifyTaskCompleted).TaskCompleted, _ Sub(s, e) Console.WriteLine("OK 1") End Sub AddHandler CType(sample2, INotifyTaskCompleted).TaskCompleted, _ Sub(s, e) Console.WriteLine("OK 2") End Sub AddHandler CType(sample2, INotifyTaskCompleted).TaskCompleted, _ Sub(s, e) Console.WriteLine("OK 3") End Sub sample1.OnTaskCompleted(New TaskCompleteEventArgs()) sample2.OnTaskCompleted(New TaskCompleteEventArgs()) CType(sample3, IRaiseEvent(Of TaskCompleteEventArgs)).Raise(sample3, New TaskCompleteEventArgs()) End Sub End Module Module INotifyTaskCompletedExtension <System.Runtime.CompilerServices.Extension> Public Sub OnTaskCompleted(source As INotifyTaskCompleted, e As TaskCompleteEventArgs) Dim ei As System.Reflection.EventInfo _ = source.GetType().GetEvent("TaskCompleted", Reflection.BindingFlags.NonPublic Or Reflection.BindingFlags.Public Or Reflection.BindingFlags.Instance) Dim rm As System.Reflection.MethodInfo = ei.GetRaiseMethod(True) If (rm IsNot Nothing) Then rm.Invoke(source, New Object() {source, New TaskCompleteEventArgs()}) Else '標準のイベント実装されている場合 Dim fi As System.Reflection.FieldInfo _ = ei.DeclaringType.GetFields(Reflection.BindingFlags.Instance Or Reflection.BindingFlags.NonPublic) _ .Where(Function(a) a.FieldType = GetType(TaskCompletedEventHandler)).FirstOrDefault() If (fi IsNot Nothing) Then Dim handler = CType(fi.GetValue(source), TaskCompletedEventHandler) handler.Invoke(source, New TaskCompleteEventArgs()) End If End If End Sub End Module Public Interface INotifyTaskCompleted Event TaskCompleted As TaskCompletedEventHandler End Interface Public Delegate Sub TaskCompletedEventHandler(sender As Object, e As TaskCompleteEventArgs) Public Class TaskCompleteEventArgs Inherits EventArgs Public Sub New() End Sub End Class ''' <summary>普通のイベント実装された場合</summary> Public MustInherit Class SampleBase Implements INotifyTaskCompleted Public Event TaskCompleted(sender As Object, e As TaskCompleteEventArgs) Implements INotifyTaskCompleted.TaskCompleted Protected Sub OnAsyncTaskCompleted() RaiseEvent TaskCompleted(Me, New TaskCompleteEventArgs) End Sub End Class Public Class Sample : Inherits SampleBase End Class ''' <summary>カスタムイベント実装された場合</summary> Public Class Sample2 Implements INotifyTaskCompleted Private Event _internalHandler As TaskCompletedEventHandler Public Custom Event TaskCompleted As TaskCompletedEventHandler Implements INotifyTaskCompleted.TaskCompleted AddHandler(value As TaskCompletedEventHandler) AddHandler _internalHandler, value End AddHandler RemoveHandler(value As TaskCompletedEventHandler) RemoveHandler _internalHandler, value End RemoveHandler RaiseEvent(source As Object, e As TaskCompleteEventArgs) RaiseEvent _internalHandler(source, e) End RaiseEvent End Event Protected Sub OnAsyncTaskCompleted() RaiseEvent TaskCompleted(Me, New TaskCompleteEventArgs()) End Sub End Class ''' <summary>イベントを外部発火させられるインターフェース</summary> Friend Interface IRaiseEvent(Of T) Sub Raise(sender As Object, e As T) End Interface ''' <summary>外部発火インターフェースを実装する場合</summary> ''' <remarks></remarks> Public Class Sample3 Implements INotifyTaskCompleted Implements IRaiseEvent(Of TaskCompleteEventArgs) Public Event TaskCompleted As TaskCompletedEventHandler Implements INotifyTaskCompleted.TaskCompleted 'インターフェース経由でしか許可しないためにprivate Private Sub Raise(sender As Object, e As TaskCompleteEventArgs) Implements IRaiseEvent(Of TaskCompleteEventArgs).Raise RaiseEvent TaskCompleted(sender, e) End Sub End Class
個別に明示されていない限りgekkaがフォーラムに投稿したコードにはフォーラム使用条件に基づき「MICROSOFT LIMITED PUBLIC LICENSE」が適用されます。(かなり自由に使ってOK!)
-
gekka 様
ご回答ありがとうございました。
ご提示いただいたコードはとても参考になりました。ご提示いただいたコードを踏まえると
やや冗長なコードとなるので提供者と利用者の双方で手間がかかるように思えました。
インターフェース(+イベント)が増えることを想定して、ご指摘どおり下記の方法で対応したいと思います。>インターフェースを実装する各クラスから、共通の処理を抜き出した新たなクラスもしくはモジュールを作り、各クラスがその共通外部クラスの関数を呼び出す
>単純な処理をたくさん記述しなければいけないのが面倒だという意味であれば、スニペットを作ってやる