C# Language
Aannemers en finalisten
Zoeken…
Invoering
Constructors zijn methoden in een klasse die worden opgeroepen wanneer een instantie van die klasse wordt gemaakt. Hun belangrijkste verantwoordelijkheid is om het nieuwe object in een bruikbare en consistente staat te laten.
Destructors / Finalizers zijn methoden in een klasse die worden opgeroepen wanneer een exemplaar daarvan wordt vernietigd. In C # worden ze zelden expliciet geschreven / gebruikt.
Opmerkingen
C # heeft eigenlijk geen destructors, maar veeleer Finalizers die de syntaxis van de destructor in C ++ -stijl gebruiken. Als u een destructor Object.Finalize()
methode Object.Finalize()
die niet rechtstreeks kan worden aangeroepen.
In tegenstelling tot andere talen met een vergelijkbare syntaxis, worden deze methoden niet aangeroepen wanneer objecten buiten bereik vallen, maar worden ze aangeroepen wanneer de Garbage Collector wordt uitgevoerd, wat zich onder bepaalde omstandigheden voordoet. Als zodanig wordt niet gegarandeerd dat ze in een bepaalde volgorde worden uitgevoerd.
Finalizers moet verantwoordelijk zijn voor het opruimen van unmanaged middelen alleen zijn (pointers verworven via de maarschalk klasse, ontvangen via p / Invoke (system calls) of ruwe pointers gebruikt worden binnen onveilig blokken). Om beheerde bronnen op te schonen, raadpleegt u IDisposable, het Dispose-patroon en de using
.
(Verder lezen: wanneer moet ik een destructor maken? )
Standaard Constructor
Wanneer een type wordt gedefinieerd zonder een constructor:
public class Animal
{
}
dan genereert de compiler een standaardconstructor equivalent aan het volgende:
public class Animal
{
public Animal() {}
}
De definitie van een constructor voor het type onderdrukt de standaardconstructorgeneratie. Als het type als volgt is gedefinieerd:
public class Animal
{
public Animal(string name) {}
}
dan kon een Animal
alleen worden gemaakt door de aangegeven constructor te bellen.
// This is valid
var myAnimal = new Animal("Fluffy");
// This fails to compile
var unnamedAnimal = new Animal();
Voor het tweede voorbeeld geeft de compiler een foutmelding weer:
'Animal' bevat geen constructor waarvoor 0 argumenten nodig zijn
Als u wilt dat een klasse zowel een parameterloze constructor heeft als een constructor die een parameter gebruikt, kunt u dit doen door beide constructors expliciet te implementeren.
public class Animal
{
public Animal() {} //Equivalent to a default constructor.
public Animal(string name) {}
}
De compiler kan geen standaardconstructor genereren als de klasse een andere klasse uitbreidt die geen parameterloze constructor heeft. Als we bijvoorbeeld een klasse Creature
:
public class Creature
{
public Creature(Genus genus) {}
}
dan zou Animal
gedefinieerd als class Animal : Creature {}
niet compileren.
Een constructor van een andere constructor aanroepen
public class Animal
{
public string Name { get; set; }
public Animal() : this("Dog")
{
}
public Animal(string name)
{
Name = name;
}
}
var dog = new Animal(); // dog.Name will be set to "Dog" by default.
var cat = new Animal("Cat"); // cat.Name is "Cat", the empty constructor is not called.
Statische constructor
Een statische constructor wordt de eerste keer genoemd dat een lid van een type wordt geïnitialiseerd, een statisch klasse-lid wordt aangeroepen of een statische methode. De statische constructor is draadveilig. Een statische constructor wordt vaak gebruikt om:
- Initialiseer de statische status, dat is de status die wordt gedeeld door verschillende instanties van dezelfde klasse.
- Maak een singleton
Voorbeeld:
class Animal
{
// * A static constructor is executed only once,
// when a class is first accessed.
// * A static constructor cannot have any access modifiers
// * A static constructor cannot have any parameters
static Animal()
{
Console.WriteLine("Animal initialized");
}
// Instance constructor, this is executed every time the class is created
public Animal()
{
Console.WriteLine("Animal created");
}
public static void Yawn()
{
Console.WriteLine("Yawn!");
}
}
var turtle = new Animal();
var giraffe = new Animal();
Output:
Dier geïnitialiseerd
Dier gemaakt
Dier gemaakt
Als de eerste aanroep een statische methode is, wordt de statische constructor aangeroepen zonder de exemplaarconstructor. Dit is OK, omdat de statische methode sowieso geen toegang heeft tot de instantiestatus.
Animal.Yawn();
Dit levert het volgende op:
Dier geïnitialiseerd
Geeuw!
Zie ook Uitzonderingen in statische constructors en generieke statische constructors .
Voorbeeld van Singleton:
public class SessionManager
{
public static SessionManager Instance;
static SessionManager()
{
Instance = new SessionManager();
}
}
De constructeur van de basisklasse aanroepen
Een constructor van een basisklasse wordt aangeroepen voordat een constructor van een afgeleide klasse wordt uitgevoerd. Als Mammal
bijvoorbeeld Animal
uitbreidt, wordt de code in de constructor van Animal
eerst aangeroepen bij het maken van een instantie van een Mammal
.
Als een afgeleide klasse niet expliciet opgeeft welke constructor van de basisklasse moet worden aangeroepen, neemt de compiler de parameterloze constructor aan.
public class Animal
{
public Animal() { Console.WriteLine("An unknown animal gets born."); }
public Animal(string name) { Console.WriteLine(name + " gets born"); }
}
public class Mammal : Animal
{
public Mammal(string name)
{
Console.WriteLine(name + " is a mammal.");
}
}
In dit geval wordt het afdrukken van een Mammal
door een new Mammal("George the Cat")
bellen new Mammal("George the Cat")
afgedrukt
Een onbekend dier wordt geboren.
George de kat is een zoogdier.
Het aanroepen van een andere constructor van de basisklasse wordt gedaan door : base(args)
tussen de handtekening van de constructor en het hoofdgedeelte:
public class Mammal : Animal
{
public Mammal(string name) : base(name)
{
Console.WriteLine(name + " is a mammal.");
}
}
Door new Mammal("George the Cat")
bellen new Mammal("George the Cat")
wordt nu afgedrukt:
George de kat wordt geboren.
George de kat is een zoogdier.
Finalizers voor afgeleide klassen
Wanneer een objectgrafiek is voltooid, is de volgorde omgekeerd van de constructie. Het supertype wordt bijvoorbeeld voltooid vóór het basistype, zoals de volgende code laat zien:
class TheBaseClass
{
~TheBaseClass()
{
Console.WriteLine("Base class finalized!");
}
}
class TheDerivedClass : TheBaseClass
{
~TheDerivedClass()
{
Console.WriteLine("Derived class finalized!");
}
}
//Don't assign to a variable
//to make the object unreachable
new TheDerivedClass();
//Just to make the example work;
//this is otherwise NOT recommended!
GC.Collect();
//Derived class finalized!
//Base class finalized!
Singleton constructeur patroon
public class SingletonClass
{
public static SingletonClass Instance { get; } = new SingletonClass();
private SingletonClass()
{
// Put custom constructor code here
}
}
Omdat de constructor privé is, kunnen er geen nieuwe exemplaren van SingletonClass
worden gemaakt door code te consumeren. De enige manier om toegang te krijgen tot de enkele instantie van SingletonClass
is door de statische eigenschap SingletonClass.Instance
.
De eigenschap Instance
wordt toegewezen door een statische constructor die door de C # compiler wordt gegenereerd. De .NET-runtime garandeert dat de statische constructor maximaal één keer wordt uitgevoerd en wordt uitgevoerd voordat Instance
eerst wordt gelezen. Daarom worden alle problemen met synchronisatie en initialisatie uitgevoerd door de runtime.
Merk op dat als de statische constructeur faalt, de Singleton
klasse permanent onbruikbaar wordt voor de levensduur van de AppDomain.
Het is ook niet gegarandeerd dat de statische constructor wordt uitgevoerd op het moment van de eerste toegang tot Instance
. Het zal eerder op een gegeven moment worden uitgevoerd. Dit maakt het tijdstip waarop initialisatie plaatsvindt niet-deterministisch. In praktische gevallen roept de JIT vaak de statische constructor op tijdens het compileren (niet uitvoeren) van een methode die verwijst naar Instance
. Dit is een prestatieoptimalisatie.
Zie de Singleton Implementations- pagina voor andere manieren om het singleton-patroon te implementeren.
Een statische constructor dwingen om te worden aangeroepen
Hoewel statische constructors altijd worden aangeroepen vóór het eerste gebruik van een type, is het soms handig om ze te kunnen dwingen om te worden aangeroepen en de klasse RuntimeHelpers
biedt hier een hulpmiddel voor:
using System.Runtime.CompilerServices;
// ...
RuntimeHelpers.RunClassConstructor(typeof(Foo).TypeHandle);
Opmerking : Alle statische initialisatie (bijvoorbeeld initialisatie van velden) wordt uitgevoerd, niet alleen de constructor zelf.
Potentieel gebruik : initialisatie forceren tijdens het splash-scherm in een UI-toepassing of ervoor zorgen dat een statische constructor niet faalt in een unit-test.
Virtuele methoden aanroepen in constructor
In tegenstelling tot C ++ in C # kun je een virtuele methode uit de klassenbouwer aanroepen (OK, dat kun je ook in C ++, maar gedrag is in eerste instantie verrassend). Bijvoorbeeld:
abstract class Base
{
protected Base()
{
_obj = CreateAnother();
}
protected virtual AnotherBase CreateAnother()
{
return new AnotherBase();
}
private readonly AnotherBase _obj;
}
sealed class Derived : Base
{
public Derived() { }
protected override AnotherBase CreateAnother()
{
return new AnotherDerived();
}
}
var test = new Derived();
// test._obj is AnotherDerived
Als je van een C ++ -achtergrond komt, is dit verrassend, de constructeur van de basisklasse ziet al een tabel met afgeleide virtuele klassenmethoden!
Let op : afgeleide klasse is mogelijk nog niet volledig geïnitialiseerd (de constructor wordt uitgevoerd na de basisklasse-constructor) en deze techniek is gevaarlijk (er is ook een StyleCop-waarschuwing hiervoor). Meestal wordt dit als een slechte gewoonte beschouwd.
Generieke statische constructeurs
Als het type waarop de statische constructor wordt aangegeven generiek is, wordt de statische constructor eenmaal aangeroepen voor elke unieke combinatie van generieke argumenten.
class Animal<T>
{
static Animal()
{
Console.WriteLine(typeof(T).FullName);
}
public static void Yawn() { }
}
Animal<Object>.Yawn();
Animal<String>.Yawn();
Dit levert het volgende op:
System.Object
System.String
Zie ook Hoe werken statische constructors voor generieke typen?
Uitzonderingen in statische constructors
Als een statische constructor een uitzondering genereert, wordt deze nooit opnieuw geprobeerd. Het type is onbruikbaar voor de levensduur van de AppDomain. Elk verder gebruik van het type zal een TypeInitializationException
rondom de oorspronkelijke uitzondering.
public class Animal
{
static Animal()
{
Console.WriteLine("Static ctor");
throw new Exception();
}
public static void Yawn() {}
}
try
{
Animal.Yawn();
}
catch (Exception e)
{
Console.WriteLine(e.ToString());
}
try
{
Animal.Yawn();
}
catch (Exception e)
{
Console.WriteLine(e.ToString());
}
Dit levert het volgende op:
Statische ctor
System.TypeInitializationException: het type initializer voor 'Animal' heeft een uitzondering gegenereerd. ---> System.Exception: uitzondering van het type 'System.Exception' is opgetreden.
[...]
System.TypeInitializationException: het type initializer voor 'Animal' heeft een uitzondering gegenereerd. ---> System.Exception: uitzondering van het type 'System.Exception' is opgetreden.
waar u kunt zien dat de feitelijke constructor slechts eenmaal wordt uitgevoerd en de uitzondering opnieuw wordt gebruikt.
Initialisatie van constructeur en onroerend goed
Zal de toewijzing van de eigenschapswaarde worden uitgevoerd voor of na de constructor van de klasse?
public class TestClass
{
public int TestProperty { get; set; } = 2;
public TestClass()
{
if (TestProperty == 1)
{
Console.WriteLine("Shall this be executed?");
}
if (TestProperty == 2)
{
Console.WriteLine("Or shall this be executed");
}
}
}
var testInstance = new TestClass() { TestProperty = 1 };
TestProperty
in het bovenstaande voorbeeld de TestProperty
waarde 1
in de constructor van de klasse of achter de constructor van de klasse?
Waardeswaarden toewijzen bij het maken van een instantie als volgt:
var testInstance = new TestClass() {TestProperty = 1};
Wordt uitgevoerd nadat de constructor is uitgevoerd. Het initialiseren van de eigenschapswaarde in de eigenschap 'class' in C # 6.0 gaat echter als volgt:
public class TestClass
{
public int TestProperty { get; set; } = 2;
public TestClass()
{
}
}
wordt gedaan voordat de constructor wordt uitgevoerd.
De twee bovenstaande concepten combineren in een enkel voorbeeld:
public class TestClass
{
public int TestProperty { get; set; } = 2;
public TestClass()
{
if (TestProperty == 1)
{
Console.WriteLine("Shall this be executed?");
}
if (TestProperty == 2)
{
Console.WriteLine("Or shall this be executed");
}
}
}
static void Main(string[] args)
{
var testInstance = new TestClass() { TestProperty = 1 };
Console.WriteLine(testInstance.TestProperty); //resulting in 1
}
Eindresultaat:
"Or shall this be executed"
"1"
Uitleg:
De TestProperty
waarde wordt eerst toegewezen als 2
, wordt de TestClass
constructor wordt uitgevoerd, wat resulteert in het afdrukken van
"Or shall this be executed"
En dan wordt de TestProperty
toegewezen als 1
vanwege new TestClass() { TestProperty = 1 }
, waardoor de uiteindelijke waarde voor de TestProperty
afgedrukt door Console.WriteLine(testInstance.TestProperty)
wordt
"1"