Поделиться через


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

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

В этом разделе содержатся следующие подразделы.

  • Виртуальные методы системы свойств
  • Шаблоны безопасных конструкторов
  • Связанные разделы

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

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

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

Если вы используете средства Microsoft 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 для владельца MyClass DependencyObject. Переопределение относится к _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, которые проходят через параметры конструктора, должны также вызывать конструктор класса по умолчанию для инициализации.

См. также

Основные понятия

Пользовательские свойства зависимостей

Общие сведения о свойствах зависимости

Безопасность свойства зависимости