Implementieren privater, statischer und geschachtelter Klassen

Abgeschlossen

Instanzkonstruktoren werden verwendet, um den Code anzugeben, der beim Erstellen einer neuen Instanz einer Klasse mit dem new-Operator ausgeführt wird. Wenn Instanzkonstruktoren den public Zugriffsmodifizierer verwenden, kann auf das resultierende Objekt von jedem anderen Code in Ihrer Anwendung zugegriffen werden. Dieser Typ des Zugriffs auf ein Objekt und seine Member ist häufig wünschenswert, aber es gibt Zeiten, in denen Sie den Zugriff auf eine Klasse oder auf die Member einer Klasse einschränken möchten.

Wenn Sie die Belichtung einer Klasse ausblenden oder reduzieren müssen, können Sie Zugriffsmodifizierer verwenden, um die Sichtbarkeit der Klasse und deren Member zu steuern.

Zugriffsmodifizierer

Alle Typen und Typmember verfügen über eine Barrierefreiheitsstufe, die steuert, ob sie über anderen Code in Ihrer kompilierten Anwendung (oder anderen Assemblys) zugänglich sind.

Klassen, die direkt in einem Namespace deklariert sind, können über public, internal oder file Zugriff verfügen. Klassen werden standardmäßig internal Zugriff zugewiesen, wenn kein Zugriffsmodifizierer angegeben wird.

Verwenden Sie die folgenden Zugriffsmodifizierer, um die Barrierefreiheit eines Typs oder Elements anzugeben, wenn Sie ihn deklarieren:

  • public: Code in jeder Assembly kann auf diesen Typ oder dieses Element zugreifen. Die Barrierefreiheitsebene des enthaltenden Typs steuert die Barrierefreiheitsebene öffentlicher Member des Typs.
  • private: Nur code, der in demselben class oder struct deklariert ist, kann auf dieses Mitglied zugreifen.
  • protected: Nur Code in demselben class oder in einem abgeleiteten class kann auf diesen Typ oder dieses Element zugreifen.
  • internal: Nur Code in derselben Assembly kann auf diesen Typ oder dieses Element zugreifen.
  • protected internal: Nur Code in derselben Assembly oder in einer abgeleiteten Klasse in einer anderen Assembly kann auf diesen Typ oder Member zugreifen.
  • private protected: Nur Code in derselben Assembly und in derselben Klasse oder einer abgeleiteten Klasse kann auf den Typ oder das Element zugreifen.
  • file: Nur Code in derselben Datei kann auf den Typ oder das Element zugreifen.

Der record Modifizierer für einen Typ bewirkt, dass der Compiler zusätzliche Member synthetisiert. Der record Modifizierer wirkt sich nicht auf die Standardbarrierefreiheit für eine record class oder eine record structaus.

Private Klassenkonstruktoren

Ein privater Konstruktor ist ein spezieller Instanzkonstruktor. Private Konstruktoren werden häufig in Klassen verwendet, die nur statische Member enthalten. Wenn eine Klasse einen oder mehrere private Konstruktoren und keine öffentlichen Konstruktoren enthält, können andere Klassen (mit Ausnahme geschachtelter Klassen) keine Instanzen der Klasse erstellen. Zum Beispiel:


class NLog
{
    // Private Constructor:
    private NLog() { }

    public static double e = Math.E;  //2.71828...
}

Die Deklaration des leeren Konstruktors verhindert die automatische Generierung eines parameterlosen Konstruktors. Wenn Sie keinen Zugriffsmodifizierer für einen Konstruktor angeben, wird standardmäßig private. Der private Modifizierer sollte jedoch verwendet werden, um zu verdeutlichen, dass die Klasse nicht instanziiert werden kann.

Private Konstruktoren werden verwendet, um das Erstellen von Instanzen einer Klasse zu verhindern, wenn keine Instanzfelder oder Methoden vorhanden sind, z. B. die Math-Klasse in der .NET-Bibliothek oder wenn eine Methode aufgerufen wird, um eine Instanz einer Klasse abzurufen. Wenn alle Methoden in der Klasse statisch sind, sollten Sie die vollständige Klasse statisch machen.

Das folgende Beispiel zeigt eine Klasse mit einem privaten Konstruktor.


public class Counter
{
    private Counter() { }

    public static int currentCount;

    public static int IncrementCount()
    {
        return ++currentCount;
    }
}

class TestCounter
{
    static void Main()
    {
        // If you uncomment the following statement, it generates
        // an error because the constructor is inaccessible:
        // Counter aCounter = new Counter();   // Error

        Counter.currentCount = 100;
        Counter.IncrementCount();
        Console.WriteLine("New count: {0}", Counter.currentCount);

        // Keep the console window open in debug mode.
        Console.WriteLine("Press any key to exit.");
        Console.ReadKey();
    }
}
// Output: New count: 101

Wenn Sie die Auskommentierung der folgenden Anweisung aus dem Beispiel aufheben, generiert der Compiler einen Buildfehler. Der Compiler versteht, dass auf einen privaten Konstruktor nicht zugegriffen werden kann.


// Counter aCounter = new Counter();   // Error

Statische Klassen

Eine statische Klasse ist im Wesentlichen identisch mit einer nicht statischen Klasse, wobei ein wichtiger Unterschied besteht: Eine statische Klasse kann nicht instanziiert werden. Mit anderen Worten, Sie können den new-Operator nicht verwenden, um eine Variable aus einer statischen Klasse zu erstellen. Obwohl Sie keine Instanz der Klasse erstellen können, können Sie auf die Member einer statischen Klasse zugreifen, indem Sie auf den Klassennamen verweisen. Wenn Sie beispielsweise über eine statische Klasse mit dem Namen UtilityClass verfügen, die eine öffentliche statische Methode namens MethodAenthält, können Sie die Methode mithilfe einer Kombination der Klassen- und Methodennamen aufrufen. Diese Syntax wird im folgenden Beispiel gezeigt:


UtilityClass.MethodA();

Eine statische Klasse kann als Container für Methoden verwendet werden, die mit Eingabeparametern arbeiten und keine internen Instanzfelder get oder set müssen.

Beispielsweise enthält die statische System.Math-Klasse in der .NET-Klassenbibliothek Methoden, die mathematische Vorgänge ausführen. Diese Methoden funktionieren ohne Anforderung, Daten zu speichern oder abzurufen, die für eine bestimmte Instanz der Math Klasse eindeutig sind. Sie rufen die Member der Klasse auf, indem Sie den Klassennamen und den Methodennamen angeben, wie im folgenden Beispiel gezeigt:


double dub = -3.14;
Console.WriteLine(Math.Abs(dub));
Console.WriteLine(Math.Floor(dub));
Console.WriteLine(Math.Round(Math.Abs(dub)));

// Output:
// 3.14
// -4
// 3

Wie bei allen Klassentypen lädt die .NET-Laufzeit die Typinformationen für eine statische Klasse, wenn das Programm, das auf die Klasse verweist, geladen wird. Das Programm kann nicht genau angeben, wann die Klasse geladen wird. Es ist jedoch garantiert, dass die Felder geladen und der statische Konstruktor initialisiert wird, bevor die Klasse zum ersten Mal in Ihrem Programm referenziert wird. Ein statischer Konstruktor wird nur einmal aufgerufen, und eine statische Klasse bleibt für die Lebensdauer der Anwendungsdomäne, in der sich Ihr Programm befindet, im Arbeitsspeicher.

Die folgende Liste enthält die Hauptmerkmale einer statischen Klasse:

  • Enthält nur statische Elemente.
  • Kann nicht instanziiert werden.
  • Ist versiegelt.
  • Instanzkonstruktoren können nicht enthalten.

Das Erstellen einer statischen Klasse entspricht daher im Grunde dem Erstellen einer Klasse, die nur statische Member und einen privaten Konstruktor enthält. Ein privater Konstruktor verhindert, dass die Klasse instanziiert wird. Der Vorteil der Verwendung einer statischen Klasse besteht darin, dass der Compiler überprüfen kann, um sicherzustellen, dass keine Instanzmember versehentlich hinzugefügt werden. Der Compiler garantiert, dass Instanzen dieser Klasse nicht erstellt werden können.

Statische Klassen sind versiegelt und können daher nicht geerbt werden. Sie können nicht von einer Klasse oder Schnittstelle erben, außer Object. Statische Klassen können keinen Instanzkonstruktor enthalten. Sie können jedoch einen static-Konstruktor enthalten. Nicht statische Klassen sollten auch einen static-Konstruktor definieren, wenn die Klasse static Member enthält, die eine nicht triviale Initialisierung erfordern.

Hier ist ein Beispiel für eine statische Klasse, die zwei Methoden enthält, die Temperatur von Celsius in Fahrenheit und von Fahrenheit in Celsius umwandeln:


public static class TemperatureConverter
{
    public static double CelsiusToFahrenheit(string temperatureCelsius)
    {
        // Convert argument to double for calculations.
        double celsius = Double.Parse(temperatureCelsius);

        // Convert Celsius to Fahrenheit.
        double fahrenheit = (celsius * 9 / 5) + 32;

        return fahrenheit;
    }

    public static double FahrenheitToCelsius(string temperatureFahrenheit)
    {
        // Convert argument to double for calculations.
        double fahrenheit = Double.Parse(temperatureFahrenheit);

        // Convert Fahrenheit to Celsius.
        double celsius = (fahrenheit - 32) * 5 / 9;

        return celsius;
    }
}

class TestTemperatureConverter
{
    static void Main()
    {
        Console.WriteLine("Please select the convertor direction");
        Console.WriteLine("1. From Celsius to Fahrenheit.");
        Console.WriteLine("2. From Fahrenheit to Celsius.");
        Console.Write(":");

        string? selection = Console.ReadLine();
        double F, C = 0;

        switch (selection)
        {
            case "1":
                Console.Write("Please enter the Celsius temperature: ");
                F = TemperatureConverter.CelsiusToFahrenheit(Console.ReadLine() ?? "0");
                Console.WriteLine("Temperature in Fahrenheit: {0:F2}", F);
                break;

            case "2":
                Console.Write("Please enter the Fahrenheit temperature: ");
                C = TemperatureConverter.FahrenheitToCelsius(Console.ReadLine() ?? "0");
                Console.WriteLine("Temperature in Celsius: {0:F2}", C);
                break;

            default:
                Console.WriteLine("Please select a convertor.");
                break;
        }

        // Keep the console window open in debug mode.
        Console.WriteLine("Press any key to exit.");
        Console.ReadKey();
    }
}
/* Example Output:
    Please select the convertor direction
    1. From Celsius to Fahrenheit.
    2. From Fahrenheit to Celsius.
    :2
    Please enter the Fahrenheit temperature: 20
    Temperature in Celsius: -6.67
    Press any key to exit.
 */

Statische Member

Eine nicht statische Klasse kann statische Methoden, Felder, Eigenschaften oder Ereignisse enthalten. Das statische Element kann für eine Klasse aufgerufen werden, auch wenn keine Instanz der Klasse vorhanden ist. Auf das statische Element wird immer über den Klassennamen und nicht über den Namen der Instanz zugegriffen. Es ist nur eine Kopie eines statischen Elements vorhanden, unabhängig davon, wie viele Instanzen der Klasse erstellt werden. Statische Methoden und Eigenschaften können nicht auf nicht statische Felder und Ereignisse in ihrem enthaltenden Typ zugreifen, und sie können nicht auf eine Instanzvariable eines Objekts zugreifen, es sei denn, sie wird explizit in einem Methodenparameter übergeben.

Es ist typischer, eine nicht statische Klasse mit einigen statischen Membern zu deklarieren, als eine gesamte Klasse als statisch zu deklarieren. Zwei häufige Verwendungen statischer Felder sind die Zählung der Anzahl der Objekte, die instanziiert werden, oder um einen Wert zu speichern, der für alle Instanzen freigegeben werden muss.

Statische Methoden können überladen, aber nicht außer Kraft gesetzt werden, da sie zur Klasse gehören, und nicht zu einer Instanz der Klasse.

Obwohl ein Feld nicht als static constdeklariert werden kann, ist ein const Feld im Wesentlichen statisch im Verhalten. Sie gehört zum Typ, nicht zu Instanzen des Typs. Daher können auf const Felder mithilfe der gleichen ClassName.MemberName Schreibweise zugegriffen werden, die für statische Felder verwendet wird. Es ist keine Objektinstanz erforderlich.

C# unterstützt keine statischen lokalen Variablen (d. h. Variablen, die im Methodenbereich deklariert sind).

Sie deklarieren statische Klassenmember mithilfe des schlüsselworts static vor dem Rückgabetyp des Elements, wie im folgenden Beispiel gezeigt:


public class Automobile
{
    public static int NumberOfWheels = 4;

    public static int SizeOfGasTank
    {
        get
        {
            return 15;
        }
    }

    public static void Drive() { }

    public static event EventType? RunOutOfGas;

    // Other nonstatic fields and properties...
}

Statische Member werden initialisiert, bevor zum ersten Mal auf das statische Element zugegriffen wird und bevor der statische Konstruktor aufgerufen wird, falls vorhanden. Verwenden Sie für den Zugriff auf ein statisches Klassenelement den Namen der Klasse anstelle eines Variablennamens, um den Speicherort des Elements anzugeben, wie im folgenden Beispiel gezeigt:


Automobile.Drive();
int i = Automobile.NumberOfWheels;

Wenn Ihre Klasse statische Felder enthält, stellen Sie einen statischen Konstruktor bereit, der sie initialisiert, wenn die Klasse geladen wird.

Ein Aufruf einer statischen Methode generiert eine Aufrufanweisung in allgemeiner Zwischensprache (CIL), während ein Aufruf einer Instanzmethode eine callvirt Anweisung generiert, die auch auf NULL-Objektverweise überprüft. Die meiste Zeit ist der Leistungsunterschied zwischen den beiden jedoch nicht signifikant.

Geschachtelte Klassen

Ein in einer anderen Klasse definierter class Typ wird als geschachtelte Klasse bezeichnet. Zum Beispiel:


public class Container
{
    class Nested
    {
        Nested() { }
    }
}

Die Barrierefreiheit einer geschachtelten Klasse ist standardmäßig auf privatefestgelegt. Dies bedeutet, dass auf geschachtelte Klassen nur über ihre enthaltende Klasse zugegriffen werden kann. Im vorherigen Beispiel kann auf die Nested Klasse nicht auf externe Typen zugegriffen werden.

Sie können einen Zugriffsmodifizierer angeben, um die Barrierefreiheit eines geschachtelten Typs wie folgt zu definieren:

  • Geschachtelte Typen einer Klasse können public, protected, internal, protected internal, private oder private protected.

Beim Definieren einer protected, protected internal oder private protected geschachtelten Klasse in einer versiegelten Klasse wird jedoch die Compilerwarnung CS0628 generiert, "neues geschütztes Element, das in der versiegelten Klasse deklariert ist".

Vorsicht

Beachten Sie, dass ein geschachtelter Typ extern sichtbar ist, gegen die Codequalitätsregel CA1034 verstößt: "Geschachtelte Typen sollten nicht sichtbar sein".

Im folgenden Beispiel wird die Nested Klasse öffentlich:


public class Container
{
    public class Nested
    {
        Nested() { }
    }
}

Die geschachtelte oder innere Klasse kann auf die enthaltende oder äußere Klasse zugreifen. Um auf die enthaltende Klasse zuzugreifen, übergeben Sie sie als Argument an den Konstruktor der geschachtelten Klasse. Zum Beispiel:


public class Container
{
    public class Nested
    {
        private Container? parent;

        public Nested()
        {
        }
        public Nested(Container parent)
        {
            this.parent = parent;
        }
    }
}

Eine geschachtelte Klasse hat Zugriff auf alle Member, auf die für die enthaltende Klasse zugegriffen werden kann. Sie kann auf private und geschützte Member der enthaltenden Klasse zugreifen, einschließlich aller geerbten geschützten Member.

In der vorherigen Deklaration ist der vollständige Name der Klasse NestedContainer.Nested. Dies ist der Name, der zum Erstellen einer neuen Instanz der geschachtelten Klasse wie folgt verwendet wird:


Container.Nested nest = new Container.Nested();