DependencyObject 的安全建構函式模式

一般而言,類別建構函式不應該呼叫回呼 (例如,虛擬方法或委派),因為建構函式可以當成衍生類別之建構函式的基底初始化來呼叫。 進入虛擬項目,可能是在任何指定物件的未完成初始化狀態中完成的。 不過,屬性系統本身會在內部呼叫並公開回呼,以做為相依性屬性系統的一部分。 如同設定具有呼叫的相依性屬性值 SetValue 一樣簡單的作業,可能會包含判斷中某處的回呼。 基於這個理由,在建構函式的主體內設定相依性屬性值時應特別小心,如果您的類型是用來做為基底類別,這可能就會發生問題。 實作 DependencyObject 建構函式的特定模式可避免相依性屬性狀態和固有回呼的特定問題,這裡記載于此。

屬性系統虛擬方法

在設定相依性屬性值的 SetValue 呼叫計算期間,可能會呼叫下列虛擬方法或回呼: ValidateValueCallback 、、 PropertyChangedCallbackCoerceValueCallbackOnPropertyChanged 。 這些虛擬方法或回呼都是為了擴充 Windows Presentation Foundation (WPF) 屬性系統和相依性屬性的多功能性,提供特定用途。 如需如何使用這些虛擬項目來自訂屬性值判斷的詳細資訊,請參閱相依性屬性回呼和驗證

FXCop 規則強制執行與屬性系統虛擬

如果您使用 Microsoft 工具 FXCop 作為建置程式的一部分,而且您是從呼叫基底建構函式的特定 WPF 架構類別衍生,或在衍生類別上實作您自己的相依性屬性,則可能會遇到特定的 FXCop 規則違規。 此違規的名稱字串為:

DoNotCallOverridableMethodsInConstructors

此規則屬於 FXCop 的預設公用規則集。 此規則可能會報告哪些內容,是透過相依性屬性系統來追蹤,此系統最終會呼叫相依性屬性系統虛擬方法。 即使依照本主題中所述的建議建構函式模式來執行,此規則違規可能還會繼續出現,因此,您可能需要在 FXCop 規則集組態中停用或隱藏該規則。

大部分的問題均來自衍生類別,而非使用現有的類別

若接著會從中衍生您在其建構序列中使用虛擬方法實作的類別,就會發生此規則所報告的問題。 如果您密封類別,或是知道或強制您的類別將不會從中衍生,則此處所說明的考量和 FXCop 規則所引發的問題對您並不適用。 不過,如果您正以有意使用它們當做基底類別 (例如,如果您正在建立範本) 或可擴展控制項程式庫集的方式來撰寫類別,則應遵循此處針對建構函式所建議的模式。

預設建構函式必須初始化回呼要求的所有值

類別使用的任何實例成員都會覆寫或回呼(Property System Virtuals 區段中清單的回呼)都必須在類別無參數建構函式中初始化,即使有些值是透過非參數建構函式的參數填入「real」 值也一樣。

下列範例程式碼 (和後續範例) 是違反這項規則的虛擬 C# 範例,並將說明問題:

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

當應用程式程式碼呼叫 new MyClass(objectvalue) 時,這會呼叫無參數建構函式和基類建構函式。 然後,它會設定 Property1 = object1 ,它會在擁有 MyClassDependencyObject 上呼叫虛擬方法 OnPropertyChanged 。 覆寫是指尚未初始化的 _myList

避免這些問題的方法之一是,確定回呼只會使用其他相依性屬性,而且每個這類相依性屬性都已建立預設值做為其已註冊中繼資料的一部分。

安全的建構函式模式

若要在您的類別用來當做基底類別時,避免產生未完成初始化的風險,請遵循下列模式:

呼叫基底初始化的無參數建構函式

實作這些呼叫基底預設值的建構函式:

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

非預設 (便捷) 建構函式,不符合任何基底簽章

如果這些建構函式使用參數在初始化中設定相依性屬性,請先呼叫您自己的無參數建構函式進行初始化,然後使用參數來設定相依性屬性。 這些可能是您的類別所定義的相依性屬性或繼承自基底類別的相依性屬性,但在這任一種情況下,請使用下列模式:

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

非預設 (便捷) 建構函式,其符合任何基底簽章

不要使用相同的參數化呼叫基底建構函式,而是再次呼叫您自己的類別無參數建構函式。 請勿呼叫基底初始設定式;您應該改為呼叫 this()。 然後使用傳遞的參數做為用來設定相關屬性的值,藉以重現原始的建構函式行為。 使用原始的基底建構函式文件做為指引,來判斷要設定特定參數的屬性:

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

必須符合所有簽章

對於基底類型有多個簽章的情況下,您必須刻意將所有可能的簽章與您自己的建構函式實作相符,該實作會使用呼叫類別無參數建構函式的建議模式,再設定其他屬性。

使用 SetValue 設定相依性屬性

如果您要設定沒有包裝函式的屬性來設定便利性,並使用 來設定值 SetValue ,則適用這些相同的模式。 您對 該傳遞建構函式參數的呼叫 SetValue 也應該呼叫 類別的無參數建構函式以進行初始化。

另請參閱