Właściwości zależności typu kolekcji

Ten temat zawiera wskazówki i sugerowane wzorce implementacji właściwości zależności, w której typ właściwości jest typem kolekcji.

Implementowanie właściwości zależności typu kolekcji

W przypadku właściwości zależności ogólnie wzorzec implementacji, który należy wykonać, jest definiowanie otoki właściwości CLR, gdzie ta właściwość jest wspierana przez DependencyProperty identyfikator, a nie pole lub inną konstrukcję. Ten sam wzorzec jest przestrzegany podczas implementowania właściwości typu kolekcji. Jednak właściwość typu kolekcji wprowadza pewną złożoność wzorca za każdym razem, gdy typ zawarty w kolekcji jest samą klasą lub Freezable pochodnąDependencyObject.

Inicjowanie kolekcji poza wartością domyślną

Podczas tworzenia właściwości zależności nie należy określać wartości domyślnej właściwości jako początkowej wartości pola. Zamiast tego należy określić wartość domyślną za pomocą metadanych właściwości zależności. Jeśli właściwość jest typem odwołania, wartość domyślna określona w metadanych właściwości zależności nie jest wartością domyślną na wystąpienie; Zamiast tego jest to wartość domyślna, która ma zastosowanie do wszystkich wystąpień typu. Dlatego należy zachować ostrożność, aby nie używać pojedynczej kolekcji statycznej zdefiniowanej przez metadane właściwości kolekcji jako wartości domyślnej dla nowo utworzonych wystąpień typu. Zamiast tego należy upewnić się, że celowo ustawisz wartość kolekcji na unikatową kolekcję (wystąpienie) w ramach logiki konstruktora klasy. W przeciwnym razie zostanie utworzona niezamierzona klasa singleton.

Rozważmy następujący przykład. W poniższej sekcji przykładu przedstawiono definicję klasy Aquarium, która zawiera wadę z wartością domyślną. Klasa definiuje właściwość AquariumObjectszależności typu kolekcji , która używa typu ogólnego List<T> z ograniczeniem FrameworkElement typu. W wywołaniu Register(String, Type, Type, PropertyMetadata) właściwości zależności metadane ustanawiają wartość domyślną jako nową ogólną List<T>.

Ostrzeżenie

Poniższy kod nie działa poprawnie.

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

Jeśli jednak po prostu pozostawiono kod, tak jak pokazano, ta wartość domyślna pojedynczej listy jest współdzielona dla wszystkich wystąpień programu Aquarium. Jeśli uruchomisz następujący kod testowy, który ma pokazać, jak utworzyć wystąpienie dwóch oddzielnych Aquarium wystąpień i dodać jeden inny Fish do każdego z nich, zobaczysz zaskakujący wynik:

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

Zamiast każdej kolekcji z liczbą, każda kolekcja ma liczbę dwóch! Jest to spowodowane tym, że każdy Aquarium dodał go Fish do kolekcji wartości domyślnych, co wynikało z pojedynczego wywołania konstruktora w metadanych i dlatego jest współużytkowane przez wszystkie wystąpienia. Ta sytuacja prawie nigdy nie jest tym, czego chcesz.

Aby rozwiązać ten problem, należy zresetować wartość właściwości zależności kolekcji do unikatowego wystąpienia w ramach wywołania konstruktora klasy. Ponieważ właściwość jest właściwością zależności tylko do odczytu, należy użyć SetValue(DependencyPropertyKey, Object) metody , aby ją ustawić, używając klasy DependencyPropertyKey , która jest dostępna tylko w klasie.

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

Teraz, jeśli ponownie uruchomisz ten sam kod testowy, zobaczysz więcej oczekiwanych wyników, gdzie każda z nich Aquarium obsługuje własną unikatową kolekcję.

W przypadku wybrania właściwości kolekcji właściwość read-write byłaby nieznaczna odmiana tego wzorca. W takim przypadku można wywołać metodę dostępu zestawu publicznego z konstruktora, aby wykonać inicjowanie, co nadal wywołuje podpis niekluczowy SetValue(DependencyProperty, Object) w otoce zestawu przy użyciu identyfikatora publicznego DependencyProperty .

Zmiany wartości powiązania raportowania z właściwości kolekcji

Właściwość kolekcji, która sama jest właściwością zależności, nie zgłasza automatycznie zmian w jej właściwościach podrzędnych. Jeśli tworzysz powiązania w kolekcji, może to uniemożliwić powiązanie ze zmianami raportowania, co spowoduje unieważnienie niektórych scenariuszy powiązań danych. Jeśli jednak używasz typu kolekcji jako typu FreezableCollection<T> kolekcji, właściwość podrzędna zmienia się na zawarte elementy w kolekcji są prawidłowo zgłaszane, a powiązanie działa zgodnie z oczekiwaniami.

Aby włączyć powiązanie właściwości podrzędnej w kolekcji obiektów zależności, utwórz właściwość kolekcji jako typ FreezableCollection<T>, z ograniczeniem typu dla tej kolekcji do dowolnej DependencyObject klasy pochodnej.

Zobacz też