コレクション型依存関係プロパティCollection-Type Dependency Properties

ここでは、プロパティの型がコレクション型である場合に依存関係プロパティを実装する方法についての、ガイダンスと推奨されるパターンを示します。This topic provides guidance and suggested patterns for how to implement a dependency property where the type of the property is a collection type.

コレクション型依存関係プロパティの実装Implementing a Collection-Type Dependency Property

一般に、依存関係プロパティの場合は、CLR プロパティラッパーを定義します。このラッパーでは、フィールドやその他のコンストラクトではなく DependencyProperty 識別子によってプロパティがサポートされます。For a dependency property in general, the implementation pattern that you follow is that you define a CLR property wrapper, where that property is backed by a DependencyProperty identifier rather than a field or other construct. コレクション型プロパティを実装するときは、これと同じパターンに従います。You follow this same pattern when you implement a collection-type property. ただし、コレクション型のプロパティは、コレクション内に含まれる型自体が DependencyObject または Freezable 派生クラスである場合に、パターンの複雑さをいくつか生み出します。However, a collection-type property introduces some complexity to the pattern whenever the type that is contained within the collection is itself a DependencyObject or Freezable derived class.

既定値を上回るコレクションの初期化Initializing the Collection Beyond the Default Value

依存関係プロパティを作成するときは、初期フィールド値としてプロパティの既定値を指定しません。When you create a dependency property, you do not specify the property default value as the initial field value. 代わりに、依存関係プロパティのメタデータを使用して既定値を指定します。Instead, you specify the default value through the dependency property metadata. プロパティが参照型の場合、依存関係プロパティのメタデータで指定する既定値はインスタンスごとの既定値ではありません。その型のすべてのインスタンスに適用される既定値です。If your property is a reference type, the default value specified in dependency property metadata is not a default value per instance; instead it is a default value that applies to all instances of the type. したがって、コレクション プロパティ メタデータによって定義される単一の静的コレクションを、その型の新しく作成されるインスタンスの作業既定値として使用しないように注意してください。Therefore you must be careful to not use the singular static collection defined by the collection property metadata as the working default value for newly created instances of your type. 代わりに、クラス コンストラクターのロジックの一部として、コレクションの値に一意 (インスタンス) のコレクションを意図的に設定する必要があります。Instead, you must make sure that you deliberately set the collection value to a unique (instance) collection as part of your class constructor logic. それ以外の場合は、意図しないシングルトン クラスを作成することになります。Otherwise you will have created an unintentional singleton class.

例を次に示します。Consider the following example. この例の次のセクションでは、Aquariumクラスの定義を示します。これには、既定値を持つ欠陥が含まれています。The following section of the example shows the definition for a class Aquarium, which contains a flaw with the default value. クラスは、コレクション型の依存関係プロパティ AquariumObjectsを定義します。このプロパティは、ジェネリック List<T> 型を FrameworkElement 型制約と共に使用します。The class defines the collection type dependency property AquariumObjects, which uses the generic List<T> type with a FrameworkElement type constraint. 依存関係プロパティの Register(String, Type, Type, PropertyMetadata) の呼び出しでは、メタデータによって既定値が新しいジェネリック List<T>に設定されます。In the Register(String, Type, Type, PropertyMetadata) call for the dependency property, the metadata establishes the default value to be a new generic List<T>.

警告

次のコードは正しく動作しません。The following code does not behave correctly.

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 のすべてのインスタンスで共有されます。However, if you just left the code as shown, that single list default value is shared for all instances of Aquarium. 次のテスト コードは、2 つの異なる Aquarium インスタンスをインスタンス化し、それぞれに単一の異なる Fish を追加する方法を示していますが、これを実行すると、想定外の結果になります。If you ran the following test code, which is intended to show how you would instantiate two separate Aquarium instances and add a single different Fish to each of them, you would see a surprising result:

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 になります。Instead of each collection having a count of one, each collection has a count of two! これは、各 AquariumFish が既定値のコレクションに追加されますが、その起因となっているのがメタデータでの単一のコンストラクター呼び出しであり、したがって、すべてのインスタンス間で共有されるためです。This is because each Aquarium added its Fish to the default value collection, which resulted from a single constructor call in the metadata and is therefore shared between all instances. これは望ましい状況ではありません。This situation is almost never what you want.

この問題を解決するには、クラス コンストラクターの呼び出しの一部として、コレクションの依存関係プロパティの値を一意のインスタンスにリセットする必要があります。To correct this problem, you must reset the collection dependency property value to a unique instance, as part of the class constructor call. プロパティは読み取り専用の依存関係プロパティであるため、クラス内でのみアクセス可能な DependencyPropertyKey を使用して、SetValue(DependencyPropertyKey, Object) メソッドを使用して設定します。Because the property is a read-only dependency property, you use the SetValue(DependencyPropertyKey, Object) method to set it, using the DependencyPropertyKey that is only accessible within the class.

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

再びテスト コードを実行すると、結果は期待したものに近くなり、各 Aquarium が独自の一意のコレクションをサポートします。Now, if you ran that same test code again, you could see more expected results, where each Aquarium supported its own unique collection.

コレクション プロパティを読み取り/書き込みにすると、このパターンには若干のバリエーションが発生します。There would be a slight variation on this pattern if you chose to have your collection property be read-write. その場合は、コンストラクターからパブリック set アクセサーを呼び出して初期化を行うことができます。この場合も、パブリック DependencyProperty 識別子を使用して、set ラッパー内の SetValue(DependencyProperty, Object) の非キー署名を呼び出します。In that case, you could call the public set accessor from the constructor to do the initialization, which would still be calling the nonkey signature of SetValue(DependencyProperty, Object) within your set wrapper, using a public DependencyProperty identifier.

コレクション プロパティからのバインディング値変更の報告Reporting Binding Value Changes from Collection Properties

それ自体が依存関係プロパティであるコレクション プロパティは、変更をサブプロパティに自動的には報告しません。A collection property that is itself a dependency property does not automatically report changes to its subproperties. コレクションにバインディングを作成している場合は、これによってバインディングが変更を報告しないことがあり、一部のデータ バインディング シナリオが無効になります。If you are creating bindings into a collection, this can prevent the binding from reporting changes, thus invalidating some data binding scenarios. ただし、コレクション型 FreezableCollection<T> をコレクション型として使用すると、コレクション内の含まれている要素に対するサブプロパティの変更が適切に報告され、バインディングは正常に機能します。However, if you use the collection type FreezableCollection<T> as your collection type, then subproperty changes to contained elements in the collection are properly reported, and binding works as expected.

依存関係オブジェクトコレクションでサブプロパティバインドを有効にするには、コレクションプロパティを型 FreezableCollection<T>として作成し、そのコレクションの型制約を任意の DependencyObject 派生クラスに設定します。To enable subproperty binding in a dependency object collection, create the collection property as type FreezableCollection<T>, with a type constraint for that collection to any DependencyObject derived class.

関連項目See also