2017-01-24 6 views
1

プロパティのバッキングフィールドにWithEvents修飾語がある場合、値の割り当てはより良い単語がないため「遅れる」ことがあります。私は簡単なデモでの動作を再現しましたので、WithEventsの目的は、ここでは明らかではありません(したがって、「ちょうどそれを取り除く」と言って建設的ではありません)WithEventsのバッキングフィールドへの割り当ての遅延

Public Class ItemViewModel 
    Public Property Id As Integer 
End Class 

Public Class ViewModel 
    Inherits ViewModelBase 

    Private WithEvents _item As ItemViewModel = New ItemViewModel() With {.Id = 0} 
    Public Property Item As ItemViewModel 
     Get 
      Return _item 
     End Get 
     Set(value As ItemViewModel) 
      SetProperty(_item, value) 
     End Set 
    End Property 
... 

SetProperty定義を:iをインクリメントIDを持つ新しいアイテムであることをItemプロパティを更新すると予想されるように

Protected Function SetProperty(Of T)(ByRef field As T, value As T, <CallerMemberName> Optional name As String = Nothing) As Boolean 
    If (EqualityComparer(Of T).Default.Equals(field, value)) Then 
     Return False 
    End If 
    field = value 
    NotifyPropertyChanged(name) 
    Return True 
End Function 

、プロパティのゲッターは、とすぐにイベントが発生するようにヒットします。しかし、バッキングフィールドの値はまだ古い値です! SetPropertyコールの直後に別のPropertyChangedイベントを追加すると、バッキングフィールドはその時点で正しい値になります。もちろん、WithEventsを取り出すと、1つのイベントで期待どおりに動作します。

これは私がSetPropertyがこのような方法で失敗するのを見た唯一の時間です。 WithEventsが引き起こしている問題は何ですか?

更新:ViewModelが、INotifyPropertyChangedを直接実装すると、値を設定した後に基底から継承する代わりに、PropertyChangedを生成します。

答えて

4

WithEventsは、.NET Framework自体がネイティブにサポートしていない機能です。 VB.NETは.NET上で実装しています。この機能は、VB6でも提供されています。しかし、COMと.NET間のイベントモデルの根本的な違いのために、VB6で機能が実装された方法は非常に異なります。

VB6でどのように機能が実装されたかについては触れません。それは本当に関連性がありません。重要なのは、.NETでイベントがどのように機能するかです。基本的に、.NETでは、イベントは明示的にフックされ、アンフックされなければなりません。イベントが定義されると、プロパティの定義方法には多くの類似点があります。特に、プロパティに「set」メソッドと「get」メソッドの対称性と同様に、ハンドラをイベントに追加するメソッドと、ハンドラを削除するメソッドがあります。

イベントがこのようなメソッドを使用する理由は、外部の呼び出し元から接続されたハンドラのリストを非表示にするためです。クラス外のコードが、添付されたハンドラの完全なリストにアクセスできる場合、それが干渉する可能性があり、非常に混乱する可能性のあるプログラミング慣行が非常に貧弱です。

VB.NETは、AddHandlerRemoveHandler演算子を使用して、これらのイベントの「追加」および「削除」メソッドへの直接呼び出しを公開します。 C#では、正確に同じ基礎となる操作は、+=-=演算子を使用して表現されます。ここで、左側の引数はイベントメンバー参照です。

WithEventsは、AddHandlerRemoveHandlerの呼び出しを隠す構文糖です。認識しておくべき重要なことは、のコールが依然としてであることです。これらは暗黙的です。だから、

、あなたがこのようなコードを記述します。

Private WithEvents _obj As ClassWithEvents 

Private Sub _obj_GronkulatedEvent() Handles _obj.GronkulatedEvent 
    ... 
End Sub 

..youは、VBを求めています。NETを_obj(いつでもオブジェクト参照を変更することができます念頭に置いて)に割り当てられていることを確認するには、GronkulatedEventイベントはそのSubで処理する必要があります。参照を変更する場合は、古いオブジェクトのGronkulatedEventをすぐに切り離し、新しいオブジェクトのGronkulatedEventを添付する必要があります。

VB.NETはフィールドをプロパティに変換することでこれを実装しています。 WithEventsを追加すると、フィールド_obj(または、場合によっては_item)がであり、実際にはフィールドではありません。秘密裏フィールドを作成し、その後_itemされるが、実装がこのようになりますプロパティを次のようになります。

だから、
Private __item As ItemViewModel ' Notice this, the actual field, has two underscores 

Private Property _item As ItemViewModel 
    <CompilerGenerated> 
    Get 
    Return __item 
    End Get 
    <CompilerGenerated, MethodImpl(Synchronized)> 
    Set(value As ItemViewModel) 
    Dim previousValue As ItemViewModel = __item 

    If previousValue IsNot Nothing Then 
     RemoveHandler previousValue.GronkulatedEvent, AddressOf _item_GronkulatedEvent 
    End If 

    __item = value 

    If value IsNot Nothing Then 
     AddHandler value.GronkulatedEvent, AddressOf _item_GronkulatedEvent 
    End If 
    End Set 
End Property 

、なぜこれはあなたが見る「ラグ」を引き起こしていますか?まあ、あなたは "ByRef"プロパティを渡すことはできません。何か "ByRef"を渡すには、そのメモリアドレスを知る必要がありますが、プロパティは "get"と "set"メソッドの背後にあるメモリアドレスを隠します。 C#のような言語では、コンパイル時エラーが発生します。プロパティはL値ではないため、参照は渡せません。しかし、VB.NETはより寛容で、あなたのために機能するように、舞台裏で余分なコードを書いています。あなたのコードで

、あなたはフィールドなので、それは、新しい値を書き込むことができ、パラメータByRefを取るSetProperty_itemメンバー、どのように見えるか渡しています。しかし、WithEventsのため、_itemメンバーは本当にプロパティです。だから、VB.NETは何をしますか? WithEventsはプロパティに、あなたのフィールドを変換しているため、VB.NETは、実際のを延期しなければならなかった、だから、

Public Property Item As ItemViewModel 
    Get 
    Return _item ' This is actually a property returning another property -- two levels of properties wrapping the actual underlying field -- but VB.NET hides this from you 
    End Get 
    Set 
    ' You wrote: SetProperty(_item, value) 
    ' But the actual code emitted by the compiler is: 
    Dim temporaryLocal As ItemViewModel = _item ' Read from the property -- a call to its Get method 

    SetProperty(temporaryLocal, value) ' SetProperty gets the memory address of the local, so when it makes the assignment, it is actually writing to this local variable, not to the underlying property 

    _item = temporaryLocal ' Once SetProperty returns, this extra "glue" code passes the value back off to the property, calling its Set method 
    End Set 
End Property 

:それはSetPropertyへの呼び出しのための一時的なローカル変数を作成し、呼び出し後のプロパティに戻ってそれを割り当てSetPropertyへの呼び出しが返るまでプロパティへの代入。

希望は意味があります! :-)

+1

ありがとうジョナサン! –

+0

フォローアップ:IDisposableの使用を禁止的に困難にする従来の問題があります。私たちはIDisposableを実装することができず、残りのハンドルをクリーンアップするためにDispose()が呼び出されることに依存しているため、WithEventsを使用するとこれを回避して、クリーンアップを処理するという考えがありました。 ** 1)** WithEventsは、オブジェクトがもはや使用されていないときに実際にハンドルを解放するので、正しくガベージコレクションされますか? ** 2)**オブジェクトが不要になったときにハンドルをリークすることなくAddHandlerとRemoveHandlerを使用できるようにするIDisposableの代替手段はありますか? –

+1

ええええええええええええええええええええええええええええええええええええええええええええええええええええええええええええええええええええええええええええええええええええええええええええええええええええええ>>おかあさんは、実際に廃棄する必要があるオブジェクトが処分されることを確実にする方法は1つだけあり、それは 'Dispose'メソッドを呼び出すことです。コードの特定のスコープでオブジェクトを使用している場合、これを 'Using'ステートメントで自動的に行うことができます。 –

関連する問題