VB のたまご

作成日: 2017/04/02, 更新日: 2017/04/02



メモリリーク3

  •  前回の続きです。

  •  今回は、Livet に組み込まれている「破棄可能なイベントリスナーと弱いイベントパターン」を使ってイベント登録してみましょう。

  • スポンサーリンク



6つ目のビュークラス

    Imports LivetWPFApplication3.Models
    Imports System.ComponentModel
    Imports Livet.EventListeners
    
    Namespace ViewModels
        Public Class Test6ViewModel
            Inherits ViewModel
    
    #Region "Person変更通知プロパティ"
            Private _Person As Model
    
            Public Property Person() As Model
                Get
                    Return _Person
                End Get
                Set(ByVal value As Model)
                    If Model.ValueEquals(_Person, value) Then Return
                    _Person = value
                    RaisePropertyChanged()
                End Set
            End Property
    #End Region
    
            Public Sub New(person As Model)
    
                Me.Person = person
                Dim listener1 = New PropertyChangedEventListener(Me.Person)
                listener1.RegisterHandler(AddressOf Me.Person_PropertyChanged)
    
                ' PropertyChangedEventListener クラスは IDisposable を実装している。
                ' Composite(コンポジット、複合という意味合い)Disposable メンバーに登録して、破棄してもらう
                ' よって、このクラスは、Using ステートメント間か、最後に Dispose メソッドを呼んで必ず破棄すること
                Me.CompositeDisposable.Add(listener1)
    
                Me.Person.Name = "jiro"
                Me.Person.Age = 45
    
            End Sub
    
            Public Sub MyDispose()
    
            End Sub
    
            Private Sub Person_PropertyChanged(sender As Object, e As PropertyChangedEventArgs)
    
                Dim value = Me.Person.GetType().GetProperty(e.PropertyName).GetValue(Me.Person, Nothing)
                ' 100万回出力されるのが辛いのでコメントアウト
                'Console.WriteLine($"{e.PropertyName} = {value} に変更されました")
    
            End Sub
    
            Public Sub Initialize()
            End Sub
    
        End Class
    
    End Namespace
    

  •  これを実行するのが以下の部分です。

  • ' 【Livet.EventListeners.PropertyChangedEventListener】+【Dispose】
    ' メモリリークは発生しない(即時解放)
    ' =PropertyChangedEventListener だけならメモリリークは発生する
    Using vm = New Test6ViewModel(Me.Person)
    
    End Using
    

  •  PropertyChangedEventListener クラスはインスタンス生成して使うリスナーです。 コンストラクタにデータクラスを指定しておき、後から、RegisterHandler メソッドを使ってイベントハンドラやラムダ式を登録します。

  •  このリスナーは強いイベントパターンなので、イベント解除処理が必要なのですが IDisposable インターフェースを実装しているので、Dispose メソッドを呼び出してイベント解除します。 それでは、ビューモデルで IDisposable インターフェースを実装しないといけないのかと言うと、Livet のビューモデルの基底クラスで既に実装されているので、不要です。 また、破棄したいデータをまとめて管理しておいて、ビューモデルの Dispose 時にまとめて破棄してくれる仕組みが Livet には組み込まれています。 つまり、基底クラスのメンバーである CompositeDisposable コレクションプロパティ(LivetCompositeDisposable 型)に、この戻り値をセットしておけば破棄処理をしてくれるので便利です。 見たらまたコメントアウトして戻しておくか、この部分を削除してしまいましょう。


7つ目のビュークラス

    Imports LivetWPFApplication3.Models
    Imports System.ComponentModel
    Imports Livet.EventListeners
    
    Namespace ViewModels
        Public Class Test7ViewModel
            Inherits ViewModel
    
    #Region "Person変更通知プロパティ"
            Private _Person As Model
    
            Public Property Person() As Model
                Get
                    Return _Person
                End Get
                Set(ByVal value As Model)
                    If Model.ValueEquals(_Person, value) Then Return
                    _Person = value
                    RaisePropertyChanged()
                End Set
            End Property
    #End Region
    
            Public Sub New(person As Model)
    
                Me.Person = person
                ' PropertyChangedEventListener が ProperChanged イベント専用リスナーなのに対して、
                ' EventListener は、あらゆるイベントを受け持てる汎用リスナー
                Dim listener1 = New EventListener(Of PropertyChangedEventHandler)(
                    Sub(h) AddHandler Me.Person.PropertyChanged, h,
                    Sub(h) RemoveHandler Me.Person.PropertyChanged, h,
                    AddressOf Me.Person_PropertyChanged)
    
                ' EventListener クラスは IDisposable を実装している。
                ' Composite(コンポジット、複合という意味合い)Disposable メンバーに登録して、破棄してもらう
                ' よって、このクラスは、Using ステートメント間か、最後に Dispose メソッドを呼んで必ず破棄すること
                Me.CompositeDisposable.Add(listener1)
    
                Me.Person.Name = "jiro"
                Me.Person.Age = 45
    
            End Sub
    
            Public Sub MyDispose()
    
            End Sub
    
            Private Sub Person_PropertyChanged(sender As Object, e As PropertyChangedEventArgs)
    
                Dim value = Me.Person.GetType().GetProperty(e.PropertyName).GetValue(Me.Person, Nothing)
                ' 100万回出力されるのが辛いのでコメントアウト
                'Console.WriteLine($"{e.PropertyName} = {value} に変更されました")
    
            End Sub
    
            Public Sub Initialize()
            End Sub
    
        End Class
    
    End Namespace
    

  •  これを実行するのが以下の部分です。

  • ' 【Livet.EventListeners.EventListener】+【Dispose】
    ' メモリリークは発生しない(即時解放)
    ' =EventListener だけならメモリリークは発生する
    Using vm = New Test7ViewModel(Me.Person)
    
    End Using
    

  •  PropertyChangedEventListener クラスが PropertyChanged イベント専用リスナーなのに対して、EventListener クラスはあらゆるイベントのリスナーになることができます。 ジェネリッククラスになっているため可能なことですね。

  •  EventListener クラスもインスタンス生成して使うやり方で、購読したいイベントのデリゲートを指定します。 第一引数にイベント登録メソッドを、第二引数にイベント解除メソッドを、第三引数に紐づけたいイベントハンドラメソッドを指定します。 これもIDisposable インターフェースを実装していますので、CompositeDisposable コレクションプロパティに、この戻り値をセットして使います。 見たらまたコメントアウトして戻しておくか、この部分を削除してしまいましょう。

  • スポンサーリンク



8つ目のビュークラス

    Imports LivetWPFApplication3.Models
    Imports System.ComponentModel
    Imports Livet.EventListeners.WeakEvents
    
    Namespace ViewModels
        Public Class Test8ViewModel
            Inherits ViewModel
    
    #Region "Person変更通知プロパティ"
            Private _Person As Model
    
            Public Property Person() As Model
                Get
                    Return _Person
                End Get
                Set(ByVal value As Model)
                    If Model.ValueEquals(_Person, value) Then Return
                    _Person = value
                    RaisePropertyChanged()
                End Set
            End Property
    #End Region
    
            Public Sub New(person As Model)
    
                Me.Person = person
                Dim listener1 = New PropertyChangedWeakEventListener(Me.Person)
                listener1.RegisterHandler(AddressOf Me.Person_PropertyChanged)
    
                ' PropertyChangedWeakEventListener クラスは IDisposable を実装している。
                ' Composite(コンポジット、複合という意味合い)Disposable メンバーに登録して、破棄してもらう
                ' よって、このクラスは、Using ステートメント間か、最後に Dispose メソッドを呼んで必ず破棄すること
                Me.CompositeDisposable.Add(listener1)
    
                Me.Person.Name = "jiro"
                Me.Person.Age = 45
    
            End Sub
    
            Public Sub MyDispose()
    
            End Sub
    
            Private Sub Person_PropertyChanged(sender As Object, e As PropertyChangedEventArgs)
    
                Dim value = Me.Person.GetType().GetProperty(e.PropertyName).GetValue(Me.Person, Nothing)
                ' 100万回出力されるのが辛いのでコメントアウト
                'Console.WriteLine($"{e.PropertyName} = {value} に変更されました")
    
            End Sub
    
            Public Sub Initialize()
            End Sub
    
        End Class
    
    End Namespace
    

  •  これを実行するのが以下の部分です。

  • ' 【Livet.EventListeners.WeakEvents.PropertyChangedWeakEventListener】+【Dispose】
    ' メモリリークは発生しない(即時解放)
    ' =PropertyChangedWeakEventListener だけならメモリリークは発生する
    ' WeakEvent と命名されているので、解放しなくてもいけると思うのだが、100万回はやりすぎだから?使い方間違えてる?
    Using vm = New Test8ViewModel(Me.Person)
    
    End Using
    

  •  PropertyChangedWeakEventListener クラスは、PropertyChangedEventListener クラスと同じ使い方で、かつ弱いイベントパターンです。 弱いイベントパターンなので、破棄しなくても大丈夫だと思うのですが、私の使い方が悪いからかメモリリークが発生してしまいます。 よって、これもIDisposable インターフェースを実装していますので、CompositeDisposable コレクションプロパティに、この戻り値をセットして使います。 見たらまたコメントアウトして戻しておくか、この部分を削除してしまいましょう。


9つ目のビュークラス

    Imports LivetWPFApplication3.Models
    Imports System.ComponentModel
    Imports Livet.EventListeners.WeakEvents
    
    Namespace ViewModels
        Public Class Test9ViewModel
            Inherits ViewModel
    
    #Region "Person変更通知プロパティ"
            Private _Person As Model
    
            Public Property Person() As Model
                Get
                    Return _Person
                End Get
                Set(ByVal value As Model)
                    If Model.ValueEquals(_Person, value) Then Return
                    _Person = value
                    RaisePropertyChanged()
                End Set
            End Property
    #End Region
    
            Public Sub New(person As Model)
    
                Me.Person = person
    
                ' PropertyChangedWeakEventListener が ProperChanged イベント専用リスナーなのに対して、
                ' LivetWeakEventListener は、あらゆるイベントを受け持てる汎用リスナー
    
                ' 第1引数・・・EventHandler(Of PropertyChangedEventArgs) から PropertyChangedEventHandler への型変換をして、
                ' 第2引数・・・第一引数の戻り値(イベントハンドラ)をデータクラスの PropertyChanged イベントに登録する処理と、
                ' 第3引数・・・第一引数の戻り値(イベントハンドラ)をデータクラスの PropertyChanged イベントから解除する処理をセットして、
                ' 第4引数・・・実行したいイベントハンドラを指定
                Dim listener1 = New LivetWeakEventListener(Of PropertyChangedEventHandler, PropertyChangedEventArgs)(
                    Function(h) Sub(sender, e) h(sender, e),
                    Sub(h) AddHandler Me.Person.PropertyChanged, h,
                    Sub(h) RemoveHandler Me.Person.PropertyChanged, h,
                    Sub(sender, e) Me.Person_PropertyChanged(sender, e))
    
                '' 以下は調査用プログラムですが、また分からなくなった時のために残しておきます。不要プログラムなので読まなくて良いです。
                '' VB では、 C# のようにデリゲートをインスタンス生成時、引数にデリゲートを渡すことができません。
                '' (第1引数で、Function(h) New PropertyChangedEventHandler(h) ができないorz)
                ''
                '' (LivetWPFApplication3.ConsoleApplication1 の動作結果より判断)
                '' よって VB では、直接デリゲートを渡すのではなく、ラムダ式経由でデリゲートを渡します。
                '' (戻り値となるラムダ式(PropertyChangedEventHandler)は窓口で、その処理内では、引数のデリゲート(EventHandler(Of PropertyChangedEventArgs))を実行させます。
                '' 細かく言うと変換していないままなのですが、動作的には型変換ができています。シグネチャが同じだからできることかもしれませんね。
                '' また、このような無理やりな方法は、おそらく一般的ではないと思います)
                '' 
                'With Nothing
    
                '    Dim listener1 = New LivetWeakEventListener(Of PropertyChangedEventHandler, PropertyChangedEventArgs)(
                '        Function(h As EventHandler(Of PropertyChangedEventArgs))
                '            Console.WriteLine("a")
                '            Return Sub(sender, e) h(sender, e)
                '        End Function,
                '        Sub(h As PropertyChangedEventHandler)
                '            Console.WriteLine("b")
                '            AddHandler Me.Person.PropertyChanged, h
                '        End Sub,
                '        Sub(h As PropertyChangedEventHandler)
                '            Console.WriteLine("c")
                '            RemoveHandler Me.Person.PropertyChanged, h
                '        End Sub,
                '        Sub(sender, e)
    
                '            Console.WriteLine("d")
                '            Me.Person_PropertyChanged(sender, e)
    
                '        End Sub)
    
                '    Dim listener1 = New LivetWeakEventListener(Of PropertyChangedEventHandler, PropertyChangedEventArgs)(
                '        Function(h As EventHandler(Of PropertyChangedEventArgs))
                '            Console.WriteLine("a")
                '            Return Sub(sender, e) h(sender, e)
                '        End Function,
                '        Sub(h As PropertyChangedEventHandler)
                '            Console.WriteLine("b")
                '            AddHandler Me.Person.PropertyChanged, h
                '        End Sub,
                '        Sub(h As PropertyChangedEventHandler)
                '            Console.WriteLine("c")
                '            RemoveHandler Me.Person.PropertyChanged, h
                '        End Sub,
                '        AddressOf Person_PropertyChanged)
    
                'End With
    
    
    
    
    
                ' LivetWeakEventListener クラスは IDisposable を実装している。
                ' Composite(コンポジット、複合という意味合い)Disposable メンバーに登録して、破棄してもらう
                ' よって、このクラスは、Using ステートメント間か、最後に Dispose メソッドを呼んで必ず破棄すること
                Me.CompositeDisposable.Add(listener1)
    
                Me.Person.Name = "jiro"
                Me.Person.Age = 45
    
            End Sub
    
            Public Sub MyDispose()
    
            End Sub
    
            Private Sub Person_PropertyChanged(sender As Object, e As PropertyChangedEventArgs)
    
                Dim value = Me.Person.GetType().GetProperty(e.PropertyName).GetValue(Me.Person, Nothing)
                ' 100万回出力されるのが辛いのでコメントアウト
                'Console.WriteLine($"{e.PropertyName} = {value} に変更されました")
    
            End Sub
    
            Public Sub Initialize()
            End Sub
    
        End Class
    
    End Namespace
    

  •  これを実行するのが以下の部分です。

  • ' 【Livet.EventListeners.WeakEvents.LivetWeakEventListener】+【Dispose】
    ' メモリリークは発生しない(即時解放)
    ' =LivetWeakEventListener だけならメモリリークは発生する
    ' WeakEvent と命名されているので、解放しなくてもいけると思うのだが、100万回はやりすぎだから?使い方間違えてる?
    Using vm = New Test9ViewModel(Me.Person)
    
    End Using
    

  •  PropertyChangedWeakEventListener クラスのジェネリック版が LivetWeakEventListener クラスです。EventListener クラスと同じ立ち位置ですね。 同じようにインスタンス生成して使います。この時に、イベントのデリゲート型とイベント引数クラス型を指定して扱いたいイベントを決定します。

  •  また、4つの引数もセットします。第一引数で、EventHandler(Of T) デリゲート型から TEventHandler デリゲート型への変換処理を渡してあげます。 ここでは、PropertyChanged イベントを扱いたいので、EventHandler(Of PropertyChangedEventArgs) から PropertyChangedEventHandler へ変換する処理をセットしています。 第二引数にイベント登録メソッドを、第三引数にイベント解除メソッドを、第四引数に紐づけたいイベントハンドラメソッドを指定します。 EventListener クラスの時と同じです。 これもIDisposable インターフェースを実装していますので、CompositeDisposable コレクションプロパティに、この戻り値をセットして使います。

  • イメージ

  •  仕組みと使い方さえ分かれば、普通のメソッド呼び出しです。第一引数がかなり難しいですが、しっかり理解してください。


ViewModelとModelの会話まとめ

  • 長かったメモリリークについての勉強が終わりましたので、会話方法をまとめます。 ViewModel から Model への会話は、モデルクラスに定義したメソッドを呼び出しておこないます。 Model から ViewModel への会話は、PropertyChanged イベントを発生させておこないます。

  • イベントを購読する際、「強いイベントパターン」と「弱いイベントパターン」の2種類でイベント購読することができます。 「弱いイベントパターン」を使うと、イベント解除処理をしなくても不要メモリが(いつのタイミングかは制御できないけど)解放されるので(この部分に関する)メモリリークの心配がありません。 「強いイベントパターン」を使うと、イベント解除処理を明示的におこなわないと不要メモリが溜まってきます。 ただパフォーマンスを求めるなら「強いイベントパターン」の方が早いですので、いちがいに「弱いイベントパターン」が最強とは言い切れません。

  • どちらにしろ自分で破棄できるときは破棄した方が安心できると思いますので、明示的な破棄を書くのがベターかなとというのが私の見解です。