Delen via


Veilige constructorpatronen voor DependencyObjects

Over het algemeen mogen klasseconstructors geen callbacks aanroepen, zoals virtuele methoden of gemachtigden, omdat constructors kunnen worden aangeroepen als basisinitialisatie van constructors voor een afgeleide klasse. Het betreden van de virtuele omgeving kan plaatsvinden in een onvolledig geïnitialiseerde toestand van een object. Het eigenschappensysteem zelf roept echter intern callbacks aan en maakt deze beschikbaar als onderdeel van het afhankelijke eigenschapsysteem. Een operatie zo eenvoudig als het instellen van een waarde voor een afhankelijkheidseigenschap met een SetValue-aanroep omvat mogelijk ergens een callback in het vaststellingsproces. Daarom moet u voorzichtig zijn bij het instellen van afhankelijkheidseigenschapswaarden in het lichaam van een constructor, wat problematisch kan worden als uw type als basisklasse wordt gebruikt. Er is een bepaald patroon voor het implementeren van DependencyObject constructors die specifieke problemen met statussen van afhankelijkheden en de inherente callbacks voorkomen, die hier worden beschreven.

Virtuele methoden voor het eigendomssysteem

De volgende virtuele methoden of callbacks worden mogelijk aangeroepen tijdens de berekeningen van de SetValue-aanroep waarmee een waarde voor een afhankelijkheidseigenschap wordt ingesteld: ValidateValueCallback, PropertyChangedCallback, CoerceValueCallback, OnPropertyChanged. Elk van deze virtuele methoden of callbacks dient een bepaald doel bij het uitbreiden van de veelzijdigheid van het WPF-eigenschappensysteem (Windows Presentation Foundation) en afhankelijkheidseigenschappen. Voor meer informatie over hoe u deze virtuals kunt gebruiken om de bepaling van eigenschapswaarden aan te passen, zie Dependency Property Callbacks en Validation.

FXCop-regelhandhaving versus virtuele eigenschappen van het systeem

Als u het Microsoft-hulpprogramma FXCop gebruikt als onderdeel van uw buildproces en u bent afgeleid van bepaalde WPF-frameworkklassen die de basisconstructor aanroepen of uw eigen afhankelijkheidseigenschappen implementeren voor afgeleide klassen, kan er een bepaalde SCHENDING van de FXCop-regel optreden. De naamstring voor deze schending is:

DoNotCallOverridableMethodsInConstructors

Dit is een regel die deel uitmaakt van de standaard openbare regelset voor FXCop. Wat deze regel mogelijk rapporteert, is een tracering via het afhankelijkheidseigenschapssysteem dat uiteindelijk een virtuele methode voor het afhankelijkheidseigenschapssysteem aanroept. Deze regelschending kan nog steeds worden weergegeven, zelfs nadat u de aanbevolen constructorpatronen hebt gevolgd die in dit onderwerp zijn beschreven. Mogelijk moet u deze regel uitschakelen of onderdrukken in de configuratie van uw FXCop-regelset.

De meeste problemen zijn afkomstig van het afleiden van klassen, niet van het gebruik van bestaande klassen

De problemen die door deze regel worden gerapporteerd, treden op wanneer een klasse die u implementeert met virtuele methoden in het constructieproces, vervolgens wordt gebruikt als basis voor een afgeleide klasse. Als u uw klasse verzegelt of op een andere manier weet of afdwingt dat uw klasse niet wordt afgeleid, zijn de hier beschreven overwegingen en de problemen die de FXCop-regel motiveerden niet op u van toepassing. Als u echter klassen op zodanige wijze ontwerpt dat ze moeten worden gebruikt als basisklassen, bijvoorbeeld als u sjablonen maakt of een uitbreidbare besturingsbibliotheekset, moet u de hier aanbevolen patronen voor constructors volgen.

Standaardconstructors moeten alle waarden initialiseren die zijn aangevraagd door callbacks

Alle instantieleden die door uw klasse worden gebruikt, overschrijvingen of callbacks (de callbacks uit de lijst in de sectie Property System Virtuals) moeten worden geïnitialiseerd in de parameterloze constructor van uw klasse, zelfs als sommige van deze waarden worden gevuld met 'echte' waarden via parameters van de niet-parameterloze constructoren.

De volgende voorbeeldcode (en volgende voorbeelden) is een pseudo-C#-voorbeeld dat deze regel schendt en het probleem uitlegt:

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;  
}  

Wanneer toepassingscode new MyClass(objectvalue)aanroept, roept dit de parameterloze constructor en de basisklasseconstructors aan. Vervolgens stelt het Property1 = object1in, dat de virtuele methode OnPropertyChanged aanroept op de eigenaar van MyClassDependencyObject. De overschrijving verwijst naar _myList, welke nog niet is geïnitialiseerd.

Een manier om deze problemen te voorkomen, is ervoor te zorgen dat callbacks alleen andere afhankelijkheidseigenschappen gebruiken en dat elke dergelijke afhankelijkheidseigenschap een vastgestelde standaardwaarde heeft als onderdeel van de geregistreerde metagegevens.

Veilige constructorpatronen

Volg deze patronen om de risico's van onvolledige initialisatie te voorkomen als uw klasse wordt gebruikt als basisklasse:

Parameterloze constructors die basisinitialisatie aanroepen

Implementeer deze constructors die de standaardbasisinstelling aanroepen:

public MyClass : SomeBaseClass {  
    public MyClass() : base() {  
        // ALL class initialization, including initial defaults for
        // possible values that other ctors specify or that callbacks need.  
    }  
}  

Niet-standaard (gemaks)constructors die niet overeenkomen met basishandtekeningen

Als deze constructors de parameters gebruiken om afhankelijkheidseigenschappen in te stellen in de initialisatie, roept u eerst uw eigen klasseparameterloze constructor aan voor initialisatie en gebruikt u vervolgens de parameters om afhankelijkheidseigenschappen in te stellen. Dit kunnen afhankelijkheidseigenschappen zijn die zijn gedefinieerd door uw klasse of afhankelijkheidseigenschappen die zijn overgenomen van basisklassen, maar in beide gevallen wordt het volgende patroon gebruikt:

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;  
    }  
}  

Niet-standaard (gemaks)constructors die overeenkomen met basishandtekeningen

In plaats van de basisconstructor met dezelfde parameterisatie aan te roepen, roept u opnieuw de parameterloze constructor van uw eigen klasse aan. Roep de basis-initialisatiefunctie niet aan; in plaats daarvan moet u this()aanroepen. Reproduceer vervolgens het oorspronkelijke constructorgedrag met behulp van de doorgegeven parameters als waarden voor het instellen van de relevante eigenschappen. Gebruik de oorspronkelijke basisconstructordocumentatie voor hulp bij het bepalen van de eigenschappen die de specifieke parameters zijn bedoeld om in te stellen:

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;  
    }  
}  

Moet overeenkomen met alle handtekeningen

Voor gevallen waarin het basistype meerdere handtekeningen heeft, moet u bewust alle mogelijke handtekeningen vergelijken met een eigen constructor-implementatie die gebruikmaakt van het aanbevolen patroon voor het aanroepen van de klasseparameterloze constructor voordat u verdere eigenschappen instelt.

Afhankelijkheidseigenschappen instellen met SetValue

Dezelfde patronen zijn van toepassing als u een eigenschap instelt die geen wrapper heeft voor het gemak van de eigenschapsinstelling en waarden instelt met SetValue. Uw aanroepen naar SetValue die door de constructorparameters worden doorgegeven, moeten ook de parameterloze constructor van de klasse aanroepen voor initialisatie.

Zie ook