Condividi tramite


Modelli di costruttore sicuri per DependencyObject

In genere, i costruttori di classe non devono chiamare callback come metodi virtuali o delegati, in quanto possono essere chiamati come inizializzazione di base di costruttori per una classe derivata. L'uso di elementi virtuali può avvenire in un stato incompleto dell'inizializzazione di qualsiasi dato oggetto. Il sistema di proprietà stesso, tuttavia, chiama ed espone internamente i callback come parte del sistema di proprietà di dipendenza. Un'operazione semplice come l'impostazione di un valore della proprietà di dipendenza con SetValue una chiamata potenzialmente include un callback in un punto qualsiasi nella determinazione. Per questa ragione, occorre prestare attenzione quando si impostano i valori delle proprietà di dipendenza all'interno del corpo di un costruttore, perché l'operazione può divenire problematica se il tipo viene usato come classe di base. Esiste un modello specifico per l'implementazione DependencyObject di costruttori che evita problemi specifici con gli stati delle proprietà di dipendenza e i callback intrinseci, documentati qui.

Metodi virtuali del sistema di proprietà

I metodi virtuali o i callback seguenti vengono potenzialmente chiamati durante i calcoli della SetValue chiamata che imposta un valore della proprietà di dipendenza: ValidateValueCallback, PropertyChangedCallback, CoerceValueCallback, OnPropertyChanged. Ognuno di questi metodi virtuali o callback serve a uno scopo specifico per espandere la versatilità del sistema di proprietà Windows Presentation Foundation (WPF) e delle proprietà di dipendenza. Per altre informazioni su come usare questi elementi virtuali per personalizzare la determinazione del valore della proprietà, vedere Callback e convalida delle proprietà di dipendenza.

FXCop Rule Enforcement vs. Property System Virtuals

Se si usa lo strumento Microsoft FXCop come parte del processo di compilazione e si deriva da determinate classi di framework WPF che chiamano il costruttore di base oppure si implementano proprietà di dipendenza personalizzate nelle classi derivate, è possibile che si verifichi una particolare violazione della regola FXCop. La stringa del nome di questa violazione è:

DoNotCallOverridableMethodsInConstructors

Si tratta di una regola che fa parte del set di regole pubblico predefinito per FXCop. È possibile che questa regola segnali una traccia tramite il sistema di proprietà di dipendenza che infine chiama il metodo virtuale di un sistema di proprietà di dipendenza. La violazione di questa regola potrebbe continuare ad apparire anche dopo avere seguito i modelli del costruttore consigliati illustrati in questo argomento, pertanto potrebbe essere necessario disabilitare o eliminare la regola nella configurazione del set di regole FXCop.

La maggior parte dei problemi è causata dalla derivazione delle classi e dal mancato uso delle classi esistenti

I problemi segnalati da questa regola si verificano quando una classe implementata con metodi virtuali nella relativa sequenza di costruzione viene successivamente derivata. Se la classe viene contrassegnata come sealed oppure si sa o si decide che la classe non sarà derivata, le considerazioni qui illustrate e i problemi che hanno motivato la regola FXCop non si applicano a questa situazione. Se tuttavia se si stanno creando classi in modo che vengano usate come classi di base, ad esempio se si stanno creando modelli o un set di librerie di controlli espandibile, è consigliabile seguire i modelli raccomandati in questo argomento per i costruttori.

I costruttori predefiniti devono inizializzare tutti i valori richiesti dai callback

Tutti i membri dell'istanza utilizzati dalla classe eseguono l'override o i callback (i callback dall'elenco nella sezione Virtuals del sistema di proprietà) devono essere inizializzati nel costruttore senza parametri della classe, anche se alcuni di questi valori vengono riempiti da valori "reali" tramite parametri dei costruttori senza parametri.

L'esempio di codice seguente e i successivi sono esempi di pseudo codice C# in cui questa regola viene violata e in cui viene illustrato il 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 il codice dell'applicazione chiama new MyClass(objectvalue), chiama il costruttore senza parametri e i costruttori della classe di base. Imposta quindi Property1 = object1, che chiama il metodo OnPropertyChanged virtuale nel proprietario MyClassDependencyObjectdi . L'override fa riferimento a _myList, che non è stato ancora inizializzato.

Un modo per evitare questi problemi consiste nel verificare che i callback usino solo altre proprietà di dipendenza e che ognuna di esse abbia un valore predefinito stabilito come parte dei relativi metadati registrati.

Modelli di costruttore sicuri

Per evitare i rischi di un'inizializzazione incompleta nel caso in cui la classe sia usata come classe di base, seguire questi modelli:

Costruttori senza parametri che chiamano l'inizializzazione di base

Implementare questi costruttori che chiamano l'impostazione predefinita di base:

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

Costruttori non predefiniti (di comodo), che non corrispondono ad alcuna firma di base

Se questi costruttori usano i parametri per impostare le proprietà di dipendenza nell'inizializzazione, chiamare prima il costruttore senza parametri della classe per l'inizializzazione e quindi usare i parametri per impostare le proprietà di dipendenza. Queste ultime potrebbero essere proprietà di dipendenza definite dalla classe o proprietà di dipendenza ereditate dalle classi di base. In entrambi i casi, tuttavia, usare il modello seguente:

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

Costruttori non predefiniti (di comodo), che corrispondono alle firme di base

Anziché chiamare il costruttore di base con la stessa parametrizzazione, chiamare di nuovo il costruttore senza parametri della classe. Non chiamare l'inizializzatore di base, bensì chiamare this(). A questo punto, riprodurre il comportamento del costruttore originale usando i parametri passati come valori per l'impostazione delle proprietà pertinenti. Per informazioni sulla determinazione delle proprietà da impostare tramite determinati parametri, usare la documentazione originale del costruttore di base:

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 corrispondere a tutte le firme

Nei casi in cui il tipo di base ha più firme, è necessario associare deliberatamente tutte le firme possibili a un'implementazione del costruttore personalizzata che usa il modello consigliato di chiamare il costruttore senza parametri della classe prima di impostare altre proprietà.

Impostazione delle proprietà di dipendenza con SetValue

Questi stessi criteri si applicano se si imposta una proprietà che non dispone di un wrapper per l'impostazione della proprietà per praticità e si impostano i valori con SetValue. Le chiamate a SetValue tali parametri del costruttore pass-through devono anche chiamare il costruttore senza parametri della classe per l'inizializzazione.

Vedi anche