Padrões de construtor seguro para DependencyObjects

De modo geral, os construtores de classe não devem chamar retornos de chamada como métodos virtuais ou representantes, porque construtores podem ser chamados como inicialização de base dos construtores para uma classe derivada. O fornecimento do virtual pode ser feito em um estado de inicialização incompleta de um determinado objeto. No entanto, o sistema de propriedades chama e expõe os retornos de chamada internamente, como parte do sistema de propriedades de dependência. Uma operação tão simples quanto definir um valor de propriedade de dependência com SetValue chamada potencialmente inclui um retorno de chamada em algum lugar na determinação. Por esse motivo, você deve ter cuidado ao definir valores de propriedade de dependência dentro do corpo de um construtor, o que poderá se tornar problemático se o tipo for usado como uma classe base. Há um padrão específico para implementar DependencyObject construtores que evita problemas específicos com estados de propriedade de dependência e os retornos de chamada inerentes, que está documentado aqui.

Métodos virtuais do sistema de propriedades

Os seguintes métodos virtuais ou retornos de chamada são potencialmente chamados durante os SetValue cálculos da chamada que define um valor de propriedade de dependência: ValidateValueCallback, , PropertyChangedCallbackCoerceValueCallback, OnPropertyChanged. Cada um desses métodos virtuais ou retornos de chamada serve a uma finalidade específica na expansão da versatilidade do sistema de propriedades e propriedades de dependência do Windows Presentation Foundation (WPF). Para obter mais informações sobre como usar esses virtuais para personalizar a determinação do valor da propriedade, consulte Retornos de chamada da propriedade de dependência e validação.

Imposição de regras FXCop versus virtuais do sistema de propriedades

Se você usar a ferramenta da Microsoft FXCop como parte do seu processo de compilação e derivar de determinadas classes de estrutura WPF chamando o construtor base ou implementar suas próprias propriedades de dependência em classes derivadas, poderá encontrar uma violação de regra FXCop específica. A cadeia de caracteres de nome dessa violação é:

DoNotCallOverridableMethodsInConstructors

Essa é uma regra que faz parte da regra pública padrão definida para o FXCop. O que essa regra pode estar relatando é um rastreamento do sistema de propriedade de dependência que eventualmente chama o método virtual do sistema de propriedade de dependência. Essa violação de regra pode continuar aparecendo mesmo após você seguir os padrões de construtor recomendados documentados neste tópico, portanto, talvez seja necessário desabilitar ou suprimir essa regra em sua configuração de conjunto de regras do FXCop.

A maioria dos problemas vem das classes derivadas, não do uso de classes existentes

Os problemas relatados por essa regra ocorrem quando uma classe que você implementa com métodos virtuais em sua sequência de construção é, então, derivada. Se você lacrar sua classe ou souber ou impor que sua classe não será derivada, as considerações explicadas aqui e os problemas que motivaram a regra do FXCop não se aplicarão a você. No entanto, se estiver criando classes de forma que elas devam ser usadas como classes base, por exemplo, se estiver criando modelos ou um conjunto de bibliotecas de controle expansível, siga os padrões recomendados aqui para construtores.

Construtores padrão devem inicializar todos os valores solicitados pelos retornos de chamada

Todos os membros de instância usados por suas substituições de classe ou retornos de chamada (os retornos de chamada da lista na seção Property System Virtuals) devem ser inicializados em seu construtor sem parâmetros de classe, mesmo que alguns desses valores sejam preenchidos por valores "reais" por meio de parâmetros dos construtores sem parâmetros.

O seguinte exemplo de código (e os exemplos depois dele) é um exemplo de "pseudo" C# que viola essa regra e explica o problema:

public class MyClass : DependencyObject  
{  
    public MyClass() {}  
    public MyClass(object toSetWobble)  
        : this()  
    {  
        Wobble = toSetWobble; //this is backed by a DependencyProperty  
        _myList = new ArrayList();    // this line should be in the default ctor  
    }  
    public static readonly DependencyProperty WobbleProperty =
        DependencyProperty.Register("Wobble", typeof(object), typeof(MyClass));  
    public object Wobble  
    {  
        get { return GetValue(WobbleProperty); }  
        set { SetValue(WobbleProperty, value); }  
    }  
    protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e)  
    {  
        int count = _myList.Count;    // null-reference exception  
    }  
    private ArrayList _myList;  
}  

Quando o código do aplicativo chama , isso chama new MyClass(objectvalue)o construtor sem parâmetros e construtores de classe base. Em seguida, ele define Property1 = object1, que chama o método OnPropertyChanged virtual na posse MyClassDependencyObject. A substituição se refere a _myList, que ainda não foi inicializado.

Uma maneira de evitar esses problemas é garantir que os retornos de chamada usem apenas outras propriedades de dependência e que cada propriedade de dependência tenha um valor padrão estabelecido como parte de seus metadados registrados.

Padrões de construtor seguros

Para evitar os riscos da inicialização incompleta se sua classe for usada como uma classe base, siga estes padrões:

Construtores sem parâmetros chamando inicialização de base

Implemente esses construtores chamando o padrão de base:

public MyClass : SomeBaseClass {  
    public MyClass() : base() {  
        // ALL class initialization, including initial defaults for
        // possible values that other ctors specify or that callbacks need.  
    }  
}  

Construtores não padrão (de conveniência) que não correspondem a nenhuma assinatura de base

Se esses construtores usarem os parâmetros para definir propriedades de dependência na inicialização, primeiro chame seu próprio construtor sem parâmetros de classe para inicialização e, em seguida, use os parâmetros para definir propriedades de dependência. Esses poderiam ser propriedades de dependência definidas por sua classe ou propriedades de dependência herdadas das classes base, mas em ambos os casos, use o seguinte padrão:

public MyClass : SomeBaseClass {  
    public MyClass(object toSetProperty1) : this() {  
        // Class initialization NOT done by default.  
        // Then, set properties to values as passed in ctor parameters.  
        Property1 = toSetProperty1;  
    }  
}  

Construtores não padrão (de conveniência) que correspondem a assinaturas de base

Em vez de chamar o construtor base com a mesma parametrização, chame novamente o construtor sem parâmetros da sua própria classe. Não chame o inicializador de base; em vez disso, você deve chamar this(). Em seguida, reproduza o comportamento do construtor original usando os parâmetros passados como valores para definir as propriedades relevantes. Use a documentação do construtor de base original para ver diretrizes para determinar as propriedades que os parâmetros específicos devem definir:

public MyClass : SomeBaseClass {  
    public MyClass(object toSetProperty1) : this() {  
        // Class initialization NOT done by default.  
        // Then, set properties to values as passed in ctor parameters.  
        Property1 = toSetProperty1;  
    }  
}  

Deve corresponder a todas as assinaturas

Para casos em que o tipo base tem várias assinaturas, você deve deliberadamente combinar todas as assinaturas possíveis com uma implementação de construtor própria que use o padrão recomendado de chamar o construtor sem parâmetros de classe antes de definir outras propriedades.

Definindo propriedades de dependência com SetValue

Esses mesmos padrões se aplicam se você estiver definindo uma propriedade que não tenha um wrapper para conveniência de configuração de propriedade e definir valores com SetValue. Suas chamadas para que passam pelos parâmetros do construtor também devem chamar o construtor sem parâmetros da classe para SetValue inicialização.

Confira também