コレクション型依存関係プロパティ

ここでは、プロパティの型がコレクション型である場合に依存関係プロパティを実装する方法についての、ガイダンスと推奨されるパターンを示します。

コレクション型依存関係プロパティの実装

一般に、依存関係プロパティの場合、従う実装パターンは、CLR プロパティ ラッパーを定義するものです。この場合、そのプロパティは、フィールドや他のコンストラクトではなく DependencyProperty 識別子によってサポートされます。 コレクション型プロパティを実装するときは、これと同じパターンに従います。 ただし、コレクションに含まれる型自体が DependencyObject または Freezable の派生クラスである場合は常に、コレクション型プロパティを使用することでパターンは複雑になります。

既定値を上回るコレクションの初期化

依存関係プロパティを作成するときは、初期フィールド値としてプロパティの既定値を指定しません。 代わりに、依存関係プロパティのメタデータを使用して既定値を指定します。 プロパティが参照型の場合、依存関係プロパティのメタデータで指定する既定値はインスタンスごとの既定値ではありません。その型のすべてのインスタンスに適用される既定値です。 したがって、コレクション プロパティ メタデータによって定義される単一の静的コレクションを、その型の新しく作成されるインスタンスの作業既定値として使用しないように注意してください。 代わりに、クラス コンストラクターのロジックの一部として、コレクションの値に一意 (インスタンス) のコレクションを意図的に設定する必要があります。 それ以外の場合は、意図しないシングルトン クラスを作成することになります。

例を次に示します。 この例は、クラス Aquarium の定義を示しています。これには、既定値の欠点があります。 このクラスで定義されているコレクション型依存関係プロパティ AquariumObjects は、FrameworkElement 型の制約でジェネリック List<T> 型を使用します。 依存関係プロパティに対する Register(String, Type, Type, PropertyMetadata) の呼び出しでは、メタデータによって、既定値が新しいジェネリック List<T> に設定されます。

警告

次のコードは正しく動作しません。

public class Fish : FrameworkElement { }
public class Aquarium : DependencyObject {
    private static readonly DependencyPropertyKey AquariumContentsPropertyKey =
        DependencyProperty.RegisterReadOnly(
          "AquariumContents",
          typeof(List<FrameworkElement>),
          typeof(Aquarium),
          new FrameworkPropertyMetadata(new List<FrameworkElement>())
        );
    public static readonly DependencyProperty AquariumContentsProperty =
        AquariumContentsPropertyKey.DependencyProperty;

    public List<FrameworkElement> AquariumContents
    {
        get { return (List<FrameworkElement>)GetValue(AquariumContentsProperty); }
    }

    // ...
}
Public Class Fish
    Inherits FrameworkElement
End Class
Public Class Aquarium
    Inherits DependencyObject
    Private Shared ReadOnly AquariumContentsPropertyKey As DependencyPropertyKey = DependencyProperty.RegisterReadOnly("AquariumContents", GetType(List(Of FrameworkElement)), GetType(Aquarium), New FrameworkPropertyMetadata(New List(Of FrameworkElement)()))
    Public Shared ReadOnly AquariumContentsProperty As DependencyProperty = AquariumContentsPropertyKey.DependencyProperty

    Public ReadOnly Property AquariumContents() As List(Of FrameworkElement)
        Get
            Return CType(GetValue(AquariumContentsProperty), List(Of FrameworkElement))
        End Get
    End Property

    ' ...

End Class

ただし、コードをこのままにすると、単一のリストの既定値が Aquarium のすべてのインスタンスで共有されます。 次のテスト コードは、2 つの異なる Aquarium インスタンスをインスタンス化し、それぞれに単一の異なる Fish を追加する方法を示していますが、これを実行すると、想定外の結果になります。

Aquarium myAq1 = new Aquarium();
Aquarium myAq2 = new Aquarium();
Fish f1 = new Fish();
Fish f2 = new Fish();
myAq1.AquariumContents.Add(f1);
myAq2.AquariumContents.Add(f2);
MessageBox.Show("aq1 contains " + myAq1.AquariumContents.Count.ToString() + " things");
MessageBox.Show("aq2 contains " + myAq2.AquariumContents.Count.ToString() + " things");
Dim myAq1 As New Aquarium()
Dim myAq2 As New Aquarium()
Dim f1 As New Fish()
Dim f2 As New Fish()
myAq1.AquariumContents.Add(f1)
myAq2.AquariumContents.Add(f2)
MessageBox.Show("aq1 contains " & myAq1.AquariumContents.Count.ToString() & " things")
MessageBox.Show("aq2 contains " & myAq2.AquariumContents.Count.ToString() & " things")

各コレクションのカウントが 1 になるのではなく、いずれのコレクションもカウントが 2 になります。 これは、各 AquariumFish が既定値のコレクションに追加されますが、その起因となっているのがメタデータでの単一のコンストラクター呼び出しであり、したがって、すべてのインスタンス間で共有されるためです。 これは望ましい状況ではありません。

この問題を解決するには、クラス コンストラクターの呼び出しの一部として、コレクションの依存関係プロパティの値を一意のインスタンスにリセットする必要があります。 プロパティは読み取り専用の依存関係プロパティであるため、クラス内だけでアクセスできる DependencyPropertyKey を使用し、SetValue(DependencyPropertyKey, Object) メソッドを使用して設定します。

public Aquarium() : base()
{
    SetValue(AquariumContentsPropertyKey, new List<FrameworkElement>());
}
Public Sub New()
    MyBase.New()
    SetValue(AquariumContentsPropertyKey, New List(Of FrameworkElement)())
End Sub

再びテスト コードを実行すると、結果は期待したものに近くなり、各 Aquarium が独自の一意のコレクションをサポートします。

コレクション プロパティを読み取り/書き込みにすると、このパターンには若干のバリエーションが発生します。 この場合は、コンストラクターからパブリック set アクセサーを呼び出して初期化を行うことができ、パブリック DependencyProperty 識別子を使用して、set ラッパー内で SetValue(DependencyProperty, Object) のキーなしのシグネチャを呼び出します。

コレクション プロパティからのバインディング値変更の報告

それ自体が依存関係プロパティであるコレクション プロパティは、変更をサブプロパティに自動的には報告しません。 コレクションにバインディングを作成している場合は、これによってバインディングが変更を報告しないことがあり、一部のデータ バインディング シナリオが無効になります。 ただし、コレクション型として FreezableCollection<T> を使用している場合は、コレクションに含まれる要素に対するサブプロパティの変更は正しく報告され、バインディングは予期したとおりに動作します。

依存関係オブジェクト コレクションでサブプロパティのバインディングを有効にするには、FreezableCollection<T> 型としてコレクション プロパティを作成し、そのコレクションの型制約を DependencyObject 派生クラスに指定します。

関連項目