集合类型依赖项属性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. 但是,只要集合中包含的类型本身就是 DependencyObjectFreezable 派生类,则集合类型属性会给模式带来一些复杂性。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的集合类型依赖项属性,该属性使用具有 FrameworkElement 类型约束的泛型 List<T> 类型。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. 如果运行以下测试代码(用于演示如何实例化两个单独的 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")

每个集合具有两个计数,而不是一个!Instead of each collection having a count of one, each collection has a count of two! 这是因为,每个 Aquarium 将其 Fish 添加到默认值集合,此默认值集合因元数据中单个构造函数调用而产生,因此会在所有实例之间共享。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. 由于属性是只读依赖属性,因此可以使用 SetValue(DependencyPropertyKey, Object) 方法来设置它,使用只能在类中访问的 DependencyPropertyKeyBecause 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. 在这种情况下,你可以从构造函数调用公共集访问器来执行初始化,这仍将使用公共 DependencyProperty 标识符在你的集包装内调用 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