Vlastnosti závislostí typu kolekce

Toto téma obsahuje pokyny a navrhované vzory pro implementaci vlastnosti závislosti, kde typ vlastnosti je typ kolekce.

Implementace vlastnosti závislosti typu kolekce

Obecně platí, že vzor implementace, který sledujete, je definovat obálku vlastností CLR, kde tato vlastnost je podporována identifikátorem DependencyProperty , nikoli polem nebo jiným konstruktorem. Stejný vzor použijete při implementaci vlastnosti typu kolekce. Vlastnost typu kolekce však představuje určitou složitost vzoru vždy, když typ, který je obsažen v kolekci, je sama o sobě DependencyObject nebo Freezable odvozená třída.

Inicializace kolekce nad rámec výchozí hodnoty

Při vytváření vlastnosti závislosti nezadáte výchozí hodnotu vlastnosti jako počáteční hodnotu pole. Místo toho zadáte výchozí hodnotu prostřednictvím metadat vlastností závislosti. Pokud je vaše vlastnost referenčním typem, výchozí hodnota zadaná v metadatech vlastností závislostí není výchozí hodnotou pro každou instanci; je to výchozí hodnota, která se vztahuje na všechny instance typu. Proto musíte být opatrní, abyste nepoužíli jedinečnou statickou kolekci definovanou metadaty vlastnosti kolekce jako pracovní výchozí hodnotu pro nově vytvořené instance vašeho typu. Místo toho je nutné, abyste záměrně nastavili hodnotu kolekce na jedinečnou kolekci (instance) jako součást logiky konstruktoru třídy. Jinak vytvoříte neúmyslnou třídu singletonu.

Představte si následující příklad. Následující část příkladu ukazuje definici třídy Aquarium, která obsahuje chybu s výchozí hodnotou. Třída definuje vlastnost AquariumObjectszávislosti typu kolekce , která používá obecný List<T> typ s FrameworkElement omezením typu. Register(String, Type, Type, PropertyMetadata) Ve volání vlastnosti závislosti metadata vytvoří výchozí hodnotu jako nový obecný List<T>.

Upozorňující

Následující kód se nechová správně.

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

Pokud jste ale právě opustili kód, jak je znázorněno, tato výchozí hodnota seznamu je sdílena pro všechny instance Aquarium. Pokud jste spustili následující testovací kód, který má ukázat, jak byste vytvořili instanci dvou samostatných Aquarium instancí a přidali do každého z nich jeden jiný Fish , uvidíte překvapivý výsledek:

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")

Místo toho, aby každá kolekce měla jeden počet, má každá kolekce počet dvou! Důvodem je to, že každá Aquarium přidala do Fish výchozí kolekce hodnot, která byla výsledkem jediného volání konstruktoru v metadatech a je proto sdílena mezi všemi instancemi. Tato situace je téměř nikdy to, co chcete.

Chcete-li tento problém opravit, je nutné obnovit hodnotu vlastnosti závislostí kolekce na jedinečnou instanci, jako součást volání konstruktoru třídy. Vzhledem k tomu, že je vlastnost závislostí jen pro čtení, použijete metodu SetValue(DependencyPropertyKey, Object) k jeho nastavení pomocí DependencyPropertyKey , která je přístupná pouze v rámci třídy.

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

Pokud jste teď spustili stejný testovací kód znovu, mohli byste vidět očekávané výsledky, kdy každá Aquarium podporovala svou vlastní jedinečnou kolekci.

Pokud jste se rozhodli, že vlastnost kolekce bude pro čtení i zápis, bude tento model mírně proměnlivý. V takovém případě můžete volat objekt veřejné sady z konstruktoru k inicializaci, který bude stále volat neklíčový SetValue(DependencyProperty, Object) podpis v rámci obálky sady pomocí veřejného DependencyProperty identifikátoru.

Vytváření sestav změn hodnoty vazby z vlastností kolekce

Vlastnost kolekce, která je sama o sobě vlastností závislosti, nehlásí automaticky změny v jeho dílčích obdobích. Pokud vytváříte vazby do kolekce, může to zabránit vytváření zpráv o změnách vazby, a tím zneplatnit některé scénáře datových vazeb. Pokud však jako typ kolekce použijete typ FreezableCollection<T> kolekce, pak se správně ohlásí dílčí změny obsažených prvků v kolekci a vazba funguje podle očekávání.

Chcete-li povolit vazbu subproperty v kolekci objektů závislostí, vytvořte vlastnost kolekce jako typ FreezableCollection<T>s omezením typu pro danou kolekci na jakoukoli DependencyObject odvozenou třídu.

Viz také