Bezpieczne wzorce konstruktora DependencyObjects

Ogólnie rzecz biorąc konstruktory klas nie powinny wywoływać wywołań zwrotnych, takich jak metody wirtualne lub delegaty, ponieważ konstruktory mogą być wywoływane jako podstawowe inicjowanie konstruktorów dla klasy pochodnej. Wprowadzanie maszyny wirtualnej może odbywać się w niekompletnym stanie inicjowania dowolnego obiektu. Jednak sam system właściwości wywołuje i uwidacznia wywołania zwrotne wewnętrznie w ramach systemu właściwości zależności. Tak prosta operacja, jak ustawienie wartości właściwości zależności z SetValue wywołaniem potencjalnie zawiera wywołanie zwrotne gdzieś w ustaleniu. Z tego powodu należy zachować ostrożność podczas ustawiania wartości właściwości zależności w treści konstruktora, co może stać się problematyczne, jeśli typ jest używany jako klasa bazowa. Istnieje konkretny wzorzec implementacji DependencyObject konstruktorów, który pozwala uniknąć konkretnych problemów ze stanami właściwości zależności i nieodłącznymi wywołaniami zwrotnymi, które opisano tutaj.

Metody wirtualne systemu właściwości

Następujące metody wirtualne lub wywołania zwrotne są potencjalnie wywoływane podczas obliczeń SetValue wywołania, które ustawia wartość właściwości zależności: ValidateValueCallback, , PropertyChangedCallbackCoerceValueCallback, OnPropertyChanged. Każda z tych metod wirtualnych lub wywołań zwrotnych służy do określonego celu w rozszerzaniu wszechstronności systemu właściwości Windows Presentation Foundation (WPF) i właściwości zależności. Aby uzyskać więcej informacji na temat sposobu używania tych maszyn wirtualnych do dostosowywania określania wartości właściwości, zobacz Wywołania zwrotne właściwości zależności i walidacja.

Wymuszanie reguł FXCop a maszyny wirtualne systemu właściwości

Jeśli używasz narzędzia firmy Microsoft FXCop w ramach procesu kompilacji i pochodzisz z niektórych klas struktur WPF wywołujących konstruktor podstawowy lub implementujesz własne właściwości zależności dla klas pochodnych, może wystąpić określone naruszenie reguły FXCop. Ciąg nazwy dla tego naruszenia to:

DoNotCallOverridableMethodsInConstructors

Jest to reguła, która jest częścią domyślnego publicznego zestawu reguł dla FXCop. Ta reguła może zgłaszać ślad za pośrednictwem systemu właściwości zależności, który ostatecznie wywołuje metodę wirtualną systemu właściwości zależności. Naruszenie tej reguły może nadal występować nawet po wykonaniu zalecanych wzorców konstruktorów udokumentowanych w tym temacie, dlatego może być konieczne wyłączenie lub pominięcie tej reguły w konfiguracji zestawu reguł FXCop.

Większość problemów pochodzi z klas pochodnych, które nie korzystają z istniejących klas

Problemy zgłaszane przez tę regułę występują, gdy klasa implementowana za pomocą metod wirtualnych w jej sekwencji budowy jest następnie pochodna. Jeśli przypieczętujesz klasę lub w inny sposób wiesz lub wymusisz, że klasa nie będzie pochodzić, zagadnienia wyjaśnione tutaj i problemy, które motywowały regułę FXCop, nie mają zastosowania do Ciebie. Jeśli jednak tworzysz klasy w taki sposób, aby były one przeznaczone do użycia jako klasy bazowe, na przykład w przypadku tworzenia szablonów lub zestawu rozszerzalnej biblioteki kontrolek, należy postępować zgodnie z wzorcami zalecanymi tutaj dla konstruktorów.

Konstruktory domyślne muszą inicjować wszystkie wartości żądane przez wywołania zwrotne

Wszystkie składowe wystąpienia, które są używane przez klasy przesłonięcia lub wywołania zwrotne (wywołania zwrotne z listy w sekcji Właściwości System Virtuals) muszą zostać zainicjowane w konstruktorze bez parametrów klasy, nawet jeśli niektóre z tych wartości są wypełnione przez "rzeczywiste" wartości za pomocą parametrów konstruktorów nieparametrycznych.

Poniższy przykładowy kod (i kolejne przykłady) to przykład pseudo-C#, który narusza tę regułę i wyjaśnia problem:

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

Gdy kod aplikacji wywołuje new MyClass(objectvalue)metodę , wywołuje konstruktor bez parametrów i konstruktory klas bazowych. Następnie ustawia Property1 = object1metodę , która wywołuje metodę OnPropertyChanged wirtualną we własnym obiekcie MyClassDependencyObject. Przesłonięcia odwołują się do _myListelementu , który nie został jeszcze zainicjowany.

Jednym ze sposobów uniknięcia tych problemów jest upewnienie się, że wywołania zwrotne używają tylko innych właściwości zależności i że każda taka właściwość zależności ma ustanowioną wartość domyślną w ramach zarejestrowanych metadanych.

wzorce konstruktorów Sejf

Aby uniknąć ryzyka niekompletnego inicjowania, jeśli klasa jest używana jako klasa bazowa, postępuj zgodnie z następującymi wzorcami:

Konstruktory bez parametrów wywołujące inicjowanie podstawowe

Zaimplementuj te konstruktory wywołujące wartość domyślną podstawową:

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

Konstruktory inne niż domyślne (wygoda), które nie pasują do żadnych podpisów podstawowych

Jeśli te konstruktory używają parametrów do ustawiania właściwości zależności w inicjowaniu, najpierw wywołaj własny konstruktor bez parametrów klasy na potrzeby inicjowania, a następnie użyj parametrów, aby ustawić właściwości zależności. Mogą to być właściwości zależności zdefiniowane przez klasę lub właściwości zależności dziedziczone z klas bazowych, ale w obu przypadkach użyj następującego wzorca:

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

Konstruktory inne niż domyślne (wygoda), które są zgodne z podpisami podstawowymi

Zamiast wywoływać konstruktor podstawowy z tą samą parametryzacji, ponownie wywołaj konstruktor bez parametrów własnej klasy. Nie należy wywoływać inicjatora podstawowego; Zamiast tego należy wywołać metodę this(). Następnie odtwórz oryginalne zachowanie konstruktora przy użyciu przekazanych parametrów jako wartości do ustawiania odpowiednich właściwości. Skorzystaj z oryginalnej dokumentacji konstruktora podstawowego, aby uzyskać wskazówki dotyczące określania właściwości, które mają być ustawione przez określone parametry:

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

Musi być zgodna ze wszystkimi podpisami

W przypadkach, gdy typ podstawowy ma wiele podpisów, należy celowo dopasować wszystkie możliwe podpisy z implementacją konstruktora własnego, który używa zalecanego wzorca wywoływania konstruktora bez parametrów klasy przed ustawieniem dalszych właściwości.

Ustawianie właściwości zależności za pomocą polecenia SetValue

Te same wzorce mają zastosowanie, jeśli ustawiasz właściwość, która nie ma otoki dla ustawienia właściwości wygody i ustawia wartości za pomocą SetValuepolecenia . Wywołania do SetValue tego przekazywania parametrów konstruktora powinny również wywoływać konstruktor bez parametrów klasy na potrzeby inicjowania.

Zobacz też