2016-11-28 8 views
6

私はINotifyPropertyChangedインターフェイスを実装する基本クラスを作成しました。このクラスには、任意のプロパティの値を設定し、必要に応じてPropertyChangedイベントを発生させる汎用関数SetPropertyも含まれています。ByRefパラメータとしてのプロパティと変数

Public Class BaseClass 
    Implements INotifyPropertyChanged 

    Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged 

    Protected Function SetProperty(Of T)(ByRef storage As T, value As T, <CallerMemberName> Optional ByVal propertyName As String = Nothing) As Boolean 
     If Object.Equals(storage, value) Then 
      Return False 
     End If 

     storage = value 
     Me.OnPropertyChanged(propertyName) 
     Return True 
    End Function 

    Protected Overridable Sub OnPropertyChanged(<CallerMemberName> Optional ByVal propertyName As String = Nothing) 
     If String.IsNullOrEmpty(propertyName) Then 
      Throw New ArgumentNullException(NameOf(propertyName)) 
     End If 

     RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propertyName)) 
    End Sub 

End Class 

次に、いくつかのデータを保持するクラスがあります。簡単にするために、このプロパティには1つのプロパティ(この例では)のみが含まれています。

Public Class Item 
    Public Property Text As String 
End Class 

私は基本クラスから継承し、データ保持クラスを使用する3番目のクラスを持っています。この3番目のクラスは、WPFウィンドウ用のViewModelであるはずです。

RelayCommandクラスのコードはリストされていません。あなた自身が実装している可能性があります。このクラスは、コマンドが実行されると、指定された関数を実行することを覚えておいてください。

Public Class ViewModel 
    Inherits BaseClass 

    Private _text1 As Item 'data holding class 
    Private _text2 As String 'simple variable 
    Private _testCommand As ICommand = New RelayCommand(AddressOf Me.Test) 

    Public Sub New() 
     _text1 = New Item 
    End Sub 

    Public Property Text1 As String 
     Get 
      Return _text1.Text 
     End Get 
     Set(ByVal value As String) 
      Me.SetProperty(Of String)(_text1.Text, value) 
     End Set 
    End Property 

    Public Property Text2 As String 
     Get 
      Return _text2 
     End Get 
     Set(ByVal value As String) 
      Me.SetProperty(Of String)(_text2, value) 
     End Set 
    End Property 

    Public ReadOnly Property TestCommand As ICommand 
     Get 
      Return _testCommand 
     End Get 
    End Property 

    Private Sub Test() 
     Me.Text1 = "Text1" 
     Me.Text2 = "Text2" 
    End Sub 

End Class 

そして私は、そのDataContextとしてのViewModelクラスを使用して、私のWPFウィンドウを持っています。

<Window x:Class="MainWindow" 
     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
     xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
     xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
     xmlns:local="clr-namespace:WpfTest" 
     mc:Ignorable="d" 
     Title="MainWindow" Height="350" Width="525"> 
    <Window.DataContext> 
     <local:ViewModel /> 
    </Window.DataContext> 

    <StackPanel Orientation="Horizontal"> 
     <TextBox Text="{Binding Text1}" Height="24" Width="100" /> 
     <TextBox Text="{Binding Text2}" Height="24" Width="100" /> 
     <Button Height="24" Content="Fill" Command="{Binding TestCommand}" /> 
    </StackPanel> 
</Window> 

ご覧のとおり、このウィンドウには2つのテキストボックスと1つのボタンしか含まれていません。 TextBoxesはプロパティText1Text2にバインドされており、ボタンはコマンドTestCommandを実行することになっています。

コマンドが実行されると、プロパティーText1Text2の両方に値が与えられます。そして、両方のプロパティがPropertyChangedイベントを発生させるので、これらの値は私のウィンドウに表示されるはずです。

ただし、値 "Text2"のみが私のウィンドウに表示されます。

プロパティText1の値は "Text1"ですが、プロパティが値を取得する前にこのプロパティのPropertyChangedイベントが発生しているようです。

プロパティが値を取得した後PropertyChangedを呼び出すために私の基本クラスのSetProperty関数を変更する方法はありますか?

ありがとうございました。

+1

http://stackoverflow.com/a/4520101/17034 –

答えて

4

実際にはどうなりますか?

プロパティはフィールドとして動作しないため、これは機能しません。 を実行すると、フィールドの参照の代わりに_text2が渡されるため、SetProperty関数は参照内の内容を変更してフィールドを変更することができます。あなたがMe.SetProperty(Of String)(_text1.Text, value)を行う際

しかし、コンパイラはプロパティのゲッターを見ているので、まず、パラメータとして戻り値への参照を渡す、は_text1のプロパティを取得します呼び出します。したがって、SetPropertyファンクションがByRefパラメータを受け取っているときは、ゲッタからの戻り値はであり、実際のフィールド値はではありません。

私はhereを理解しているので、あなたのプロパティがByRefだと言うと、コンパイラは関数呼び出しを終了するときにフィールドrefを自動的に変更します...それはなぜあなたのイベントの後に変化しているのかを説明します...

This other blogこの奇妙な動作を確認しているようです。

+0

しかし、プロパティの値は変更されますが、関数SetPropertyが残っている場合に限ります。 – Nostromo

+0

この記事でよく分かります。公式であろうとなかろうと、 'Item.Text'明示的なgetter/setterに' Trace.WriteLine() 'をそれぞれ指定すると、私が見ている動作を正確に記述します。 –

3

C#では、同等のコードはコンパイルされませんでした。 .NETは参考文献としてプロパティを渡すのが面倒ではありません。なぜなら、Eric Lippertのような人々は別の場所に行ってしまっているからです(私は、EricがCのどこかにある問題を解決していますが、 C#チームが受け入れられないと考える欠点があります)。

VBのようになりますが、ちょっと変わった特殊なケースです:私が見ている動作は、参照渡しの一時変数を作成してから、その値をプロパティメソッドが完了します。これは回避策です(後述のエリック・リッペルト自身のコメント、@Martin Verjansの優れた答えも参照)。byref/refが.NETでどのように実装されているかわからない人にとっては直感的ではない副作用があります。

VB.NETとC#(とF#、IronPythonなどのため、できません。)は互いに互換性がある必要がありますので、VB ByRefパラメータはC#コードから渡されたC#ref引数と互換性がなければなりません。したがって、すべての回避策は完全に呼び出し元の責任である必要があります。正気の範囲内で、それはコールが始まる前と戻った後にできることにそれを制限します。ここで

ECMA 335 (Common Language Infrastructure) standardが( "byref" のはCtrl + F検索を)言っているものです。

  • § I.8.2.1.1  マネージドポインタと関連する型

    A 管理されたポインタ(§I.12.1.1.2)、またはの参照(§I.8.6.1.3、§I.12.4.1.5.2)は、局所変数、パラメータ、複合型のフィールド、または配列の要素です。言い換える...

、限りコンパイラに関しては、ByRef storage As Tは実際のコードは、値を置くメモリ内の記憶場所のアドレスです。実行時には非常に効率的ですが、getterやsetterを使った構文的砂糖魔法の範囲はありません。プロパティは、のメソッド、ゲッタとセッタ(もちろん、どちらか一方のみ)です。

したがって、storageSetProperty()の新しい値を取得し、SetProperty()の完了後には_text1.Textに新しい値が設定されます。しかし、コンパイラは、あなたが期待するものではないイベントの実際のシーケンスを引き起こすいくつかのオカルトの宣言を導入しました。

結果としてSetPropertyは、あなたが書いた方法では使用できません。私がテストした最も簡単な修正は、Text1のセッターでOnPropertyChanged()に直接電話することです。

Public Property Text1 As String 
    Get 
     Return _text1.Text 
    End Get 
    Set(ByVal value As String) 
     _text1.Text = value 
     Me.OnPropertyChanged() 
    End Set 
End Property 

少なくとも少し醜いことはありませんが、これを処理する方法はありません。あなたはText2のような普通のバッキングフィールドをText1に与えることができますが、その後は_text1.Textと同期させておく必要があります。これは、上記のIMOよりも醜いです。なぜなら、あなたは2つを同期させておく必要がありますし、Text1セッターにも余分なコードが残っているからです。

+0

VB.NETでプロパティを渡すことが不可能かどうかはわかりません。私はちょうど与えられたシナリオをテストし、参照として渡されるときプロパティの値が変更されます。 – Streamline

+0

@ストリームラインありがとう、私は今いくつかのテストコードを書いています。 –

+0

@Streamlineあなたが正しいです、それは値を変更しますが、 'SetProperty'が終了した後で、OPが記述しているのとまったく同じです。非常に奇妙な。 –

関連する問題