Propriedades de dependência do tipo de coleçãoCollection-Type Dependency Properties

Este tópico fornece diretrizes e padrões sugeridos para como implementar uma propriedade de dependência em que o tipo da propriedade é um tipo de coleção.This topic provides guidance and suggested patterns for how to implement a dependency property where the type of the property is a collection type.

Implementando uma Propriedade de Dependência do Tipo de ColeçãoImplementing a Collection-Type Dependency Property

Para uma propriedade de dependência em geral, o padrão de implementação que você segue é DependencyProperty que você define um invólucro de propriedade CLR, onde essa propriedade é apoiada por um identificador em vez de um campo ou outra construção.For a dependency property in general, the implementation pattern that you follow is that you define a CLR property wrapper, where that property is backed by a DependencyProperty identifier rather than a field or other construct. Você segue esse mesmo padrão quando implementa uma propriedade de tipo de coleção.You follow this same pattern when you implement a collection-type property. No entanto, uma propriedade do tipo coleção introduz alguma complexidade ao padrão DependencyObject sempre Freezable que o tipo que está contido dentro da coleção é em si uma classe ou derivada.However, a collection-type property introduces some complexity to the pattern whenever the type that is contained within the collection is itself a DependencyObject or Freezable derived class.

Inicializando a coleção além do valor padrãoInitializing the Collection Beyond the Default Value

Quando você cria uma propriedade de dependência, você não especificar o valor padrão da propriedade como o valor do campo inicial.When you create a dependency property, you do not specify the property default value as the initial field value. Em vez disso, você pode especificar o valor padrão por meio de metadados de propriedade de dependência.Instead, you specify the default value through the dependency property metadata. Se a propriedade for um tipo de referência, o valor padrão especificado nos metadados de propriedade de dependência não será um valor padrão por instância; em vez disso, será um valor padrão que se aplica a todas as instâncias do tipo.If your property is a reference type, the default value specified in dependency property metadata is not a default value per instance; instead it is a default value that applies to all instances of the type. Portanto, você deve ter cuidado para não usar a coleção estática singular definida pelos metadados de propriedade de coleção como o valor padrão de trabalho para instâncias recém-criadas do seu tipo.Therefore you must be careful to not use the singular static collection defined by the collection property metadata as the working default value for newly created instances of your type. Em vez disso, você deve verificar se definiu deliberadamente o valor da coleção para uma coleção exclusiva (instância) como parte da lógica do construtor de classe.Instead, you must make sure that you deliberately set the collection value to a unique (instance) collection as part of your class constructor logic. Caso contrário, você terá criado uma classe singleton não intencional.Otherwise you will have created an unintentional singleton class.

Considere o exemplo a seguir.Consider the following example. A seção a seguir do exemplo Aquariummostra a definição de uma classe , que contém uma falha com o valor padrão.The following section of the example shows the definition for a class Aquarium, which contains a flaw with the default value. A classe define a propriedade AquariumObjectsde dependência do List<T> tipo FrameworkElement de coleção, que usa o tipo genérico com uma restrição de tipo.The class defines the collection type dependency property AquariumObjects, which uses the generic List<T> type with a FrameworkElement type constraint. Na Register(String, Type, Type, PropertyMetadata) chamada para a propriedade de dependência, os metadados estabelecem List<T>que o valor padrão é um novo genérico .In the Register(String, Type, Type, PropertyMetadata) call for the dependency property, the metadata establishes the default value to be a new generic List<T>.

Aviso

O código a seguir não se comporta corretamente.The following code does not behave correctly.

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

No entanto, se você apenas deixar o código como mostrado, aquele valor padrão de lista será compartilhado para todas as instâncias de Aquarium.However, if you just left the code as shown, that single list default value is shared for all instances of Aquarium. Se você tiver executado o seguinte código de teste, que destina-se a mostrar como você poderia instanciar duas instâncias separadas de Aquarium e adicionado um único Fish diferente para cada um deles, você verá um resultado surpreendente:If you ran the following test code, which is intended to show how you would instantiate two separate Aquarium instances and add a single different Fish to each of them, you would see a surprising result:

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

Em vez de cada coleção ter uma contagem de um, cada conjunto possui uma contagem de dois.Instead of each collection having a count of one, each collection has a count of two! Isso ocorre porque cada Aquarium adicionou Fish à coleção de valores padrão, o que resultou de uma única chamada a construtor nos metadados e, portanto, é compartilhado entre todas as instâncias.This is because each Aquarium added its Fish to the default value collection, which resulted from a single constructor call in the metadata and is therefore shared between all instances. Essa situação quase nunca é o que você deseja.This situation is almost never what you want.

Para corrigir esse problema, você deve redefinir o valor da propriedade de dependência da coleção para uma instância única, como parte da chamada do construtor de classe.To correct this problem, you must reset the collection dependency property value to a unique instance, as part of the class constructor call. Como a propriedade é uma propriedade de dependência SetValue(DependencyPropertyKey, Object) somente leitura, você DependencyPropertyKey usa o método para defini-la, usando o que só é acessível dentro da classe.Because the property is a read-only dependency property, you use the SetValue(DependencyPropertyKey, Object) method to set it, using the DependencyPropertyKey that is only accessible within the class.

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

Agora, se você executasse novamente esse mesmo código de teste, você poderia ver resultados mais esperados, no qual cada Aquarium daria suporte à sua própria coleção única.Now, if you ran that same test code again, you could see more expected results, where each Aquarium supported its own unique collection.

Haveria uma variação pequena nesse padrão se você optasse por ter sua propriedade da coleção a ser leitura/gravação.There would be a slight variation on this pattern if you chose to have your collection property be read-write. Nesse caso, você pode chamar o acessório de conjunto público do construtor para fazer a SetValue(DependencyProperty, Object) inicialização, que ainda estaria DependencyProperty chamando a assinatura não-chave de dentro do seu invólucro definido, usando um identificador público.In that case, you could call the public set accessor from the constructor to do the initialization, which would still be calling the nonkey signature of SetValue(DependencyProperty, Object) within your set wrapper, using a public DependencyProperty identifier.

Relatório de alterações de valor de associação de propriedades de coleçãoReporting Binding Value Changes from Collection Properties

Uma propriedade de coleção que é uma propriedade de dependência não relata automaticamente alterações para suas subpropriedades.A collection property that is itself a dependency property does not automatically report changes to its subproperties. Se você estiver criando vínculos em uma coleção, isso poderá impedir que a associação relate alterações, portanto invalidando alguns cenários de vinculação de dados.If you are creating bindings into a collection, this can prevent the binding from reporting changes, thus invalidating some data binding scenarios. No entanto, se FreezableCollection<T> você usar o tipo de coleta como seu tipo de coleta, então as alterações de subpropriedade para elementos contidos na coleção são devidamente relatadas e as obras de vinculação, como esperado.However, if you use the collection type FreezableCollection<T> as your collection type, then subproperty changes to contained elements in the collection are properly reported, and binding works as expected.

Para habilitar a vinculação de subpropriedades em uma FreezableCollection<T>coleção de objetos de DependencyObject dependência, crie a propriedade de coleta como tipo, com uma restrição de tipo para essa coleta para qualquer classe derivada.To enable subproperty binding in a dependency object collection, create the collection property as type FreezableCollection<T>, with a type constraint for that collection to any DependencyObject derived class.

Confira tambémSee also