Обучение
Схема обучения
Use advance techniques in canvas apps to perform custom updates and optimization - Training
Use advance techniques in canvas apps to perform custom updates and optimization
Этот браузер больше не поддерживается.
Выполните обновление до Microsoft Edge, чтобы воспользоваться новейшими функциями, обновлениями для системы безопасности и технической поддержкой.
Примечание
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
Существует общий принцип программирования управляемого кода, часто применяемый средствами анализа кода, что конструкторы классов не должны вызывать переопределиемые методы. Если переопределимый метод вызывается конструктором базового класса, а производный класс переопределяет этот метод, метод переопределения в производном классе может выполняться до конструктора производных классов. Если конструктор производных классов выполняет инициализацию класса, метод производного класса может получить доступ к неинициализированным членам класса. Классы свойств зависимостей должны избегать настройки значений свойств зависимостей в конструкторе классов, чтобы избежать проблем с инициализацией среды выполнения. В этой статье описывается, как реализовать DependencyObject конструкторы таким образом, чтобы избежать этих проблем.
Виртуальные методы и обратные вызовы зависимостей являются частью системы свойств Windows Presentation Foundation (WPF) и расширяют универсальность свойств зависимостей.
Основная операция, такая как установка значения свойства зависимости с помощью SetValue, вызовет событие OnPropertyChanged и потенциально несколько обратных вызовов системы свойств WPF.
OnPropertyChanged
пример виртуального метода системы свойств WPF, который можно переопределить классами, имеющими DependencyObject в иерархии наследования. Если задать значение свойства зависимости в конструкторе, который вызывается во время создания экземпляра вашего пользовательского класса свойства зависимости, и класс, производный от него, переопределяет виртуальный метод OnPropertyChanged
, то метод OnPropertyChanged
производного класса будет выполняться до вызова конструктора любого производного класса.
PropertyChangedCallback и CoerceValueCallback являются примерами обратных вызовов системы свойств WPF, которые могут быть зарегистрированы классами свойств зависимостей и переопределены классами, производными от них. Если вы задаёте значение свойства зависимости в конструкторе своего пользовательского класса свойств зависимости, и класс, производный от этого класса, переопределяет один из этих обратных вызовов в метаданных свойства, то обратный вызов производного класса будет выполняться до того, как будет вызван какой-либо конструктор производного класса. Проблема не имеет отношения к ValidateValueCallback, поскольку она не является частью метаданных свойств и может быть указана только классом, который регистрирует.
Дополнительные сведения о обратных вызовах свойств зависимостей см. в статье "Обратные вызовы свойств зависимостей" и "Проверка".
Анализаторы платформы компилятора .NET проверяют код C# или Visual Basic для проблем с качеством кода и стилем. При вызове переопределения методов в конструкторе при активном правиле анализатора CA2214 вы получите предупреждение CA2214: Don't call overridable methods in constructors
. Но правило не помечает виртуальные методы и обратные вызовы, вызываемые базовой системой свойств WPF, когда значение свойства зависимостей задается в конструкторе.
Если вы запечатываете пользовательский класс свойств зависимостей или иначе знаете, что класс не будет производным от, то проблемы инициализации среды выполнения производных классов не применяются к такому классу. Но если вы создаете наследуемый класс зависимых свойств, например, если вы создаете шаблоны или настраиваемую библиотеку контролов, избегайте вызова переопределяемых методов или установки значений зависимых свойств в конструкторе.
Следующий тестовый код демонстрирует небезопасный шаблон конструктора, где конструктор базового класса задает значение свойства зависимостей таким образом, активируя вызовы виртуальных методов и обратных вызовов.
private static void TestUnsafeConstructorPattern()
{
//Aquarium aquarium = new();
//Debug.WriteLine($"Aquarium temperature (C): {aquarium.TempCelcius}");
// Instantiate and set tropical aquarium temperature.
TropicalAquarium tropicalAquarium = new(tempCelcius: 25);
Debug.WriteLine($"Tropical aquarium temperature (C): " +
$"{tropicalAquarium.TempCelcius}");
/* Test output:
Derived class static constructor running.
Base class ValidateValueCallback running.
Base class ValidateValueCallback running.
Base class ValidateValueCallback running.
Base class parameterless constructor running.
Base class ValidateValueCallback running.
Derived class CoerceValueCallback running.
Derived class CoerceValueCallback: null reference exception.
Derived class OnPropertyChanged event running.
Derived class OnPropertyChanged event: null reference exception.
Derived class PropertyChangedCallback running.
Derived class PropertyChangedCallback: null reference exception.
Aquarium temperature (C): 20
Derived class parameterless constructor running.
Derived class parameter constructor running.
Base class ValidateValueCallback running.
Derived class CoerceValueCallback running.
Derived class OnPropertyChanged event running.
Derived class PropertyChangedCallback running.
Tropical aquarium temperature (C): 25
*/
}
}
public class Aquarium : DependencyObject
{
// Register a dependency property with the specified property name,
// property type, owner type, property metadata with default value,
// and validate-value callback.
public static readonly DependencyProperty TempCelciusProperty =
DependencyProperty.Register(
name: "TempCelcius",
propertyType: typeof(int),
ownerType: typeof(Aquarium),
typeMetadata: new PropertyMetadata(defaultValue: 0),
validateValueCallback:
new ValidateValueCallback(ValidateValueCallback));
// Parameterless constructor.
public Aquarium()
{
Debug.WriteLine("Base class parameterless constructor running.");
// Set typical aquarium temperature.
TempCelcius = 20;
Debug.WriteLine($"Aquarium temperature (C): {TempCelcius}");
}
// Declare public read-write accessors.
public int TempCelcius
{
get => (int)GetValue(TempCelciusProperty);
set => SetValue(TempCelciusProperty, value);
}
// Validate-value callback.
public static bool ValidateValueCallback(object value)
{
Debug.WriteLine("Base class ValidateValueCallback running.");
double val = (int)value;
return val >= 0;
}
}
public class TropicalAquarium : Aquarium
{
// Class field.
private static List<int> s_temperatureLog;
// Static constructor.
static TropicalAquarium()
{
Debug.WriteLine("Derived class static constructor running.");
// Create a new metadata instance with callbacks specified.
PropertyMetadata newPropertyMetadata = new(
defaultValue: 0,
propertyChangedCallback: new PropertyChangedCallback(PropertyChangedCallback),
coerceValueCallback: new CoerceValueCallback(CoerceValueCallback));
// Call OverrideMetadata on the dependency property identifier.
TempCelciusProperty.OverrideMetadata(
forType: typeof(TropicalAquarium),
typeMetadata: newPropertyMetadata);
}
// Parameterless constructor.
public TropicalAquarium()
{
Debug.WriteLine("Derived class parameterless constructor running.");
s_temperatureLog = new List<int>();
}
// Parameter constructor.
public TropicalAquarium(int tempCelcius) : this()
{
Debug.WriteLine("Derived class parameter constructor running.");
TempCelcius = tempCelcius;
s_temperatureLog.Add(tempCelcius);
}
// Property-changed callback.
private static void PropertyChangedCallback(DependencyObject depObj,
DependencyPropertyChangedEventArgs e)
{
Debug.WriteLine("Derived class PropertyChangedCallback running.");
try
{
s_temperatureLog.Add((int)e.NewValue);
}
catch (NullReferenceException)
{
Debug.WriteLine("Derived class PropertyChangedCallback: null reference exception.");
}
}
// Coerce-value callback.
private static object CoerceValueCallback(DependencyObject depObj, object value)
{
Debug.WriteLine("Derived class CoerceValueCallback running.");
try
{
s_temperatureLog.Add((int)value);
}
catch (NullReferenceException)
{
Debug.WriteLine("Derived class CoerceValueCallback: null reference exception.");
}
return value;
}
// OnPropertyChanged event.
protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e)
{
Debug.WriteLine("Derived class OnPropertyChanged event running.");
try
{
s_temperatureLog.Add((int)e.NewValue);
}
catch (NullReferenceException)
{
Debug.WriteLine("Derived class OnPropertyChanged event: null reference exception.");
}
// Mandatory call to base implementation.
base.OnPropertyChanged(e);
}
}
Private Shared Sub TestUnsafeConstructorPattern()
'Aquarium aquarium = new Aquarium();
'Debug.WriteLine($"Aquarium temperature (C): {aquarium.TempCelcius}");
' Instantiate And set tropical aquarium temperature.
Dim tropicalAquarium As New TropicalAquarium(tempCelc:=25)
Debug.WriteLine($"Tropical aquarium temperature (C):
{tropicalAquarium.TempCelcius}")
' Test output:
' Derived class static constructor running.
' Base class ValidateValueCallback running.
' Base class ValidateValueCallback running.
' Base class ValidateValueCallback running.
' Base class parameterless constructor running.
' Base class ValidateValueCallback running.
' Derived class CoerceValueCallback running.
' Derived class CoerceValueCallback: null reference exception.
' Derived class OnPropertyChanged event running.
' Derived class OnPropertyChanged event: null reference exception.
' Derived class PropertyChangedCallback running.
' Derived class PropertyChangedCallback: null reference exception.
' Aquarium temperature(C): 20
' Derived class parameterless constructor running.
' Derived class parameter constructor running.
' Base class ValidateValueCallback running.
' Derived class CoerceValueCallback running.
' Derived class OnPropertyChanged event running.
' Derived class PropertyChangedCallback running.
' Tropical Aquarium temperature (C): 25
End Sub
End Class
Public Class Aquarium
Inherits DependencyObject
'Register a dependency property with the specified property name,
' property type, owner type, property metadata with default value,
' and validate-value callback.
Public Shared ReadOnly TempCelciusProperty As DependencyProperty =
DependencyProperty.Register(
name:="TempCelcius",
propertyType:=GetType(Integer),
ownerType:=GetType(Aquarium),
typeMetadata:=New PropertyMetadata(defaultValue:=0),
validateValueCallback:=
New ValidateValueCallback(AddressOf ValidateValueCallback))
' Parameterless constructor.
Public Sub New()
Debug.WriteLine("Base class parameterless constructor running.")
' Set typical aquarium temperature.
TempCelcius = 20
Debug.WriteLine($"Aquarium temperature (C): {TempCelcius}")
End Sub
' Declare public read-write accessors.
Public Property TempCelcius As Integer
Get
Return GetValue(TempCelciusProperty)
End Get
Set(value As Integer)
SetValue(TempCelciusProperty, value)
End Set
End Property
' Validate-value callback.
Public Shared Function ValidateValueCallback(value As Object) As Boolean
Debug.WriteLine("Base class ValidateValueCallback running.")
Dim val As Double = CInt(value)
Return val >= 0
End Function
End Class
Public Class TropicalAquarium
Inherits Aquarium
' Class field.
Private Shared s_temperatureLog As List(Of Integer)
' Static constructor.
Shared Sub New()
Debug.WriteLine("Derived class static constructor running.")
' Create a new metadata instance with callbacks specified.
Dim newPropertyMetadata As New PropertyMetadata(
defaultValue:=0,
propertyChangedCallback:=
New PropertyChangedCallback(AddressOf PropertyChangedCallback),
coerceValueCallback:=
New CoerceValueCallback(AddressOf CoerceValueCallback))
' Call OverrideMetadata on the dependency property identifier.
TempCelciusProperty.OverrideMetadata(
forType:=GetType(TropicalAquarium),
typeMetadata:=newPropertyMetadata)
End Sub
' Parameterless constructor.
Public Sub New()
Debug.WriteLine("Derived class parameterless constructor running.")
s_temperatureLog = New List(Of Integer)()
End Sub
' Parameter constructor.
Public Sub New(tempCelc As Integer)
Me.New()
Debug.WriteLine("Derived class parameter constructor running.")
TempCelcius = tempCelc
s_temperatureLog.Add(TempCelcius)
End Sub
' Property-changed callback.
Private Shared Sub PropertyChangedCallback(depObj As DependencyObject,
e As DependencyPropertyChangedEventArgs)
Debug.WriteLine("Derived class PropertyChangedCallback running.")
Try
s_temperatureLog.Add(e.NewValue)
Catch ex As NullReferenceException
Debug.WriteLine("Derived class PropertyChangedCallback: null reference exception.")
End Try
End Sub
' Coerce-value callback.
Private Shared Function CoerceValueCallback(depObj As DependencyObject, value As Object) As Object
Debug.WriteLine("Derived class CoerceValueCallback running.")
Try
s_temperatureLog.Add(value)
Catch ex As NullReferenceException
Debug.WriteLine("Derived class CoerceValueCallback: null reference exception.")
End Try
Return value
End Function
' OnPropertyChanged event.
Protected Overrides Sub OnPropertyChanged(e As DependencyPropertyChangedEventArgs)
Debug.WriteLine("Derived class OnPropertyChanged event running.")
Try
s_temperatureLog.Add(e.NewValue)
Catch ex As NullReferenceException
Debug.WriteLine("Derived class OnPropertyChanged event: null reference exception.")
End Try
' Mandatory call to base implementation.
MyBase.OnPropertyChanged(e)
End Sub
End Class
Порядок вызова методов в небезопасном тесте шаблона конструктора:
Производный класс со статическим конструктором, который переопределяет метаданные свойства зависимостей для регистрации Aquarium
и PropertyChangedCallback в CoerceValueCallback.
Конструктор базового класса, который задает новое значение свойства зависимостей, что приводит к вызову SetValue метода. Вызов SetValue
активирует обратные вызовы и события в следующем порядке:
ValidateValueCallback, который реализуется в базовом классе. Этот обратный вызов не является частью метаданных свойства зависимостей и не может быть реализован в производном классе путем переопределения метаданных.
PropertyChangedCallback
, который реализуется в производном классе путем переопределения метаданных зависимого свойства. Этот обратный вызов вызывает исключение со ссылкой NULL при вызове метода в неинициализированном поле s_temperatureLog
класса.
CoerceValueCallback
, который реализуется в производном классе путем переопределения метаданных зависимого свойства. Этот обратный вызов вызывает исключение со ссылкой NULL при вызове метода в неинициализированном поле s_temperatureLog
класса.
OnPropertyChanged событие, которое реализуется в производном классе путем переопределения виртуального метода. Это событие вызывает исключение со ссылкой NULL при вызове метода в неинициализированном поле s_temperatureLog
класса.
Конструктор без параметров производного класса, который инициализирует s_temperatureLog
.
Конструктор производного параметра класса, который задает новое значение свойства зависимостей, что приводит к другому вызову SetValue
метода. Так как s_temperatureLog
теперь инициализирован, обратные вызовы и события будут выполняться без возникновения исключений ссылок null.
Эти проблемы инициализации можно избежать с помощью безопасных шаблонов конструкторов.
Проблемы инициализации производного класса, демонстрируемые в тестовом коде, можно устранить разными способами, включая:
Избегайте задания значения свойства зависимостей в конструкторе пользовательского класса свойств зависимостей, если класс может использоваться в качестве базового класса. Если необходимо инициализировать значение свойства зависимостей, рекомендуется задать требуемое значение в качестве значения по умолчанию в метаданных свойств во время регистрации свойств зависимостей или при переопределении метаданных.
Инициализировать поля производного класса перед их использованием. Например, используя любой из следующих подходов:
Создайте экземпляр и назначьте поля экземпляра в одной инструкции. В предыдущем примере оператор List<int> s_temperatureLog = new();
позволяет избежать позднего назначения.
Выполните назначение в статическом конструкторе производного класса, который запускается перед любым конструктором базового класса. В предыдущем примере при вводе инструкции s_temperatureLog = new List<int>();
присваивания в производное статическое конструктор класса избегает позднего назначения.
Используйте отложенную инициализацию и создание экземпляров, которая инициализирует объекты как и когда они необходимы. В предыдущем примере создание экземпляров и назначение s_temperatureLog
с помощью отложенной инициализации и создания экземпляров не позволит избежать позднего назначения. Дополнительные сведения см. в разделе "Отложенная инициализация".
Избегайте использования неинициализированных переменных класса в системных вызовах и событиях системы свойств WPF.
Отзыв о .NET Desktop feedback
.NET Desktop feedback — это проект с открытым исходным кодом. Выберите ссылку, чтобы оставить отзыв:
Обучение
Схема обучения
Use advance techniques in canvas apps to perform custom updates and optimization - Training
Use advance techniques in canvas apps to perform custom updates and optimization
События