Modelos de constructores seguros para objetos DependencyObject

En general, los constructores de clase no deben llamar a devoluciones de llamada como métodos virtuales o delegados, porque se puede llamar a los constructores como inicialización base de constructores para una clase derivada. Es posible entrar en el método virtual en un estado de inicialización incompleto de cualquier objeto determinado. Pero el propio sistema de propiedades llama y expone internamente las devoluciones de llamada, como parte del sistema de propiedades de dependencia. Una operación tan simple como establecer un valor de propiedad de dependencia con una llamada a SetValue incluye potencialmente una devolución de llamada en algún punto de la determinación. Por esta razón, deben extremarse las precauciones al establecer los valores de propiedad de dependencia dentro del cuerpo de un constructor, algo que puede resultar problemático si el tipo se usa como una clase base. Existe un patrón concreto para implementar constructores de DependencyObject que evita los problemas concretos con los estados de las propiedades de dependencia y las devoluciones de llamada inherentes, que se documenta aquí.

Métodos virtuales del sistema de propiedades

Los métodos virtuales o devoluciones de llamada siguientes se llaman potencialmente durante los cálculos de la llamada a SetValue que establece un valor de propiedad de dependencia: ValidateValueCallback, PropertyChangedCallback, CoerceValueCallback, OnPropertyChanged. Cada uno de estos métodos virtuales o devoluciones de llamada sirve para una finalidad concreta dirigida a expandir la versatilidad del sistema de propiedades y las propiedades de dependencia de Windows Presentation Foundation (WPF). Para más información sobre cómo usar estos métodos virtuales para personalizar la determinación de valores de propiedad, vea Devoluciones de llamada y validación de las propiedades de dependencia.

Diferencias entre la aplicación de la regla de FXCop y los métodos virtuales del sistema de propiedad

Si usa la herramienta FXCop de Microsoft como parte del proceso de compilación, y deriva de algunas clases del marco WPF mediante la llamada al constructor base, o bien implementa propiedades de dependencia propias en clases derivadas, es posible que se produzca una infracción de una regla de FXCop concreta. La cadena de nombre de esta infracción es:

DoNotCallOverridableMethodsInConstructors

Se trata de una regla que forma parte del conjunto de reglas público predeterminado de FXCop. Es posible que esta regla comunique la existencia de una traza a través del sistema de propiedades de dependencia que, llegado un punto, llama a un método virtual de dicho sistema. Esta infracción de la regla podría seguir apareciendo aunque se sigan los patrones de constructor recomendados que se documentan en este tema, en cuyo caso puede que sea necesario deshabilitar o suprimir esa regla en la configuración del conjunto de reglas de FXCop.

La mayoría de los problemas proceden de derivar clases, no de usar las clases existentes

Los problemas que comunica esta regla se producen cuando se deriva de una clase que se implementa con métodos virtuales en su secuencia de construcción. Si sella la clase, o si sabe o exige de algún otro modo que no se derive de ella, las consideraciones que se explican aquí y los problemas que dan lugar a la regla de FXCop no son de aplicación en su caso. Pero si se crean clases previstas para su uso como clases base, como plantillas, o un conjunto ampliable de bibliotecas de controles, por ejemplo, entonces se deben seguir los patrones recomendados para los constructores.

Los constructores predeterminados deben inicializar todos los valores solicitados por las devoluciones de llamada

Cualquier miembro de instancia que se use en las invalidaciones de clase o las devoluciones de llamada (las devoluciones de llamada de la lista de la sección Métodos virtuales del sistema de propiedades) se deben inicializar en el constructor sin parámetros de la clase, aunque algunos de esos valores se rellenen con valores "reales" mediante parámetros de los constructores que no sean sin parámetros.

El código de ejemplo siguiente (y de los ejemplos siguientes) es un ejemplo de seudocódigo de C# que infringe esta regla y explica el 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;  
}  

Cuando el código de aplicación llama a new MyClass(objectvalue), se llama al constructor sin parámetros y a los constructores de clase base. A continuación, establece Property1 = object1, que llama al método virtual OnPropertyChanged en el propietario MyClass DependencyObject. La invalidación hace referencia a _myList, que no se ha inicializado todavía.

Una manera de evitar estos problemas es asegurarse de que las devoluciones de llamada solo usan otras propiedades de dependencia, y que cada una de estas últimas tiene un valor predeterminado establecido como parte de sus metadatos registrados.

Patrones de constructor seguros

Para evitar los riesgos de inicialización incompleta si la clase se usa como clase base, siga estos patrones:

Constructores sin parámetros que llaman a la inicialización base

Implemente estos constructores llamando al valor predeterminado base:

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

Constructores no predeterminados (útiles) que no coinciden con ninguna firma base

Si estos constructores usan los parámetros para establecer las propiedades de dependencia en la inicialización, llame primero al constructor sin parámetros de su propia clase para la inicialización y, después, use los parámetros para establecer las propiedades de dependencia. Pueden ser propiedades de dependencia definidas por la clase o bien heredadas de las clases base, pero debe usar el patrón siguiente en cualquier caso:

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;  
    }  
}  

Constructores no predeterminados (útiles) que coinciden con firmas base

En lugar de llamar al constructor base con la misma parametrización, llame de nuevo al constructor sin parámetros de su propia clase. No llame al inicializador base, en su lugar debe llamar a this(). Después, reproduzca el comportamiento del constructor original mediante los parámetros pasados como valores para establecer las propiedades pertinentes. Use la documentación del constructor base original para obtener orientación para determinar las propiedades que debe establecer cada parámetro concreto:

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;  
    }  
}  

Debe hacer coincidir todas las firmas

Para los casos en los que el tipo base tiene varias firmas, debe hacer coincidir deliberadamente todas las firmas posibles con una implementación de constructor propia que use el patrón recomendado consistente en llamar al constructor sin parámetros de clase antes de establecer otras propiedades.

Establecer propiedades de dependencia con SetValue

Estos mismos patrones se aplican si establece una propiedad que no tiene un contenedor para facilitar el establecimiento de propiedades, y establecen los valores con SetValue. Las llamadas a SetValue que pasan por los parámetros del constructor también deben llamar al constructor sin parámetros de la clase para la inicialización.

Vea también