Suche…


Einführung

Konstruktoren sind Methoden in einer Klasse, die aufgerufen werden, wenn eine Instanz dieser Klasse erstellt wird. Ihre Hauptaufgabe besteht darin, das neue Objekt in einem nützlichen und konsistenten Zustand zu belassen.

Destruktoren / Finalizer sind Methoden in einer Klasse, die aufgerufen werden, wenn eine Instanz davon zerstört wird. In C # werden sie selten explizit geschrieben / verwendet.

Bemerkungen

C # hat eigentlich keine Destruktoren, sondern Finalizer, die die Destruktorsyntax im C ++ - Stil verwenden. Durch die Angabe eines Destruktors wird die Object.Finalize() Methode überschrieben, die nicht direkt aufgerufen werden kann.

Im Gegensatz zu anderen Sprachen mit ähnlicher Syntax werden diese Methoden nicht aufgerufen, wenn Objekte den Gültigkeitsbereich verlassen, sondern beim Ausführen des Garbage Collector, was unter bestimmten Bedingungen auftritt. Daher können sie nicht in einer bestimmten Reihenfolge ausgeführt werden.

Finalizers sollte nur zur Reinigung von nicht verwalteten Ressourcen verantwortlich sein (Zeiger über die Marshal - Klasse erworben, erhalten durch p / Invoke (Systemaufrufe) oder rohe Zeiger innerhalb unsichere Blöcke verwendet). Um bereinigte Ressourcen zu bereinigen, überprüfen Sie bitte IDisposable, das Dispose-Muster und die using Anweisung.

(Weiterführende Literatur: Wann sollte ich einen Destruktor erstellen? )

Standardkonstruktor

Wenn ein Typ ohne Konstruktor definiert wird:

public class Animal
{
}

dann generiert der Compiler einen Standardkonstruktor, der dem folgenden entspricht:

public class Animal
{
    public Animal() {}
}

Die Definition eines beliebigen Konstruktors für den Typ unterdrückt die Erstellung des Standardkonstruktors. Wenn der Typ wie folgt definiert wurde:

public class Animal
{
    public Animal(string name) {}
}

dann kann ein Animal nur durch Aufrufen des deklarierten Konstruktors erstellt werden.

// This is valid
var myAnimal = new Animal("Fluffy");
// This fails to compile
var unnamedAnimal = new Animal();

Für das zweite Beispiel zeigt der Compiler eine Fehlermeldung an:

'Animal' enthält keinen Konstruktor, der 0 Argumente akzeptiert

Wenn eine Klasse sowohl einen parameterlosen Konstruktor als auch einen Konstruktor haben soll, der einen Parameter übernimmt, können Sie dies tun, indem Sie beide Konstruktoren explizit implementieren.

public class Animal
{
    
    public Animal() {} //Equivalent to a default constructor.
    public Animal(string name) {}
}

Der Compiler kann keinen Standardkonstruktor generieren, wenn die Klasse eine andere Klasse erweitert, die keinen parameterlosen Konstruktor besitzt. Wenn wir zum Beispiel eine Klasse Creature :

public class Creature
{
    public Creature(Genus genus) {}
}

Dann wird Animal als class Animal : Creature {} nicht kompiliert.

Aufruf eines Konstruktors aus einem anderen Konstruktor

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.

Statischer Konstruktor

Ein statischer Konstruktor wird aufgerufen, wenn zum ersten Mal ein Member eines Typs initialisiert wird, ein statischer Klassenmitglied aufgerufen wird oder eine statische Methode. Der statische Konstruktor ist threadsicher. Ein statischer Konstruktor wird normalerweise verwendet, um:

  • Initialisieren Sie den statischen Status, dh den Status, der von verschiedenen Instanzen derselben Klasse gemeinsam genutzt wird.
  • Erstellen Sie ein Singleton

Beispiel:

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();

Ausgabe:

Tier initialisiert
Tier erstellt
Tier erstellt

Demo anzeigen

Wenn der erste Aufruf eine statische Methode ist, wird der statische Konstruktor ohne den Instanzkonstruktor aufgerufen. Dies ist in Ordnung, da die statische Methode sowieso nicht auf den Instanzstatus zugreifen kann.

Animal.Yawn();

Dies wird ausgegeben:

Tier initialisiert
Gähnen!

Siehe auch Ausnahmen in statischen Konstruktoren und generischen statischen Konstruktoren .

Singleton-Beispiel:

public class SessionManager
{
    public static SessionManager Instance;

    static SessionManager()
    {
        Instance = new SessionManager();
    }
}

Aufruf des Basisklassenkonstruktors

Ein Konstruktor einer Basisklasse wird aufgerufen, bevor ein Konstruktor einer abgeleiteten Klasse ausgeführt wird. Wenn beispielsweise Mammal Animal , wird der im Konstruktor von Animal enthaltene Code beim Erstellen einer Instanz eines Mammal zuerst aufgerufen.

Wenn eine abgeleitete Klasse nicht explizit angibt, welcher Konstruktor der Basisklasse aufgerufen werden soll, nimmt der Compiler den parameterlosen Konstruktor an.

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 diesem Fall wird das Instantiieren eines Mammal durch Aufrufen des new Mammal("George the Cat") gedruckt

Ein unbekanntes Tier wird geboren.
George the Cat ist ein Säugetier.

Demo anzeigen

Das Aufrufen eines anderen Konstruktors der Basisklasse erfolgt durch Platzieren von : base(args) zwischen der Signatur des Konstruktors und seinem Rumpf:

public class Mammal : Animal
{
    public Mammal(string name) : base(name)
    {
        Console.WriteLine(name + " is a mammal.");
    }
}

Das Aufrufen eines new Mammal("George the Cat") wird jetzt gedruckt:

George the Cat wird geboren.
George the Cat ist ein Säugetier.

Demo anzeigen

Finalisierer für abgeleitete Klassen

Wenn ein Objektdiagramm abgeschlossen ist, ist die Reihenfolge in umgekehrter Reihenfolge der Konstruktion. Der Supertyp wird beispielsweise vor dem Basistyp abgeschlossen, wie der folgende Code veranschaulicht:

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-Konstruktormuster

public class SingletonClass
{
    public static SingletonClass Instance { get; } = new SingletonClass();

    private SingletonClass()
    {
        // Put custom constructor code here
    }    
}

Da der Konstruktor privat ist, können keine neuen Instanzen von SingletonClass erstellt werden, indem Code verwendet wird. Die einzige Möglichkeit, auf die einzelne Instanz von SingletonClass zuzugreifen, ist die statische Eigenschaft SingletonClass.Instance .

Die Instance wird von einem statischen Konstruktor zugewiesen, den der C # -Compiler generiert. Die .NET-Laufzeitumgebung gewährleistet, dass der statische Konstruktor höchstens einmal ausgeführt wird, bevor die Instance zuerst gelesen wird. Daher werden alle Synchronisierungs- und Initialisierungsprobleme von der Laufzeitumgebung ausgeführt.

Beachten Sie, dass die Singleton Klasse bei einem Ausfall des statischen Konstruktors für die gesamte Lebensdauer der AppDomain dauerhaft unbrauchbar wird.

Es kann auch nicht garantiert werden, dass der statische Konstruktor beim ersten Zugriff von Instance . Es wird vielmehr irgendwann davor laufen. Dies macht den Zeitpunkt, zu dem die Initialisierung stattfindet, nicht deterministisch. In praktischen Fällen ruft das JIT häufig den statischen Konstruktor während der Kompilierung (nicht der Ausführung) einer auf die Instance referenzierenden Methode auf. Dies ist eine Leistungsoptimierung.

Auf der Singleton-Implementierungsseite finden Sie weitere Möglichkeiten zum Implementieren des Singleton-Musters.

Erzwingen des Aufrufs eines statischen Konstruktors

Während statische Konstruktoren immer vor der ersten Verwendung eines Typs aufgerufen werden, kann es manchmal nützlich sein, sie zum Aufruf zu zwingen, und die RuntimeHelpers Klasse stellt einen Helfer dafür bereit:

using System.Runtime.CompilerServices;    
// ...
RuntimeHelpers.RunClassConstructor(typeof(Foo).TypeHandle);

Anmerkung : Es wird die gesamte statische Initialisierung (beispielsweise Feldinitialisierer) ausgeführt, nicht nur der Konstruktor selbst.

Mögliche Verwendungen : Erzwingen der Initialisierung während des Begrüßungsbildschirms in einer UI-Anwendung oder Sicherstellen, dass ein statischer Konstruktor bei einem Komponententest nicht fehlschlägt.

Virtuelle Methoden im Konstruktor aufrufen

Im Gegensatz zu C ++ in C # können Sie eine virtuelle Methode vom Klassenkonstruktor aus aufrufen (OK, Sie können dies auch in C ++ tun, aber das Verhalten ist zunächst überraschend). Zum Beispiel:

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

Wenn Sie aus einem C ++ - Hintergrund stammen, ist dies überraschend. Der Konstruktor der Basisklasse sieht bereits eine virtuelle Methodentabelle der abgeleiteten Klasse!

Seien Sie vorsichtig : Die abgeleitete Klasse wurde möglicherweise noch nicht vollständig initialisiert (ihr Konstruktor wird nach dem Konstruktor der Basisklasse ausgeführt), und diese Technik ist gefährlich (es gibt auch eine StyleCop-Warnung). Normalerweise wird dies als schlechte Praxis angesehen.

Generische statische Konstruktoren

Wenn der Typ, für den der statische Konstruktor deklariert ist, generisch ist, wird der statische Konstruktor einmal für jede eindeutige Kombination generischer Argumente aufgerufen.

class Animal<T>
{
    static Animal()
    {
        Console.WriteLine(typeof(T).FullName);
    }

    public static void Yawn() { }
}

Animal<Object>.Yawn();
Animal<String>.Yawn();

Dies wird ausgegeben:

System.Object
System.String

Siehe auch Wie funktionieren statische Konstruktoren für generische Typen?

Ausnahmen bei statischen Konstruktoren

Wenn ein statischer Konstruktor eine Ausnahme auslöst, wird er nie wiederholt. Der Typ kann für die Lebensdauer der AppDomain nicht verwendet werden. Bei weiteren Verwendungen des Typs wird eine TypeInitializationException die die ursprüngliche Ausnahme TypeInitializationException .

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

Dies wird ausgegeben:

Statischer ctor

System.TypeInitializationException: Der Typinitialisierer für 'Animal' hat eine Ausnahme ausgelöst. ---> System.Exception: Ausnahme des Typs 'System.Exception' wurde ausgelöst.

[...]

System.TypeInitializationException: Der Typinitialisierer für 'Animal' hat eine Ausnahme ausgelöst. ---> System.Exception: Ausnahme des Typs 'System.Exception' wurde ausgelöst.

Dort sehen Sie, dass der eigentliche Konstruktor nur einmal ausgeführt wird und die Ausnahme erneut verwendet wird.

Konstruktor- und Eigenschaftsinitialisierung

Soll die Eigenschaftswertzuweisung vor oder nach dem Konstruktor der Klasse ausgeführt werden?

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 der TestProperty Wert im TestProperty Beispiel 1 im Konstruktor der Klasse oder nach dem Klassenkonstruktor sein?


Zuweisen von Eigenschaftswerten bei der Instanzerstellung wie folgt:

var testInstance = new TestClass() {TestProperty = 1};

Wird ausgeführt, nachdem der Konstruktor ausgeführt wurde. Initialisieren Sie den Eigenschaftswert in der Klasseneigenschaft in C # 6.0 jedoch wie folgt:

public class TestClass 
{
    public int TestProperty { get; set; } = 2;

    public TestClass() 
    {
    }
}

wird ausgeführt, bevor der Konstruktor ausgeführt wird.


Die beiden obigen Konzepte in einem einzigen Beispiel kombinieren:

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
}

Endergebnis:

"Or shall this be executed"
"1"

Erläuterung:

Der TestProperty Wert wird zuerst als 2 zugewiesen. Anschließend wird der TestClass Konstruktor ausgeführt, was zum Ausdruck von führt

"Or shall this be executed"

Und dann wird der TestProperty wird als zugeordnet werden 1 aufgrund new TestClass() { TestProperty = 1 } , für die die endgültige Wert machen TestProperty gedruckt von Console.WriteLine(testInstance.TestProperty) zu sein ,

"1"


Modified text is an extract of the original Stack Overflow Documentation
Lizenziert unter CC BY-SA 3.0
Nicht angeschlossen an Stack Overflow