Свойства зависимостей типа коллекции

Этот раздел содержит рекомендации и примеры шаблонов для реализации свойства зависимостей, где типом свойства является коллекция.

Реализация свойства зависимостей типа "коллекция"

В общем случае реализация свойств зависимостей выполняется путем определения оболочки свойства CLR, где этому свойству назначается идентификатор DependencyProperty, а не поле или другая конструкция. При реализации свойства типа "коллекция" вы следуете этому же шаблону. Однако свойство типа "коллекция" некоторым образом усложняет шаблон, если тип, содержащийся в коллекции, сам является производным классом DependencyObject или Freezable.

Инициализация коллекции за пределами значения по умолчанию

При создании свойства зависимостей вы не указываете значение свойства по умолчанию в качестве начального значения поля. Вместо этого значение по умолчанию указывается через метаданные свойства зависимостей. Если свойство является ссылочным типом, значение по умолчанию, заданное в метаданных свойства зависимостей, не является значением по умолчанию для каждого экземпляра. Напротив, это значение по умолчанию, которое применяется ко всем экземплярам типа. Поэтому необходимо избегать использования единственной статической коллекции, определенной метаданными свойства коллекции, в качестве рабочего значения по умолчанию для вновь создаваемых экземпляров типа. Вместо этого необходимо явно задать в качестве значения коллекции уникальную коллекцию (экземпляр) как часть логики конструктора класса. В противном случае будет создан случайный одноэлементный класс.

Рассмотрим следующий пример. В следующем разделе примера демонстрируется определение класса Aquarium, в котором имеется ошибка со значением по умолчанию. В этом классе определяется свойство зависимостей типа коллекции AquariumObjects, в котором используется универсальный тип List<T> с ограничением типа FrameworkElement. В вызове 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")

Вместо того чтобы каждой коллекции назначалось число 1, каждой коллекции назначается число 2! Это происходит, поскольку каждый 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 поддерживает свою собственную уникальную коллекцию.

Если свойство коллекции будет доступным для чтения и записи, в этот шаблон потребуется внести небольшое изменение. В этом случае можно вызвать метод доступа set из конструктора, чтобы выполнить инициализацию, при которой также будет вызываться неключевая сигнатура метода SetValue(DependencyProperty, Object) из оболочки метода set с помощью открытого идентификатора DependencyProperty.

Передача сведений об изменении значений привязки из свойств коллекции

Свойство коллекции, которое само является свойством зависимостей, не сообщает об изменениях своих подсвойств автоматически. При создании привязок в коллекции это может мешать привязке сообщать об изменениях и, следовательно, нарушить некоторые сценарии привязки данных. Однако если в качестве типа коллекции использовать тип FreezableCollection<T>, то сведения об изменении подсвойств у элементов коллекции будут передаваться корректно, и привязка тоже будет работать надлежащим образом.

Чтобы включить привязку подсвойств в коллекции объектов зависимостей, создайте свойство коллекции типа FreezableCollection<T>, с ограничением типа для этой коллекции до любого производного класса DependencyObject.

См. также