Шаблоны безопасного конструктора для DependencyObjects

Как правило, конструкторы классов не должны выполнять обратные вызовы, такие как виртуальные методы или делегаты, поскольку конструкторы могут вызываться в качестве базовой инициализации конструкторов для производного класса. Ввод виртуального объекта может выполняться в состоянии незавершенной инициализации любого заданного объекта. Однако сама система свойств внутренне вызывает и предоставляет обратные вызовы как часть системы свойств зависимостей. Такая простая операция, как установка значения свойства зависимостей с помощью вызова SetValue, потенциально включает обратный вызов в процессе определения. По этой причине следует соблюдать осторожность при установке значений свойств зависимостей в теле конструктора, что может стать проблемой, если тип используется в качестве базового класса. Существует шаблон для реализации конструкторов DependencyObject, позволяющий избежать определенных проблем с состояниями свойств зависимостей и обратными вызовами, которые здесь описаны.

Виртуальные методы системы свойств

Следующие виртуальные методы или обратные вызовы потенциально вызываются во время вычислений вызова SetValue, который устанавливает значение свойства зависимостей: ValidateValueCallback, PropertyChangedCallback, CoerceValueCallback, OnPropertyChanged. Каждый из этих виртуальных методов или обратных вызовов служит определенной цели в расширении универсальности системы свойств Windows Presentation Foundation (WPF) и свойств зависимостей. Дополнительные сведения об использовании этих виртуальных методов для настройки определения значений свойств см. в разделе Проверка и обратные вызовы свойств зависимостей.

Принудительное применение правил FXCop и виртуальные функции системы свойств

Если используется средство FXCop от Майкрософт как часть процесса построения и создаются производные от определенных классов среды WPF, вызывающие базовый конструктор, либо реализуются собственные свойства зависимостей в производных классах, можно столкнуться с нарушением правил FXCop. Строка имени для этого нарушения:

DoNotCallOverridableMethodsInConstructors

Это правило, которое является частью набора общих правил по умолчанию для FXCop. Это правило может сообщать о трассировке по системе свойств зависимостей, которая в конечном итоге вызывает виртуальный метод системы свойств зависимостей. Нарушение этого правила может по-прежнему отображаться даже после применения рекомендуемых шаблонов конструктора, описанных в этом разделе, поэтому может потребоваться отключить или запретить это правило в конфигурации набора правил FXCop.

Большинство проблем возникает из-за производных классов, а не использования существующих

Проблемы, о которых сообщает это правило, происходят при создании производного класса от класса, реализуемого с помощью виртуальных методов в его последовательности создания. Если запечатать класс или любым другим образом запретить создание производных от него классов, рассматриваемые здесь проблемы, связанные с правилом FXCop, для вас не применимы. Тем не менее при создании классов таким образом, чтобы они предназначались для использования в качестве базовых классов, например при создании шаблонов или расширяемого набора библиотек элементов управления, необходимо следовать шаблонам, рекомендуемым здесь для конструкторов.

Конструкторы по умолчанию должны инициализировать все значения, запрашиваемые обратными вызовами

Любые члены экземпляра, которые используются переопределениями класса или обратными вызовами (обратные вызовы из списка в разделе «Виртуальные методы системы свойств»), должны быть инициализированы в конструкторе класса по умолчанию, даже если некоторые из этих значений заполняются «реальными» значениями с помощью параметров конструкторов, содержащих параметры..

Следующий пример кода (и последующие примеры) представляет собой пример псевдокода 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, который вызывает виртуальный метод OnPropertyChanged в собственном MyClassDependencyObject. Переопределение ссылается на _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, проходящие через параметры конструктора, должны также вызывать конструктор класса по без параметров для инициализации.

См. также