Partager via


Modèles de constructeur sécurisé pour DependencyObjects

En règle générale, les constructeurs de classe ne doivent pas appeler des rappels tels que les méthodes ou les délégués virtuels, étant donné que les constructeurs peuvent être appelés comme une initialisation de base des constructeurs d’une classe dérivée. L’utilisation de méthodes virtuelles peut se faire à un état d’initialisation incomplet d’un objet donné. Toutefois, le système de propriétés appelle et expose les rappels en interne, dans le cadre du système de propriétés de dépendance. Une opération aussi simple que la définition d’une valeur de propriété de dépendance avec SetValue un appel inclut potentiellement un rappel quelque part dans la détermination. Pour cette raison, vous devez être prudent lorsque vous définissez des valeurs de propriété dans le corps d’un constructeur, car cela peut devenir problématique si votre type est utilisé comme classe de base de dépendance. Il existe un modèle particulier pour implémenter DependencyObject des constructeurs qui évitent des problèmes spécifiques liés aux états de propriété de dépendance et aux rappels inhérents, qui sont documentés ici.

Méthodes virtuelles de système de propriétés

Les méthodes virtuelles ou rappels suivants sont potentiellement appelées pendant les calculs de l’appel SetValue qui définit une valeur de propriété de dépendance : ValidateValueCallback, , PropertyChangedCallback, CoerceValueCallbackOnPropertyChanged. Chacune de ces méthodes virtuelles ou rappels sert un objectif particulier dans l’expansion de la polyvalence du système de propriétés windows Presentation Foundation (WPF) et des propriétés de dépendance. Pour plus d’informations sur l’utilisation de ces virtuels en vue de personnaliser la détermination des valeurs de propriété, consultez Validation et rappels de propriétés de dépendance.

FXCop Rule Enforcement vs. Property System Virtuals

Si vous utilisez l’outil Microsoft FXCop dans le cadre de votre processus de génération, et que vous dérivez de certaines classes d’infrastructure WPF appelant le constructeur de base ou implémentez vos propres propriétés de dépendance sur les classes dérivées, vous pouvez rencontrer une violation de règle FXCop particulière. La chaîne de nom de cette violation est la suivante :

DoNotCallOverridableMethodsInConstructors

Cette règle fait partie de la règle publique par défaut définie pour FXCop. Elle peut signaler une trace, via le système de propriétés de dépendance, qui appelle une méthode virtuelle de système de propriété de dépendance. Le message indiquant une violation de règle peut continuer de s’afficher, même après l’application des modèles de constructeur recommandés documentés dans cette rubrique. Vous devrez donc peut-être désactiver ou supprimer cette règle dans la configuration de l’ensemble de règles FXCop.

La plupart des problèmes proviennent des classes dérivées, et non des classes existantes

Les problèmes signalés par cette règle se produisent lorsqu’une classe que vous implémentez avec des méthodes virtuelles dans sa séquence de construction est ensuite dérivée. Si vous scellez la classe, ou si vous ne voulez pas qu’elle soit dérivée, les explications fournies ici et les problèmes liés aux règles FXCop ne s’appliquent pas. Toutefois, si vous créez des classes qui sont destinées à être utilisées comme des classes de base, par exemple si vous créez des modèles ou un ensemble de bibliothèques de contrôles pouvant être développé, vous devez suivre les modèles recommandés ici pour les constructeurs.

Les constructeurs par défaut doivent initialiser toutes les valeurs demandées par les rappels

Tous les membres d’instance utilisés par vos remplacements de classe ou rappels (les rappels de la liste dans la section Virtuals du système de propriétés) doivent être initialisés dans votre constructeur sans paramètre de classe, même si certaines de ces valeurs sont remplies par des valeurs « réelles » par le biais de paramètres des constructeurs sans paramètres.

L’exemple ci-dessous, et ceux qui suivront, montrent du code C# qui enfreint cette règle et explique le problème :

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

Lorsque le code de l’application appelle new MyClass(objectvalue), cela appelle le constructeur sans paramètre et les constructeurs de classe de base. Ensuite, il définit Property1 = object1, qui appelle la méthode OnPropertyChanged virtuelle sur le propriétaire MyClass DependencyObject. La substitution référence _myList, qui n’a pas encore été initialisé.

Pour éviter ces problèmes, vérifiez que les rappels utilisent uniquement les autres propriétés de dépendance, et que cette propriété de dépendance a une valeur par défaut définie dans le cadre de ses métadonnées enregistrées.

Modèles de constructeur sécurisés

Pour éviter les risques d’initialisation incomplète si votre classe est utilisée comme classe de base, suivez ces modèles :

Constructeurs sans paramètre appelant l’initialisation de base

Implémentez ces constructeurs qui appellent la valeur de base par défaut :

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

Constructeurs non définis par défaut (d’usage) ne correspondant à aucune signature de base

Si ces constructeurs utilisent les paramètres pour définir les propriétés de dépendance dans l’initialisation, appelez d’abord votre propre constructeur sans paramètre de classe pour l’initialisation, puis utilisez les paramètres pour définir les propriétés de dépendance. Il peut s’agir des propriétés de dépendance définies par votre classe ou des propriétés de dépendance héritées des classes de base. Dans les deux cas, utilisez le modèle suivant :

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

Constructeurs non définis par défaut (d’usage) correspondant à une signature de base

Au lieu d’appeler le constructeur de base avec le même paramétrage, appelez à nouveau le constructeur sans paramètre de votre propre classe. N’appelez pas l’initialiseur de base, mais this(). Reproduisez ensuite le comportement du constructeur d’origine en utilisant les paramètres passés comme des valeurs pour définir les propriétés nécessaires. Utilisez la documentation du constructeur de base d’origine pour déterminer les propriétés que les paramètres sont censés définir :

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

Nécessité de correspondre à toutes les signatures

Dans les cas où le type de base a plusieurs signatures, vous devez correspondre délibérément à toutes les signatures possibles avec une implémentation de constructeur de votre propre qui utilise le modèle recommandé d’appel du constructeur sans paramètre de classe avant de définir d’autres propriétés.

Définition de propriétés de dépendance avec SetValue

Ces mêmes modèles s’appliquent si vous définissez une propriété qui n’a pas de wrapper pour la commodité du paramètre de propriété et définissez des valeurs avec SetValue. Vos appels à SetValue ces paramètres de constructeur doivent également appeler le constructeur sans paramètre de la classe pour l’initialisation.

Voir aussi