集合類型的相依性屬性

本主題提供如何實作屬性類型為集合類型之相依性屬性的指引和建議模式。

實作集合類型相依性屬性

對於一般相依性屬性,您遵循的實作模式是定義 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 的所有執行個體共用。 如果執行下列測試程式碼,它原是要示範如何具現化兩個不同的 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")

不是每個集合都有一個計數,而是每個集合都有兩個計數! 這是因為每個 Aquarium 都將自己的 Fish 新增至預設值集合,由中繼資料中的單一建構函式呼叫所致,因此在所有執行個體之間共用。 這種情況根本就不是您想要的。

若要修正這個問題,您必須將集合相依性屬性值重設為唯一的執行個體,當成類別建構函式呼叫的一部分。 因為 屬性是唯讀相依性屬性,所以您可以使用 SetValue(DependencyPropertyKey, Object) 方法來設定它,方法是使用 DependencyPropertyKey 只能在 類別記憶體取的 。

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

現在,如果再次執行相同的測試程式碼,您會看到更符合預期的結果,其中每個 Aquarium 都支援自己的唯一集合。

如果選擇讓您的集合屬性為可讀寫,則這個模式可能要略加修改。 在此情況下,您可以從建構函式呼叫公用集合存取子來執行初始化,這仍會使用公用 DependencyProperty 識別碼呼叫集合包裝函式內的 非索引鍵簽章 SetValue(DependencyProperty, Object)

報告集合屬性中的繫結值變更

本身為相依性屬性的集合屬性不會自動報告其子屬性變更。 如果您要建立集合繫結,這可以防止繫結報告變更,因而使某些資料繫結案例失效。 不過,如果您使用集合類型做為集合類型 FreezableCollection<T> ,則會正確報告集合中所含專案的子屬性變更,而且系結會如預期般運作。

若要在相依性物件集合中啟用子屬性系結,請使用該集合的類型條件約束建立集合屬性 FreezableCollection<T> 至任何 DependencyObject 衍生類別。

另請參閱