VB のたまご

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



メモリリーク2

  •  前回の続きです。

  •  今回は、.NET 標準で組み込まれている「弱いイベントパターン」を使ってイベント登録してみましょう。 「弱い」って何?って思ってしまいますが、普段、動的にイベント登録する時に使う【AddHandler】は、【RemoveHandler】しないとイベント購読が解除されないことから、 「強いイベントパターン」と呼ばれています。これに対して、「弱いイベントパターン」を使うと【イベント登録】しっぱなしで【イベント解除】しなくても、メモリリークが発生しなくなります。 内部処理で、メモリ解放処理をやってくれるためです。イベントとイベントハンドラの結びつきが「弱い」ことから、このように呼ばれています。 すごく便利ですが、パフォーマンスが良い感じではないのが今一つです。100万回ループのせいな気もしますがorz。

  • スポンサーリンク



3つ目のビューモデル

    Imports LivetWPFApplication3.Models
    Imports System.ComponentModel
    Imports System.Windows
    
    Namespace ViewModels
        Public Class Test3ViewModel
            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
                WeakEventManager(Of INotifyPropertyChanged, PropertyChangedEventArgs).AddHandler(Me.Person, "PropertyChanged", AddressOf Me.Person_PropertyChanged)
                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
    

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

  • ' 【System.Windows.WeakEventManager(Of TSource, TEventArgs)】
    ' メモリリークは発生しない(遅延開放。ある程度溜まって、必要になったら解放)
    ' 弱いイベントと呼ばれている。解放処理をしなくてもメモリリークは発生しない
    ' ただし、一生懸命メモリをやりくりしているからか、ボタンクリックやプログラム終了処理の反応が遅くなる、応答無しでは無い。
    ' でも普通、100万回もイベント処理を登録しないので、実際はそんなに遅くないと思われる
    Using vm = New Test3ViewModel(Me.Person)
    
    End Using
    

  •  これを実行してもメモリリークは発生しません。WeakEventManager クラスは静的クラスで、イベントを定義しているクラスとイベント引数を受け持つジェネリッククラスです。 AddHandler メソッドを使ってイベント登録します。第一引数はイベントを持つクラスのインスタンス、第二引数はイベント名、第三引数はイベントハンドラを指定します。 見たらまたコメントアウトして戻しておくか、この部分を削除してしまいましょう。

  • スポンサーリンク



4つ目のビューモデル

    Imports LivetWPFApplication3.Models
    Imports System.ComponentModel
    
    Namespace ViewModels
        Public Class Test4ViewModel
            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
                PropertyChangedEventManager.AddHandler(Me.Person, AddressOf Me.Person_PropertyChanged, String.Empty)
                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
    

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

  • ' 【System.ComponentModel.PropertyChangedEventManager】のうち、AddHandler メソッドを利用。
    ' 以前からある? AddListener メソッドの簡易版。
    ' IWeakEventListener インターフェースを継承したクラスを準備しなくてもいいので楽
    ' メモリリークは発生しない(遅延開放。ある程度溜まって、必要になったら解放)
    ' 弱いイベントと呼ばれている。解放処理をしなくてもメモリリークは発生しない
    ' ただし、一生懸命メモリをやりくりしているからか、ボタンクリックやプログラム終了処理の反応が遅くなる、応答無しでは無い。
    ' でも普通、100万回もイベント処理を登録しないので、実際はそんなに遅くないと思われる
    Using vm = New Test4ViewModel(Me.Person)
    
    End Using
    

  •  これを実行してもメモリリークは発生しません。PropertyChangedEventManager クラスは静的クラスで、PropertyChanged イベント専用のリスナーになります。 変更通知プロパティに対して使う場合は、こちらの方が楽ですね。AddHandler メソッドには、イベントを持つクラスのインスタンス、紐づけたいイベントハンドラ、購読したいプロパティ名を指定します。 購読したいプロパティは、空欄を指定すると全プロパティを対象にイベントを監視するようになります。 見たらまたコメントアウトして戻しておくか、この部分を削除してしまいましょう。


5つ目のビューモデル

    Imports LivetWPFApplication3.Models
    Imports System.ComponentModel
    Imports System.Windows
    
    Namespace ViewModels
        Public Class Test5ViewModel
            Inherits ViewModel
            Implements IWeakEventListener
    
    #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
                'PropertyChangedEventManager.AddHandler(Me.Person, AddressOf Me.Person_PropertyChanged, String.Empty)
                PropertyChangedEventManager.AddListener(Me.Person, Me, String.Empty)
                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
    
            ' データ⇔イベントハンドラという直接的な購読ではなく、
            ' データ⇔代理人(IWeakEventListener)⇔イベントハンドラという間接的な購読にすることで、
            ' データとイベントハンドラの間には直接的な参照が発生しなくなる
            Public Function ReceiveWeakEvent(managerType As Type, sender As Object, e As EventArgs) As Boolean Implements IWeakEventListener.ReceiveWeakEvent
    
                Dim pe = CType(e, PropertyChangedEventArgs)
                Dim value = Me.Person.GetType().GetProperty(pe.PropertyName).GetValue(Me.Person, Nothing)
                ' 100万回出力されるのが辛いのでコメントアウト
                'Console.WriteLine($"{pe.PropertyName} = {value} に変更されました")
    
                Return True
    
            End Function
    
            Public Sub Initialize()
            End Sub
    
        End Class
    
    End Namespace
    

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

  • ' 【System.ComponentModel.PropertyChangedEventManager】のうち、AddListener メソッドを利用。
    ' メモリリークは発生しない(遅延開放。ある程度溜まって、必要になったら解放)
    ' 弱いイベントと呼ばれている。解放処理をしなくてもメモリリークは発生しない
    ' ただし、一生懸命メモリをやりくりしているからか、ボタンクリックやプログラム終了処理の反応が遅くなる、応答無しでは無い。
    ' でも普通、100万回もイベント処理を登録しないので、実際はそんなに遅くないと思われる
    Using vm = New Test5ViewModel(Me.Person)
    
    End Using
    

  •  先程と同じクラスですが、今度は AddListener メソッドを使ったバージョンです。機能も AddHandler メソッドと同じなのですが、作りが複雑なので注意です。 通常、イベント⇔イベントハンドラと2者間の関わりがあるのですが、AddListener メソッドでは3者間の関わりによって弱いイベントパターンを実現させています。 つまり、イベント⇔代理人(IWeakEventListener を実装したクラス)⇔イベントハンドラとすることで、イベント⇔イベントハンドという直接の関わりが無くなります。 見たらまたコメントアウトして戻しておくか、この部分を削除してしまいましょう。

  •  次回に続きます。