Condividi tramite


Safe Constructor Patterns for Dependency Objects

Microsoft Silverlight will reach end of support after October 2021. Learn more.

This topic describes some best practices for safe constructor patterns for objects used in Silverlight to avoid a particular issue. The issue results from an interaction between the Silverlight dependency property system, and the initialization sequence for a .NET class based on invoking an overloaded constructor.

The Theory Behind Safe Constructor Patterns

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 method 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. Specifically, the PropertyChangedCallback is potentially called during the computations of the SetValue call that sets a dependency property value. 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. 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.

FxCop Rule Enforcement vs. Property System Virtuals

If you use the Microsoft tool FxCop as part of your build process, and you either derive from certain Silverlight 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

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. 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. 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

Any instance members that are used by your class overrides or callbacks (PropertyChangedCallback) must be initialized in your class default constructor, even if some of those values are filled by "real" values through parameters of the nondefault constructors.

The following example code (and subsequent examples) is a pseudo-code example that violates this rule and explains the problem:

Public Class [MyClass] 
    Inherits DependencyObject 
    Public Sub New() 
    End Sub 
    Public Sub New(ByVal toSetWobble As Object) 
        Me.New() 
        Wobble = toSetWobble 
        'this is backed by a DependencyProperty 
        _myList = New List(Of Object)() 
        ' this line should be in the default ctor 
    End Sub 

    Public Shared ReadOnly WobbleProperty As DependencyProperty = DependencyProperty.Register("Wobble", GetType(Object), GetType([MyClass]), New PropertyMetadata(New PropertyChangedCallback(AddressOf OnWobblePropertyChanged)))
    Public Property Wobble() As Object 
        Get 
            Return GetValue(WobbleProperty) 
        End Get 
        Set(ByVal value As Object) 
            SetValue(WobbleProperty, value) 
        End Set 
    End Property 
    Private Shared Sub OnWobblePropertyChanged(ByVal sender As Object, ByVal e As DependencyPropertyChangedEventArgs) 
        Dim count As Integer = TryCast(sender, [MyClass])._myList.Count 
        ' null-reference exception here on instantiation 
    End Sub 
    Private _myList As List(Of Object) 
End Class 
public class MyClass : DependencyObject
{
    public MyClass() { }
    public MyClass(object toSetWobble)
        : this()
    {
        Wobble = toSetWobble; //this is backed by a DependencyProperty
        _myList = new List<object>();    // this line should be in the default ctor
    }

    public static readonly DependencyProperty WobbleProperty =
        DependencyProperty.Register("Wobble", typeof(object), typeof(MyClass), new PropertyMetadata(new PropertyChangedCallback(OnWobblePropertyChanged)));
    public object Wobble
    {
        get { return GetValue(WobbleProperty); }
        set { SetValue(WobbleProperty, value); }
    }
    static void OnWobblePropertyChanged(object sender, DependencyPropertyChangedEventArgs e)
    {
        int count = (sender as MyClass)._myList.Count;    // null-reference exception here on instantiation
    }
    private List<object> _myList;
}

When application code calls new MyClass(objectvalue), this calls the default constructor and base class constructors. Then it sets Property1 = object1, which calls the virtual method OnPropertyChanged on the owning MyClass DependencyObject. The 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:

Default 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.
    }
}
Public MyClass Inherits SomeBaseClass {
    Public Sub New()
        MyBase.New()
        ' ALL class initialization, including initial defaults for 
        ' possible values that other ctors specify or that callbacks need.
    End Sub
End Class

Nondefault (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 default 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;
    }
}
Public MyClass Inherits SomeBaseClass {
    Public Sub New(toSetProperty1 As Object)
        Me.New()
        ' Class initialization NOT done by default.
        ' Then, set properties to values as passed in ctor parameters.
        Property1 = toSetProperty1
    End Sub
End Class

Nondefault (convenience) constructors, which do match base signatures

Instead of calling the base constructor with the same parameterization, again call your own class's default constructor. 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;
    }
}
Public MyClass Inherits SomeBaseClass
    Public Sub New(toSetProperty1 As Object)
        Me.New()
        ' Class initialization NOT done by default.
        ' Then, set properties to values as passed in ctor parameters.
        Property1 = toSetProperty1
    End Sub
End Class

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 default constructor before setting further properties.

Setting dependency properties with 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. Your calls to SetValue that pass through constructor parameters should also call the class default constructor for initialization.