Partilhar via


Padrões de construção seguros para DependencyObjects

Geralmente, os construtores de classe não devem chamar callbacks (retornos de chamada), como métodos virtuais ou delegados, porque os construtores podem ser chamados como parte da inicialização de um construtor de uma classe derivada. A inserção no virtual pode ser feita num estado de inicialização incompleto de um dado objeto. No entanto, o próprio sistema de propriedades chama e expõe retornos de chamada internamente, como parte do sistema de propriedade de dependência. Uma operação tão simples quanto definir o valor de uma propriedade de dependência com a chamada SetValue potencialmente inclui uma função de retorno 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 pode se tornar problemático se seu tipo for usado como uma classe base. Há um padrão específico para implementar os construtores DependencyObject que evita problemas associados a estados de propriedades de dependência e callbacks intrínsecos, conforme documentado aqui.

Métodos virtuais do sistema de propriedade

Os seguintes métodos virtuais ou retornos de chamada podem ser chamados durante os cálculos da chamada SetValue que define um valor de propriedade dependente: ValidateValueCallback, PropertyChangedCallback, CoerceValueCallback, OnPropertyChanged. Cada um desses métodos virtuais ou retornos de chamada tem um propósito específico para expandir a versatilidade do sistema de propriedades da Windows Presentation Foundation (WPF) e das propriedades de dependência. Para obter mais informações sobre como usar esses virtuais para personalizar a determinação do valor da propriedade, consulte Dependency Property Callbacks and Validation.

Execução de Regras FXCop vs. Virtuais do Sistema de Propriedades

Se você usar a ferramenta da Microsoft FXCop como parte de 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 para essa violação é:

DoNotCallOverridableMethodsInConstructors

Esta é uma regra que faz parte do conjunto de regras públicas padrão para FXCop. O que esta regra poderá estar a relatar é um rastreio através do sistema de propriedades de dependência que por fim chama um método virtual do próprio sistema de propriedades de dependência. Essa violação de regra pode continuar a aparecer mesmo depois de seguir os padrões de construtor recomendados documentados neste tópico, portanto, talvez seja necessário desabilitar ou suprimir essa regra na configuração do conjunto de regras FXCop.

A maioria dos problemas vem de classes derivadas, não usando classes existentes

Os problemas relatados por esta regra ocorrem quando uma classe que é implementada com métodos virtuais na sua sequência de construção é posteriormente derivada. Se você selar sua classe, ou de outra forma souber ou impor que sua classe não será derivada, as considerações explicadas aqui e os problemas que motivaram a regra FXCop não se aplicam a você. No entanto, se você estiver criando classes de tal forma que elas se destinem a ser usadas como classes base, por exemplo, se você estiver criando modelos ou um conjunto de bibliotecas de controle expansíveis, você deve seguir os padrões recomendados aqui para construtores.

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

Todos os membros de instância que são 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 código de exemplo a seguir (e exemplos subsequentes) é um exemplo 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 new MyClass(objectvalue), ele invoca o construtor sem parâmetros e os construtores da classe base. Em seguida, ele define Property1 = object1, que chama o método virtual OnPropertyChanged no proprietário MyClassDependencyObject. A substituição refere-se a _myList, que ainda não está inicializada.

Uma maneira de evitar esses problemas é assegurar que as funções de retorno usem apenas outras propriedades de dependência, e que cada uma dessas propriedades de dependência tenha um valor padrão estabelecido como parte dos seus metadados registados.

Padrões de construtores seguros

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

Construtores sem parâmetros chamando a inicialização da base

Implemente estes construtores chamando o padrão 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 convenientes (não padrão), que não correspondem a qualquer assinatura base

Se esses construtores usam 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. Estas podem ser propriedades de dependência definidas pela sua classe ou propriedades de dependência herdadas de 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 (por conveniência), que correspondem às 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 original do construtor usando os parâmetros passados como valores para definir as propriedades relevantes. Use a documentação original do construtor base para obter orientação na determinação das 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 corresponder deliberadamente todas as assinaturas possíveis com uma implementação de construtor própria que usa 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 aplicam-se se estiver a configurar uma propriedade que não tenha um wrapper para facilidade de configuração, e configurar valores com SetValue. Suas chamadas para SetValue que passam pelos parâmetros do construtor também devem chamar o construtor sem parâmetros da classe para inicialização.

Ver também