DependencyObject 的安全构造函数模式Safe Constructor Patterns for DependencyObjects

通常,类构造函数不应调用诸如虚拟方法或委托等回叫,其原因是构造函数可作为派生类的构造函数的基本初始化进行调用。Generally, class constructors should not call callbacks such as virtual methods or delegates, because constructors can be called as base initialization of constructors for a derived class. 输入该虚拟的操作可能会在任何给定对象的不完全初始化状态下进行。Entering the virtual might be done at an incomplete initialization state of any given object. 但是,属性系统本身在内部调用并公开回叫,作为依赖属性系统的一部分。However, the property system itself calls and exposes callbacks internally, as part of the dependency property system. 简单的操作是使用 SetValue 调用设置依赖属性值,这可能会在确定的某个位置包含回调。As simple an operation as setting a dependency property value with SetValue call potentially includes a callback somewhere in the determination. 因此,在构造函数体内设置依赖属性值时应保持谨慎(将类型用作基类可能会导致问题)。For this reason, you should be careful when setting dependency property values within the body of a constructor, which can become problematic if your type is used as a base class. 提供了一种特定模式来实现 DependencyObject 构造函数,这些构造函数避免了依赖属性状态和内在回调的特定问题,此处对此进行了介绍。There is a particular pattern for implementing DependencyObject constructors that avoids specific problems with dependency property states and the inherent callbacks, which is documented here.

属性系统虚拟方法Property System Virtual Methods

在设置依赖属性值的 SetValue 调用的计算过程中,可能会调用以下虚拟方法或回调: ValidateValueCallbackPropertyChangedCallbackCoerceValueCallbackOnPropertyChangedThe following virtual methods or callbacks are potentially called during the computations of the SetValue call that sets a dependency property value: ValidateValueCallback, PropertyChangedCallback, CoerceValueCallback, OnPropertyChanged. 这些虚拟方法或回叫中的每一个在扩展 Windows Presentation Foundation (WPF)Windows Presentation Foundation (WPF) 属性系统和依赖属性的多样性方面都具有特定的用途。Each of these virtual methods or callbacks serves a particular purpose in expanding the versatility of the Windows Presentation Foundation (WPF)Windows Presentation Foundation (WPF) property system and dependency properties. 有关如何使用这些虚拟方法对属性值确定进行自定义的详细信息,请参阅依赖属性回调和验证For more information on how to use these virtuals to customize property value determination, see Dependency Property Callbacks and Validation.

FXCop 规则强制与属性系统虚方法FXCop Rule Enforcement vs. Property System Virtuals

如果将 Microsoft 工具 FXCop 用作生成过程的一部分,并从某些调用基构造函数的 WPFWPF 框架类派生,或在派生的类上实现自己的依赖属性,则可能会违反某个 FXCop 规则。If you use the Microsoft tool FXCop as part of your build process, and you either derive from certain WPFWPF framework classes calling the base constructor, or implement your own dependency properties on derived classes, you might encounter a particular FXCop rule violation. 此违反事件的名称字符串为:The name string for this violation is:

DoNotCallOverridableMethodsInConstructors

此规则是为 FXCop 设置的默认公共规则的一部分。This is a rule that is part of the default public rule set for FXCop. 此规则所报告的内容可能为通过依赖属性系统实现的跟踪情况,该系统会最终调用依赖属性系统虚拟方法。What this rule might be reporting is a trace through the dependency property system that eventually calls a dependency property system virtual method. 即使遵循本主题中介绍并建议使用的构造函数模式,仍可能存在此规则冲突,因此可能需要在 FXCop 规则设置配置中禁用或取消该规则。This rule violation might continue to appear even after following the recommended constructor patterns documented in this topic, so you might need to disable or suppress that rule in your FXCop rule set configuration.

大多数问题是因派生类导致的,而不是因为使用现有类所致Most Issues Come From Deriving Classes, Not Using Existing Classes

当按构造顺序使用虚拟方法实现一个类后从该类进行派生时,便会发生此规则所报告的问题。The issues reported by this rule occur when a class that you implement with virtual methods in its construction sequence is then derived from. 如果密封了类,或者确信不会派生自此类或强制不得派生自此类,则此处介绍的注意事项和触发 FXCop 规则的问题将不适用。If you seal your class, or otherwise know or enforce that your class will not be derived from, the considerations explained here and the issues that motivated the FXCop rule do not apply to you. 但是,如果出于将类用作基类的目的来创作类,例如,如果正创建模板或可扩展的控件库集,则应遵循此处建议的用于构造函数的模式。However, if you are authoring classes in such a way that they are intended to be used as base classes, for instance if you are creating templates, or an expandable control library set, you should follow the patterns recommended here for constructors.

默认构造函数必须初始化由回叫请求的所有值Default Constructors Must Initialize All Values Requested By Callbacks

您的类所使用的任何实例成员(从属性系统虚方法部分的列表中的回调)都必须在您的类无参数构造函数中进行初始化,即使其中一些值通过 "真实" 值填充nonparameterless 构造函数的参数。Any instance members that are used by your class overrides or callbacks (the callbacks from the list in the Property System Virtuals section) must be initialized in your class parameterless constructor, even if some of those values are filled by "real" values through parameters of the nonparameterless constructors.

以下代码示例(和后面的示例)是一个与此规则冲突的伪 C# 示例,其中对问题进行了说明:The following example code (and subsequent examples) is a pseudo-C# example that violates this rule and explains the 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;  
}  

当应用程序代码调用 new MyClass(objectvalue)时,这将调用无参数构造函数和基类构造函数。When application code calls new MyClass(objectvalue), this calls the parameterless constructor and base class constructors. 然后,它会设置 Property1 = object1,这将对所属 MyClass DependencyObject调用虚拟方法 OnPropertyChangedThen it sets Property1 = object1, which calls the virtual method OnPropertyChanged on the owning MyClass DependencyObject. 重写引用尚未初始化的 _myListThe override refers to _myList, which has not been initialized yet.

避免这些问题的一个方法是:确保回叫仅使用其他依赖属性,并且每个这样的依赖属性都有一个确立的默认值作为其注册元数据的一部分。One way to avoid these issues is to make sure that callbacks use only other dependency properties, and that each such dependency property has an established default value as part of its registered metadata.

安全构造函数模式Safe Constructor Patterns

若要在将类用作基类的情况下规避未完全实现初始化的风险,请遵循以下模式:To avoid the risks of incomplete initialization if your class is used as a base class, follow these patterns:

调用基初始化的无参数构造函数Parameterless constructors calling base initialization

实现以下用于调用基本默认值的构造函数:Implement these constructors calling the base default:

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

非默认(方便)构造函数,不与任何基本签名匹配Non-default (convenience) constructors, not matching any base signatures

如果这些构造函数使用参数设置初始化中的依赖项属性,请首先调用自己的类参数构造函数进行初始化,然后使用参数设置依赖属性。If these constructors use the parameters to set dependency properties in the initialization, first call your own class parameterless constructor for initialization, and then use the parameters to set dependency properties. 这些可能是类所定义的依赖属性,也可能是从基类继承的依赖属性,但在这两种情况下都使用以下模式:These could either be dependency properties defined by your class, or dependency properties inherited from base classes, but in either case use the following pattern:

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

非默认(方便)构造函数,确实与基本签名匹配Non-default (convenience) constructors, which do match base signatures

不要用相同的参数化调用基构造函数,而应再次调用自己的类的无参数构造函数。Instead of calling the base constructor with the same parameterization, again call your own class' parameterless constructor. 请勿调用基本初始值设定项,而应调用 this()Do not call the base initializer; instead you should call this(). 然后使用传递的参数作为用于设置相关属性的值来重现原始构造函数行为。Then reproduce the original constructor behavior by using the passed parameters as values for setting the relevant properties. 将原始基本构造函数文档用作指导依据,确定要对特定参数设置的属性:Use the original base constructor documentation for guidance in determining the properties that the particular parameters are intended to set:

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

必须匹配所有签名Must match all signatures

对于基类型具有多个签名的情况,你必须将所有可能的签名与你自己的构造函数实现进行了匹配,以便在进一步设置之前使用调用类无参数构造函数的推荐模式属性.For cases where the base type has multiple signatures, you must deliberately match all possible signatures with a constructor implementation of your own that uses the recommended pattern of calling the class parameterless constructor before setting further properties.

使用 SetValue 设置依赖属性Setting dependency properties with SetValue

如果要设置的属性没有属性设置的包装,则这些相同的模式也适用,并使用 SetValue设置值。These same patterns apply if you are setting a property that does not have a wrapper for property setting convenience, and set values with SetValue. 对通过构造函数参数的 SetValue 的调用还应调用类的无参数构造函数以进行初始化。Your calls to SetValue that pass through constructor parameters should also call the class' parameterless constructor for initialization.

另请参阅See also