Aracılığıyla paylaş


DependencyObjects için güvenli oluşturucu desenleri (WPF .NET)

Yönetilen kod programlamada genellikle kod çözümleme araçları tarafından zorlanan, sınıf oluşturucularının geçersiz kılınabilir yöntemleri çağırmaması gereken genel bir ilke vardır. Geçersiz kılınabilir bir yöntem temel sınıf oluşturucusu tarafından çağrılırsa ve türetilmiş bir sınıf bu yöntemi geçersiz kılarsa, türetilmiş sınıftaki geçersiz kılma yöntemi türetilmiş sınıf oluşturucusunun önünde çalıştırılabilir. Türetilmiş sınıf oluşturucu sınıf başlatma gerçekleştirirse, türetilmiş sınıf yöntemi başlatılmamış sınıf üyelerine erişebilir. Bağımlılık özelliği sınıfları, çalışma zamanı başlatma sorunlarını önlemek için sınıf oluşturucusunda bağımlılık özelliği değerlerini ayarlamaktan kaçınmalıdır. Bu makalede, oluşturucuların bu sorunlardan kaçınacak şekilde nasıl uygulandığı DependencyObject açıklanmaktadır.

Özellik sistemi sanal yöntemleri ve geri çağırmaları

Bağımlılık özelliği sanal yöntemleri ve geri çağırmaları, Windows Presentation Foundation (WPF) özellik sisteminin bir parçasıdır ve bağımlılık özelliklerinin çok yönlülüğünü genişletir.

kullanarak SetValue bağımlılık özelliği değeri ayarlama gibi temel bir işlem, olayı ve büyük olasılıkla birkaç WPF özellik sistemi geri çağırmasını çağırır OnPropertyChanged .

OnPropertyChanged , devralma hiyerarşisindeki sınıflar DependencyObject tarafından geçersiz kılınabilen wpf özellik sistemi sanal yöntemine bir örnektir. Özel bağımlılık özellik sınıfınızın örneği oluşturulurken çağrılan bir oluşturucuda bağımlılık özelliği değeri ayarlarsanız ve ondan türetilen bir sınıf sanal yöntemi geçersiz kılarsa OnPropertyChanged , türetilmiş sınıf yöntemi herhangi bir türetilmiş sınıf OnPropertyChanged oluşturucusundan önce çalışır.

PropertyChangedCallback ve CoerceValueCallback bağımlılık özellik sınıfları tarafından kaydedilebilen ve bunlardan türetilen sınıflar tarafından geçersiz kılınabilen WPF özellik sistemi geri çağırma örnekleridir. Özel bağımlılık özellik sınıfınızın oluşturucusunda bir bağımlılık özelliği değeri ayarlarsanız ve bu değerden türetilen bir sınıf özellik meta verilerindeki bu geri çağırmalardan birini geçersiz kılarsa, türetilmiş sınıf geri çağrısı türetilmiş sınıf oluşturucularından önce çalışır. Özellik meta verilerinin bir parçası olmadığından ve yalnızca kayıt sınıfı tarafından belirtilebildiği için bu sorun ile ilgili ValidateValueCallback değildir.

Bağımlılık özelliği geri çağırmaları hakkında daha fazla bilgi için bkz . Bağımlılık özelliği geri çağırmaları ve doğrulama.

.NET çözümleyicileri

.NET derleyici platformu çözümleyicileri, kod kalitesi ve stil sorunları için C# veya Visual Basic kodunuzu inceler. Çözümleyici kuralı CA2214 etkin olduğunda bir oluşturucuda geçersiz kılınabilir yöntemleri çağırırsanız uyarısını CA2214: Don't call overridable methods in constructorsalırsınız. Ancak kural, bir bağımlılık özelliği değeri bir oluşturucuda ayarlandığında temel WPF özellik sistemi tarafından çağrılan sanal yöntemleri ve geri çağırmaları işaretlemez.

Türetilmiş sınıfların neden olduğu sorunlar

Özel bağımlılık özellik sınıfınızı mühürlerseniz veya sınıfınızın türetilmeyeceğini biliyorsanız, türetilmiş sınıf çalışma zamanı başlatma sorunları bu sınıf için geçerli değildir. Ancak, devralınabilir bir bağımlılık özelliği sınıfı oluşturursanız, örneğin şablon veya genişletilebilir bir denetim kitaplığı kümesi oluşturuyorsanız geçersiz kılınabilir yöntemleri çağırmaktan veya bir oluşturucudan bağımlılık özelliği değerleri ayarlamaktan kaçının.

Aşağıdaki test kodu, temel sınıf oluşturucusunun bağımlılık özelliği değeri ayarlayıp sanal yöntemlere ve geri çağırmalara çağrıları tetiklediği güvenli olmayan bir oluşturucu desenini gösterir.

    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

Güvenli olmayan oluşturucu desen testinde yöntemlerin çağrılma sırası:

  1. ve CoerceValueCallbackkaydetmek PropertyChangedCallback için bağımlılık özelliği meta verilerini Aquarium geçersiz kılan türetilmiş sınıf statik oluşturucu.

  2. Yöntemine çağrıyla SetValue sonuçlanan yeni bir bağımlılık özelliği değeri ayarlayan temel sınıf oluşturucu. Çağrı, SetValue geri çağırmaları ve olayları aşağıdaki sırayla tetikler:

    1. ValidateValueCallback, temel sınıfında uygulanır. Bu geri çağırma bağımlılık özelliği meta verilerinin bir parçası değildir ve meta veriler geçersiz kılınarak türetilmiş sınıfta uygulanamaz.

    2. PropertyChangedCallback, bağımlılık özelliği meta verileri geçersiz kılınarak türetilmiş sınıfta uygulanır. Bu geri çağırma, başlatılmamış sınıf alanında s_temperatureLogbir yöntem çağırdığında null başvuru özel durumu oluşturur.

    3. CoerceValueCallback, bağımlılık özelliği meta verileri geçersiz kılınarak türetilmiş sınıfta uygulanır. Bu geri çağırma, başlatılmamış sınıf alanında s_temperatureLogbir yöntem çağırdığında null başvuru özel durumu oluşturur.

    4. OnPropertyChanged sanal yöntemi geçersiz kılarak türetilmiş sınıfta uygulanan event. Bu olay başlatılmamış sınıf alanında s_temperatureLogbir yöntem çağırdığında null başvuru özel duruma neden olur.

  3. türetilmiş sınıf parametresiz oluşturucu, tarafından başlatılır s_temperatureLog.

  4. Yöntemine başka bir çağrıyla SetValue sonuçlanan yeni bir bağımlılık özelliği değeri ayarlayan türetilmiş sınıf parametresi oluşturucu. Artık s_temperatureLog başlatıldığından, geri çağırmalar ve olaylar null başvuru özel durumlarına neden olmadan çalışır.

Bu başlatma sorunları, güvenli oluşturucu desenlerinin kullanılmasıyla önlenebilir.

Güvenli oluşturucu desenleri

Test kodunda belirtilen türetilmiş sınıf başlatma sorunları aşağıdakiler gibi farklı yollarla çözülebilir:

  • Sınıfınız temel sınıf olarak kullanılıyorsa, özel bağımlılık özellik sınıfınızın oluşturucusunda bağımlılık özelliği değeri ayarlamaktan kaçının. Bir bağımlılık özelliği değeri başlatmanız gerekiyorsa, bağımlılık özelliği kaydı sırasında veya meta verileri geçersiz kılma sırasında gerekli değeri özellik meta verilerinde varsayılan değer olarak ayarlamayı göz önünde bulundurun.

  • Türetilmiş sınıf alanlarını kullanılmadan önce başlatın. Örneğin, aşağıdaki yaklaşımlardan herhangi birini kullanarak:

    • Tek bir deyimde örnek alanlarının örneğini oluşturun ve atayın. Önceki örnekte deyimi List<int> s_temperatureLog = new(); geç atamayı önleseydi.

    • Herhangi bir temel sınıf oluşturucusunun önünde çalışan türetilmiş sınıf statik oluşturucusunda atama gerçekleştirin. Önceki örnekte, atama deyimini s_temperatureLog = new List<int>(); türetilmiş sınıf statik oluşturucusunun içine koymak geç atamayı önleyemli olurdu.

    • Nesneleri gerektiği gibi ve gerektiğinde başlatan yavaş başlatma ve örnekleme kullanın. Önceki örnekte, yavaş başlatma ve örnekleme kullanarak örnekleme ve atama s_temperatureLog , geç atamayı önleyemli olacaktır. Daha fazla bilgi için bkz . Gecikmeli başlatma.

  • WPF özellik sistemi geri çağırmalarında ve olaylarında başlatılmamış sınıf değişkenlerini kullanmaktan kaçının.

Ayrıca bkz.