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

  • 質問

  • お世話になります。
    ただいまポリモーフィズムについて学習中です。

    例えば以下のようなタスクの完了を通知するための単純なインターフェイスがあるとします。

        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
    2016年10月3日 10:06

回答

  • とても面倒な手順を踏めば外部からイベント発火はできないこともないです。
    (外部から発火するための共通メソッドをインターフェースで定義するのがマシなてだと思いますが)

    複雑な処理をしなければいけないのであればインターフェースを実装する各クラスから、共通の処理を抜き出した新たなクラスもしくはモジュールを作り、各クラスがその共通外部クラスの関数を呼び出すのが正常だと思います。
    単純な処理をたくさん記述しなければいけないのが面倒だという意味であれば、スニペットを作ってやるといいです。

    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!)

    • 編集済み gekkaMVP 2016年10月3日 12:17
    • 回答としてマーク mikupedia 2016年10月5日 14:45
    2016年10月3日 12:08

すべての返信

  • とても面倒な手順を踏めば外部からイベント発火はできないこともないです。
    (外部から発火するための共通メソッドをインターフェースで定義するのがマシなてだと思いますが)

    複雑な処理をしなければいけないのであればインターフェースを実装する各クラスから、共通の処理を抜き出した新たなクラスもしくはモジュールを作り、各クラスがその共通外部クラスの関数を呼び出すのが正常だと思います。
    単純な処理をたくさん記述しなければいけないのが面倒だという意味であれば、スニペットを作ってやるといいです。

    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!)

    • 編集済み gekkaMVP 2016年10月3日 12:17
    • 回答としてマーク mikupedia 2016年10月5日 14:45
    2016年10月3日 12:08
  • gekka 様

    ご回答ありがとうございました。
    ご提示いただいたコードはとても参考になりました。

    ご提示いただいたコードを踏まえると
    やや冗長なコードとなるので提供者と利用者の双方で手間がかかるように思えました。
    インターフェース(+イベント)が増えることを想定して、ご指摘どおり下記の方法で対応したいと思います。

    >インターフェースを実装する各クラスから、共通の処理を抜き出した新たなクラスもしくはモジュールを作り、各クラスがその共通外部クラスの関数を呼び出す
    >単純な処理をたくさん記述しなければいけないのが面倒だという意味であれば、スニペットを作ってやる

    2016年10月5日 14:44