Propiedades de dependencia de tipo de colección

En este tema se proporciona una guía y modelos recomendados para implementar una propiedad de dependencia donde el tipo de la propiedad es un tipo de colección.

Implementar una propiedad de dependencia de tipo de colección

Para una propiedad de dependencia en general, el modelo de implementación que sigue consiste en definir un contenedor de propiedad CLR, donde esa propiedad está respaldada por un identificador DependencyProperty en lugar de un campo u otra construcción. Siga este mismo modelo al implementar una propiedad de tipo de colección. Sin embargo, una propiedad de tipo de colección presenta cierta complejidad en el modelo siempre que el tipo que está dentro de la colección es en sí mismo una clase derivada DependencyObject o Freezable.

Inicialización de la colección más allá del valor predeterminado

Cuando cree una propiedad de dependencia, no especifique el valor predeterminado de la propiedad como el valor inicial del campo. En su lugar, especifique el valor predeterminado a través de los metadatos de la propiedad de dependencia. Si la propiedad es un tipo de referencia, el valor predeterminado especificado en los metadatos de la propiedad de dependencia no es un valor predeterminado por instancia; en su lugar, es un valor predeterminado que se aplica a todas las instancias del tipo. Por lo tanto, debe tener cuidado de no usar la colección estática singular definida por los metadatos de la propiedad de colección como el valor predeterminado operativo para las instancias recién creadas del tipo. En su lugar, debe asegurarse de establecer deliberadamente el valor de la colección en una colección única (instancia) como parte de la lógica del constructor de clase. De lo contrario, habrá creado involuntariamente una clase singleton.

Considere el ejemplo siguiente. En la siguiente sección del ejemplo se muestra la definición de una clase Aquarium, que contiene un error con el valor predeterminado. La clase define la propiedad de dependencia del tipo de colección AquariumObjects, que usa el tipo genérico List<T> con una restricción de tipo FrameworkElement. En la llamada Register(String, Type, Type, PropertyMetadata) a la propiedad de dependencia, los metadatos establecen el valor predeterminado para que sea un nuevo List<T> genérico.

Advertencia

El código siguiente no funciona correctamente.

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

Sin embargo, si se limita a dejar el código tal como se muestra, ese valor predeterminado de lista único se comparte en todas las instancias de Aquarium. Si ejecutara el siguiente código de prueba, destinado a mostrar cómo se crea una instancia de dos instancias Aquarium independientes y se agrega un único valor Fish diferente a cada una de ellas, vería un resultado sorprendente:

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

En lugar de tener un recuento de uno, cada colección tiene un recuento de dos. Esto se debe a que cada Aquarium agregó su Fish a la colección de valores predeterminados, que resultó de una única llamada de constructor en los metadatos y, por tanto, se compartió en todas las instancias. Esta situación casi nunca es la deseada.

Para corregir este problema, debe restablecer el valor de la propiedad de dependencia de la colección en una instancia única, como parte de la llamada al constructor de clase. Dado que la propiedad es una propiedad de dependencia de solo lectura, se usa el método SetValue(DependencyPropertyKey, Object) para establecerla mediante DependencyPropertyKey, que solo es accesible dentro de la clase.

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

Ahora, si ejecutara el mismo código de prueba de nuevo, podría obtener resultados más previsibles, donde cada Aquarium admite su propia colección única.

Se produciría una ligera variación en este modelo si decidiera definir la propiedad de colección como de lectura y escritura. En ese caso, es posible llamar al descriptor de acceso público establecido desde el constructor para realizar la inicialización, que seguiría llamando a la firma sin clave de SetValue(DependencyProperty, Object) dentro del contenedor definido mediante un identificador público DependencyProperty.

Notificación de cambios en los valores de enlace de las propiedades de colección

Una propiedad de colección que es una propiedad de dependencia no notifica automáticamente los cambios en sus subpropiedades. Si está creando enlaces en una colección, esto puede impedir que el enlace comunique los cambios, lo que invalidaría algunos escenarios de enlace de datos. Sin embargo, si usa el tipo de colección FreezableCollection<T>, los cambios de subpropiedades en los elementos que se incluyen en la colección se comunican correctamente y el enlace funciona según lo previsto.

Para habilitar el enlace de subpropiedades en una colección de objetos de dependencia, cree la propiedad de colección como el tipo FreezableCollection<T>, con una restricción de tipo de esa colección para cualquier clase derivada DependencyObject.

Vea también