Zabezpečené vzory konstruktoru pro DependencyObjects

Obecně platí, že konstruktory tříd by neměly volat zpětná volání, jako jsou virtuální metody nebo delegáty, protože konstruktory lze volat jako základní inicializaci konstruktorů pro odvozenou třídu. Zadávání virtuálního objektu může být provedeno v neúplném inicializačním stavu libovolného objektu. Samotný systém vlastností však volá a zpřístupňuje zpětná volání interně jako součást systému vlastností závislostí. Stejně jednoduchá operace jako nastavení hodnoty vlastnosti závislosti s voláním SetValue potenciálně zahrnuje zpětné volání někde v určení. Z tohoto důvodu byste měli být opatrní při nastavování hodnot vlastností závislostí v těle konstruktoru, což může být problematické, pokud je váš typ použit jako základní třída. Pro implementaci DependencyObject konstruktorů existuje určitý vzor, který zabraňuje specifickým problémům se stavy vlastností závislostí a související zpětné volání, které je zde zdokumentované.

Virtuální metody systému vlastností

Následující virtuální metody nebo zpětná volání jsou potenciálně volány během výpočtů SetValue volání, které nastaví hodnotu vlastnosti závislosti: ValidateValueCallback, PropertyChangedCallback, CoerceValueCallback, OnPropertyChanged. Každá z těchto virtuálních metod nebo zpětných volání slouží ke konkrétnímu účelu při rozšiřování všestrannosti systému vlastností Windows Presentation Foundation (WPF) a vlastností závislostí. Další informace o tom, jak tyto virtuální počítače použít k přizpůsobení určení hodnoty vlastnosti, naleznete v tématu Zpětné volání a ověřování vlastností závislostí.

FXCop Rule Enforcement vs. Property System Virtuals

Pokud jako součást procesu sestavení používáte nástroj Microsoft FXCop a buď jste odvozeni od určitých tříd architektury WPF, které volají základní konstruktor, nebo implementujete vlastní vlastnosti závislostí na odvozených třídách, může dojít k určitému porušení pravidla FXCop. Řetězec názvu pro toto porušení je:

DoNotCallOverridableMethodsInConstructors

Toto je pravidlo, které je součástí výchozí veřejné sady pravidel pro FXCop. Toto pravidlo může hlásit trasování systémem vlastností závislostí, který nakonec volá virtuální metodu systému vlastností závislostí. Toto porušení pravidla se může i nadále zobrazovat i po provedení doporučených vzorů konstruktorů zdokumentovaných v tomto tématu, takže možná budete muset toto pravidlo zakázat nebo potlačit v konfiguraci sady pravidel FXCop.

Většina problémů pochází z odvozených tříd a nepoužívá existující třídy.

K problémům hlášeným tímto pravidlem dochází v případě, že třída, kterou implementujete s virtuálními metodami v jeho konstrukční sekvenci, je pak odvozena z. Pokud zapečetíte třídu nebo jinak víte nebo vynucujete, že vaše třída nebude odvozena, jsou zde vysvětleny důležité informace a problémy, které motivují pravidlo FXCop, se na vás nevztahují. Pokud však vytváříte třídy takovým způsobem, že jsou určeny k použití jako základní třídy, například pokud vytváříte šablony nebo sadu rozšiřitelné knihovny ovládacích prvků, měli byste postupovat podle vzorů doporučených zde pro konstruktory.

Výchozí konstruktory musí inicializovat všechny hodnoty požadované zpětnými voláními.

Všechny členy instance, které vaše třída přepíše nebo zpětné volání (zpětné volání ze seznamu v oddílu Property System Virtuals) musí být inicializovány v konstruktoru bez parametrů třídy, i když některé z těchto hodnot jsou vyplněny "skutečnými" hodnotami prostřednictvím parametrů neparametrových konstruktorů.

Následující ukázkový kód (a další příklady) je pseudo-C#, který porušuje toto pravidlo a vysvětluje problém:

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

Při volání new MyClass(objectvalue)kódu aplikace tato volá konstruktor bez parametrů a konstruktory základní třídy. Pak nastaví Property1 = object1, který volá virtuální metodu OnPropertyChanged ve vlastnictví MyClassDependencyObject. Přepsání odkazuje na _myList, který ještě nebyl inicializován.

Jedním ze způsobů, jak se těmto problémům vyhnout, je zajistit, aby zpětná volání používala pouze jiné vlastnosti závislosti a každá taková vlastnost závislostí má v rámci registrovaných metadat nastavenou výchozí hodnotu.

vzory konstruktoru Sejf

Abyste se vyhnuli rizikům neúplné inicializace, pokud se vaše třída používá jako základní třída, postupujte podle těchto vzorů:

Konstruktory bez parametrů volají základní inicializaci

Implementujte tyto konstruktory, které volají základní výchozí nastavení:

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

Jiné než výchozí (pohodlí) konstruktory, které neodpovídají žádným základním podpisům

Pokud tyto konstruktory používají parametry k nastavení vlastností závislostí v inicializaci, nejprve zavolejte vlastní konstruktor bez parametrů třídy pro inicializaci a pak pomocí parametrů nastavte vlastnosti závislosti. Mohou to být buď vlastnosti závislosti definované vaší třídou, nebo vlastnosti závislostí zděděné ze základních tříd, ale v obou případech použijte následující vzor:

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

Jiné než výchozí konstruktory (pohodlí), které odpovídají základním podpisům

Místo volání základního konstruktoru se stejnou parametrizací znovu zavolejte konstruktor bez parametrů vlastní třídy. Nevolejte základní inicializátor; místo toho byste měli zavolat this(). Potom reprodukujte původní chování konstruktoru pomocí předaných parametrů jako hodnot pro nastavení relevantních vlastností. Pokyny k určení vlastností, které mají konkrétní parametry nastavit, použijte původní dokumentaci k základnímu konstruktoru:

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

Musí odpovídat všem podpisům.

V případech, kdy základní typ obsahuje více podpisů, musíte záměrně shodovat všechny možné podpisy s vlastní implementací konstruktoru, který používá doporučený vzor volání konstruktoru bez parametrů třídy před nastavením dalších vlastností.

Nastavení vlastností závislosti pomocí SetValue

Stejné vzory platí, pokud nastavujete vlastnost, která nemá obálku pro usnadnění nastavení vlastnosti, a nastavte hodnoty pomocí SetValue. Vaše volání předávací SetValue parametry konstruktoru by také měla volat konstruktor bez parametrů třídy pro inicializaci.

Viz také