Propriedades de dependência do tipo de coleção

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.

Implementando uma Propriedade de Dependência do Tipo de Coleção

Para uma propriedade de dependência em geral, o padrão de implementação que você segue é que você define um wrapper de propriedade CLR, onde essa propriedade é apoiada por um identificador em vez de um DependencyProperty campo ou outra construção. Você segue esse mesmo padrão quando implementa uma propriedade de tipo de coleção. No entanto, uma propriedade collection-type introduz alguma complexidade ao padrão sempre que o tipo contido na coleção é uma DependencyObject classe ou Freezable derivada.

Inicializando a coleção além do valor padrão

Quando você cria uma propriedade de dependência, você não especificar o valor padrão da propriedade como o valor do campo inicial. Em vez disso, você pode especificar o valor padrão por meio de metadados de propriedade de dependência. 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. 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. 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. Caso contrário, você terá criado uma classe singleton não intencional.

Considere o exemplo a seguir. A seção a seguir do exemplo mostra a definição de uma classe Aquarium, que contém uma falha com o valor padrão. A classe define a propriedade AquariumObjectsde dependência de tipo de coleção , que usa o tipo genérico List<T> com uma restrição de FrameworkElement tipo. Na chamada para a Register(String, Type, Type, PropertyMetadata) propriedade de dependência, os metadados estabelecem o valor padrão para ser um novo genérico List<T>.

Aviso

O código a seguir não se comporta corretamente.

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. 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:

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. 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. Essa situação quase nunca é o que você deseja.

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. Como a propriedade é uma propriedade de dependência somente leitura, use o método para defini-la, usando o SetValue(DependencyPropertyKey, Object)DependencyPropertyKey que só é acessível dentro da classe.

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.

Haveria uma variação pequena nesse padrão se você optasse por ter sua propriedade da coleção a ser leitura/gravação. Nesse caso, você poderia chamar o acessador de conjunto público do construtor para fazer a inicialização, que ainda estaria chamando a assinatura não-chave de dentro do wrapper do SetValue(DependencyProperty, Object) conjunto, usando um identificador público DependencyProperty .

Relatório de alterações de valor de associação de propriedades de coleção

Uma propriedade de coleção que é uma propriedade de dependência não relata automaticamente alterações para suas subpropriedades. 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. No entanto, se você usar o tipo de coleção como seu tipo FreezableCollection<T> de coleção, as alterações de subpropriedade para elementos contidos na coleção serão relatadas corretamente e a vinculação funcionará conforme o esperado.

Para habilitar a associação de subpropriedade em uma coleção de objetos de dependência, crie a propriedade collection como type FreezableCollection<T>, com uma restrição de tipo para essa coleção para qualquer DependencyObject classe derivada.

Confira também