Freigeben über


7 Grundlegende Konzepte

7.1 Anwendungsstart

Ein Programm kann entweder als Klassenbibliothek kompiliert werden, die als Teil anderer Anwendungen verwendet wird, oder als Anwendung, die direkt gestartet werden kann. Der Mechanismus zum Ermitteln dieses Kompilierungsmodus ist implementierungsdefiniert und außerhalb dieser Spezifikation.

Ein als Antrag kompiliertes Programm muss mindestens eine Methode enthalten, die als Einstiegspunkt qualifiziert ist, indem die folgenden Anforderungen erfüllt werden:

  • Er hat den Namen Main.
  • Er muss sein static.
  • Er darf nicht generisch sein.
  • Sie muss in einem nicht generischen Typ deklariert werden. Wenn es sich bei dem Typ, der die Methode deklariert, um einen geschachtelten Typ handelt, kann es sich bei keinem der eingeschlossenen Typen um generische Typen handelt.
  • Möglicherweise verfügt er über den async Modifizierer, der den Rückgabetyp der Methode aufweist System.Threading.Tasks.Task oder System.Threading.Tasks.Task<int>.
  • Der Rückgabetyp muss void, int, , System.Threading.Tasks.Taskoder System.Threading.Tasks.Task<int>.
  • Es darf keine Teilmethode (§15.6.9) ohne Implementierung sein.
  • Die Parameterliste muss entweder leer sein oder einen einzelnen Wertparameter vom Typ string[]aufweisen.

Hinweis: Methoden mit dem async Modifizierer müssen genau einen der beiden oben angegebenen Rückgabetypen aufweisen, um als Einstiegspunkt zu gelten. Eine async void Methode oder eine async Methode, die einen anderen awaitablen Typ zurückgibt, z ValueTask . B. oder ValueTask<int> gilt nicht als Einstiegspunkt. Endnote

Wenn mehr als eine Methode als Einstiegspunkt in einem Programm deklariert wird, kann ein externer Mechanismus verwendet werden, um anzugeben, welche Methode als tatsächlicher Einstiegspunkt für die Anwendung gilt. Wenn eine qualifizierende Methode mit einem Rückgabetyp int oder void gefunden wird, gilt jede qualifizierende Methode mit einem Rückgabetyp System.Threading.Tasks.Task oder System.Threading.Tasks.Task<int> wird nicht als Einstiegspunktmethode betrachtet. Es handelt sich um einen Kompilierungszeitfehler für ein Programm, das als Anwendung ohne genau einen Einstiegspunkt kompiliert werden soll. Ein als Klassenbibliothek kompiliertes Programm kann Methoden enthalten, die als Einstiegspunkte der Anwendung gelten würden, aber die resultierende Bibliothek hat keinen Einstiegspunkt.

Die deklarierte Barrierefreiheit (§7.5.2) einer Methode wird normalerweise durch die in der Deklaration angegebenen Zugriffsmodifizierer (§15.3.6) bestimmt, und entsprechend wird die deklarierte Barrierefreiheit eines Typs durch die in der Deklaration angegebenen Zugriffsmodifizierer bestimmt. Damit eine bestimmte Methode eines bestimmten Typs aufgerufen werden kann, muss sowohl der Typ als auch das Element zugänglich sein. Der Einstiegspunkt der Anwendung ist jedoch ein Sonderfall. Insbesondere kann die Ausführungsumgebung unabhängig von der deklarierten Barrierefreiheit und unabhängig von der deklarierten Barrierefreiheit der eingeschlossenen Typdeklarationen auf den Einstiegspunkt der Anwendung zugreifen.

Wenn die Einstiegspunktmethode einen Rückgabetyp aufweist System.Threading.Tasks.Task oder System.Threading.Tasks.Task<int>synthetisiert der Compiler eine synchrone Einstiegspunktmethode, die die entsprechende Main Methode aufruft. Die synthetisierte Methode verfügt über Parameter und Rückgabetypen basierend auf der Main Methode:

  • Die Parameterliste der synthetisierten Methode entspricht der Parameterliste der Main Methode.
  • Wenn der Rückgabetyp der Main Methode lautet System.Threading.Tasks.Task, ist der Rückgabetyp der synthetisierten Methode void
  • Wenn der Rückgabetyp der Main Methode lautet System.Threading.Tasks.Task<int>, ist der Rückgabetyp der synthetisierten Methode int

Die Ausführung der synthetisierten Methode wird wie folgt fortgesetzt:

  • Die synthetisierte Methode ruft die Main Methode auf und übergibt den string[] Parameterwert als Argument, wenn die Main Methode einen solchen Parameter aufweist.
  • Wenn die Main Methode eine Ausnahme auslöst, wird die Ausnahme von der synthetisierten Methode weitergegeben.
  • Andernfalls wartet der synthetisierte Einstiegspunkt auf den Abschluss der zurückgegebenen Aufgabe, indem er entweder die parameterlose Instanzmethode oder die durch §C.3 beschriebene Erweiterungsmethode aufruftGetAwaiter().GetResult(). Wenn die Aufgabe fehlschlägt, GetResult() wird eine Ausnahme ausgelöst, und diese Ausnahme wird von der synthetisierten Methode weitergegeben.
  • Bei einer Main Methode mit einem Rückgabetyp von System.Threading.Tasks.Task<int>, wenn die Aufgabe erfolgreich abgeschlossen wird, wird der int von GetResult() der synthetisierten Methode zurückgegebene Wert zurückgegeben.

Der effektive Einstiegspunkt einer Anwendung ist der Einstiegspunkt, der im Programm deklariert ist, oder die synthetisierte Methode, wenn eine wie oben beschrieben erforderlich ist. Der Rückgabetyp des effektiven Einstiegspunkts ist daher immer void oder int.

Wenn eine Anwendung ausgeführt wird, wird eine neue Anwendungsdomäne erstellt. Mehrere verschiedene Instanziierungen einer Anwendung können auf demselben Computer gleichzeitig vorhanden sein, und jede verfügt über eine eigene Anwendungsdomäne. Eine Anwendungsdomäne ermöglicht die Anwendungsisolation, indem sie als Container für den Anwendungsstatus fungiert. Eine Anwendungsdomäne fungiert als Container und Grenze für die in der Anwendung definierten Typen und die von ihr verwendeten Klassenbibliotheken. Typen, die in eine Anwendungsdomäne geladen werden, unterscheiden sich von den gleichen Typen, die in eine andere Anwendungsdomäne geladen werden, und Instanzen von Objekten werden nicht direkt zwischen Anwendungsdomänen gemeinsam genutzt. Beispielsweise verfügt jede Anwendungsdomäne über eine eigene Kopie statischer Variablen für diese Typen, und ein statischer Konstruktor für einen Typ wird höchstens einmal pro Anwendungsdomäne ausgeführt. Implementierungen sind frei, implementierungsdefinierte Richtlinien oder Mechanismen für die Erstellung und Zerstörung von Anwendungsdomänen bereitzustellen.

Der Anwendungsstart tritt auf, wenn die Ausführungsumgebung den effektiven Einstiegspunkt der Anwendung aufruft. Wenn der effektive Einstiegspunkt einen Parameter deklariert, stellt die Implementierung während des Anwendungsstarts sicher, dass der Anfangswert dieses Parameters ein Nicht-Null-Verweis auf ein Zeichenfolgenarray ist. Dieses Array muss aus Nicht-NULL-Verweisen auf Zeichenfolgen bestehen, die als Anwendungsparameter bezeichnet werden, die vor dem Start der Anwendung von der Hostumgebung implementierungsdefinierte Werte erhalten. Die Absicht besteht darin, die Anwendungsinformationen bereitzustellen, die vor dem Starten der Anwendung von einer anderen Stelle in der gehosteten Umgebung bestimmt wurden.

Hinweis: Auf Systemen, die eine Befehlszeile unterstützen, entsprechen Anwendungsparametern dem, was allgemein als Befehlszeilenargumente bezeichnet wird. Endnote

Wenn der Rückgabetyp des effektiven Einstiegspunkts lautet int, wird der Rückgabewert aus dem Methodenaufruf durch die Ausführungsumgebung in der Anwendungsbeendigung (§7.2) verwendet.

Abgesehen von den oben aufgeführten Situationen verhalten sich Einstiegspunktmethoden wie die, die nicht in jeder Hinsicht Einstiegspunkte sind. Wenn der Einstiegspunkt zu einem anderen Zeitpunkt während der Lebensdauer der Anwendung aufgerufen wird, z. B. durch reguläre Methodenaufrufe, gibt es keine spezielle Behandlung der Methode: Wenn ein Parameter vorhanden ist, kann er einen Anfangswert von nulloder einen Nichtwertnull aufweisen, der auf ein Array verweist, das NULL-Verweise enthält. Ebenso hat der Rückgabewert des Einstiegspunkts keine besondere Bedeutung als im Aufruf aus der Ausführungsumgebung.

7.2 Kündigung der Bewerbung

Die Beendigung der Anwendung gibt die Steuerung in die Ausführungsumgebung zurück.

Wenn der Rückgabetyp der effektiven Einstiegspunktmethode der Anwendung erfolgt und die Ausführung ohne Ausnahme abgeschlossen wirdint, dient der Wert des int zurückgegebenen Elements als Beendigungsstatuscode der Anwendung. Der Zweck dieses Codes besteht darin, die Kommunikation von Erfolg oder Fehlschlagen an die Ausführungsumgebung zu ermöglichen. Wenn der Rückgabetyp der effektiven Einstiegspunktmethode erfolgt und die Ausführung ohne Ausnahme abgeschlossen ist void , lautet 0der Beendigungsstatuscode .

Wenn die effektive Einstiegspunktmethode aufgrund einer Ausnahme (§21.4) beendet wird, wird der Beendigungscode implementierungsdefiniert. Darüber hinaus kann die Implementierung alternative APIs zum Angeben des Ausgangscodes bereitstellen.

Unabhängig davon, ob Finalizer (§15.13) als Teil der Anwendungsendung ausgeführt werden, wird implementierungsdefiniert.

Hinweis: Die .NET Framework-Implementierung bemüht sich, Finalizer (§15.13) für alle Objekte aufzurufen, die noch nicht garbage collection wurden, es sei denn, diese Bereinigung wurde unterdrückt (z. B. durch einen Aufruf der Bibliotheksmethode GC.SuppressFinalize). Endnote

7.3 Erklärungen

Deklarationen in einem C#-Programm definieren die bestandteilen Elemente des Programms. C#-Programme werden mithilfe von Namespaces organisiert. Diese werden mithilfe von Namespacedeklarationen (§14) eingeführt, die Typdeklarationen und geschachtelte Namespacedeklarationen enthalten können. Typdeklarationen (§14.7) werden verwendet, um Klassen (§15), Strukturen (§16), Schnittstellen (§18), Enumerationen (§19) und Delegaten (§20) zu definieren. Die Typen von Elementen, die in einer Typdeklaration zulässig sind, hängen von der Form der Typdeklaration ab. Beispiel: Klassendeklarationen können Deklarationen für Konstanten (§15.4), Felder (§15.5), Methoden (§15.6), Eigenschaften (§15.7), Ereignisse (§15.8), Indexer (§15.9) enthalten. Operatoren (§15.10), Instanzkonstruktoren (§15.11), statische Konstruktoren (§15.12), Finalizer (§15.13) und geschachtelte Typen (§15.3.9).

Eine Deklaration definiert einen Namen im Deklarationsraum , zu dem die Deklaration gehört. Es handelt sich um einen Kompilierungsfehler, wenn zwei oder mehr Deklarationen vorhanden sind, die Member mit demselben Namen in einem Deklarationsraum einführen, mit Ausnahme der folgenden Fälle:

  • Zwei oder mehr Namespacedeklarationen mit demselben Namen sind im selben Deklarationsraum zulässig. Solche Namespacedeklarationen werden aggregiert, um einen einzelnen logischen Namespace zu bilden und einen einzelnen Deklarationsraum freizugeben.
  • Deklarationen in separaten Programmen, aber im selben Namespacedeklarationsraum dürfen denselben Namen gemeinsam nutzen.

    Hinweis: Diese Deklarationen können jedoch mehrdeutig sein, wenn sie in derselben Anwendung enthalten sind. Endnote

  • Zwei oder mehr Methoden mit demselben Namen, aber unterschiedliche Signaturen sind im selben Deklarationsraum zulässig (§7.6).
  • Zwei oder mehr Typdeklarationen mit demselben Namen, aber unterschiedliche Zahlen von Typparametern sind im selben Deklarationsraum zulässig (§7.8.2).
  • Zwei oder mehr Typdeklarationen mit dem Teilmodifizierer im selben Deklarationsraum können denselben Namen, dieselbe Anzahl von Typparametern und dieselbe Klassifizierung (Klasse, Struktur oder Schnittstelle) aufweisen. In diesem Fall tragen die Typdeklarationen zu einem einzelnen Typ bei und werden selbst aggregiert, um einen einzelnen Deklarationsraum (§15.2.7) zu bilden.
  • Eine Namespacedeklaration und eine Typdeklaration im selben Deklarationsraum können denselben Namen gemeinsam nutzen, solange die Typdeklaration mindestens einen Typparameter hat (§7.8.2).

Es gibt mehrere verschiedene Typen von Deklarationszeichen, wie im Folgenden beschrieben.

  • In allen Kompilierungseinheiten eines Programms sind namespace_member_declarationohne eingeschlossene namespace_declaration Member eines einzigen kombinierten Deklarationsraums, der als globaler Deklarationsbereich bezeichnet wird.
  • Innerhalb aller Kompilierungseinheiten eines Programms sind namespace_member_declarationinnerhalb namespace_declarations mit demselben vollqualifizierten Namespacenamen Member eines einzigen kombinierten Deklarationsraums.
  • Jede compilation_unit und namespace_body verfügt über einen Aliasdeklarationsbereich. Jede extern_alias_directive und using_alias_directive der compilation_unit oder namespace_body trägt zum Aliasdeklarationsbereich bei (§14.5.2).
  • Jede nicht partielle Klasse, Struktur oder Schnittstellendeklaration erstellt einen neuen Deklarationsraum. Jede partielle Klassen-, Struktur- oder Schnittstellendeklaration trägt zu einem Deklarationsbereich bei, der von allen übereinstimmenden Teilen im selben Programm gemeinsam genutzt wird (§16.2.4). Namen werden in diesen Deklarationsbereich über class_member_declarations, struct_member_declarations, interface_member_declarations oder type_parameters eingeführt. Mit Ausnahme von überladenen Instanzkonstruktordeklarationen und statischen Konstruktordeklarationen kann eine Klasse oder Struktur keine Memberdeklaration mit demselben Namen wie die Klasse oder Struktur enthalten. Eine Klasse, Struktur oder Schnittstelle ermöglicht die Deklaration überladener Methoden und Indexer. Darüber hinaus ermöglicht eine Klasse oder Struktur die Deklaration von überladenen Instanzkonstruktoren und -operatoren. Eine Klasse, Struktur oder Schnittstelle kann z. B. mehrere Methodendeklarationen mit demselben Namen enthalten, vorausgesetzt, diese Methodendeklarationen unterscheiden sich in ihrer Signatur (§7.6). Beachten Sie, dass Basisklassen nicht zum Deklarationsraum einer Klasse beitragen, und Basisschnittstellen tragen nicht zum Deklarationsraum einer Schnittstelle bei. Daher kann eine abgeleitete Klasse oder Schnittstelle ein Element mit demselben Namen wie ein geerbtes Element deklarieren. Ein solches Mitglied wird gesagt, das geerbte Mitglied auszublenden.
  • Jede Delegatdeklaration erstellt einen neuen Deklarationsraum. Namen werden in diesen Deklarationsbereich durch Parameter (fixed_parameters und parameter_array) und type_parameters eingeführt.
  • Jede Enumerationsdeklaration erstellt einen neuen Deklarationsraum. Namen werden über enum_member_declarations in diesen Deklarationsbereich eingeführt.
  • Jede Methodendeklaration, Eigenschaftendeklaration, Eigenschaftsaccessordeklaration, Indexerdeklaration, Indexeraccessordeklaration, Operatordeklaration, Instanzkonstruktordeklaration, anonyme Funktion und lokale Funktion erstellt einen neuen Deklarationsbereich, der als lokaler Deklarationsbereich bezeichnet wird. Namen werden in diesen Deklarationsbereich durch Parameter (fixed_parameters und parameter_array) und type_parameters eingeführt. Der Set-Accessor für eine Eigenschaft oder ein Indexer führt den Namen value als Parameter ein. Der Textkörper des Funktionselements, der anonymen Funktion oder der lokalen Funktion (sofern vorhanden) wird als im lokalen Variablendeklarationsbereich geschachtelt. Wenn ein lokales Variablendeklarationsraum und ein geschachteltes lokales Variablendeklarationsraum Elemente mit demselben Namen enthalten, wird der äußere lokale Name im Bereich des geschachtelten lokalen Namens durch den geschachtelten lokalen Namen ausgeblendet (§7.7.1).
  • Zusätzliche lokale Deklarationsplätze können in Memberdeklarationen, anonymen Funktionen und lokalen Funktionen auftreten. Namen werden in diese Deklarationsräume durch Muster, declaration_expressions, declaration_statements und exception_specifiers eingeführt. Lokale Variablendeklarationsplätze können geschachtelt sein, aber es handelt sich um einen Fehler für einen lokalen Variablendeklarationsbereich und einen geschachtelten lokalen Variablendeklarationsbereich, der Elemente mit demselben Namen enthält. Daher ist es innerhalb eines geschachtelten Deklarationsbereichs nicht möglich, eine lokale Variable, lokale Funktion oder Konstante mit demselben Namen wie ein Parameter, Typparameter, lokale Variable, lokale Funktion oder Konstante in einem eingeschlossenen Deklarationsraum zu deklarieren. Es ist möglich, dass zwei Deklarationsbereiche Elemente mit demselben Namen enthalten, solange keiner der beiden Deklarationsbereiche den anderen enthält. Lokale Deklarationsräume werden durch die folgenden Konstrukte erstellt:
    • Jede variable_initializer in einem Feld und einer Eigenschaftsdeklaration führt einen eigenen lokalen Variablendeklarationsbereich ein, der nicht in einem anderen lokalen Variablendeklarationsbereich geschachtelt ist.
    • Der Textkörper eines Funktionselements, einer anonymen Funktion oder einer lokalen Funktion erstellt, falls vorhanden, einen lokalen Deklarationsbereich der Variablen, der als geschachtelt im lokalen Variablendeklarationsbereich der Funktion betrachtet wird.
    • Jede constructor_initializer erstellt einen lokalen Deklarationsbereich, der in der Instanzkonstruktordeklaration geschachtelt ist. Der lokale Deklarationsbereich der Variablen für den Konstruktortext wird wiederum in diesem lokalen Variablendeklarationsbereich geschachtelt.
    • Jeder Block, switch_block, specific_catch_clause, iteration_statement und using_statement erstellt einen geschachtelten lokalen Variablendeklarationsbereich.
    • Jede embedded_statement , die nicht direkt Teil eines statement_list ist, erstellt einen geschachtelten Deklarationsbereich für lokale Variablen.
    • Jede switch_section erstellt einen geschachtelten lokalen Variablendeklarationsbereich. Variablen, die direkt innerhalb des statement_list des switch_section deklariert werden (jedoch nicht innerhalb eines geschachtelten lokalen Variablendeklarationsbereichs innerhalb der statement_list), werden direkt zum lokalen Variablendeklarationsbereich des eingeschlossenen switch_block und nicht in den switch_section hinzugefügt.
    • Die syntaktische Übersetzung einer query_expression (§12.20.3) kann einen oder mehrere Lambda-Ausdrücke einführen. Als anonyme Funktionen erstellt jede dieser Funktionen wie oben beschrieben einen lokalen Deklarationsraum für Variablen.
  • Jeder Block oder switch_block erstellt einen separaten Deklarationsraum für Bezeichnungen. Namen werden in diesen Deklarationsbereich über labeled_statements eingeführt, und auf die Namen wird über goto_statements verwiesen. Der Beschriftungsdeklarationsbereich eines Blocks enthält alle geschachtelten Blöcke. Daher ist es in einem geschachtelten Block nicht möglich, eine Bezeichnung mit demselben Namen wie eine Beschriftung in einem eingeschlossenen Block zu deklarieren.

Hinweis: Die Tatsache, dass Variablen, die direkt in einem switch_section deklariert wurden, dem lokalen Deklarationsbereich der variablen switch_block anstelle der switch_section hinzugefügt werden, kann zu überraschendem Code führen. Im folgenden Beispiel befindet sich die lokale Variable y im Bereich des Switchabschnitts für den Standardfall, obwohl die Deklaration im Switch-Abschnitt für den Fall 0 angezeigt wird. Die lokale Variable z befindet sich nicht im Bereich des Switch-Abschnitts für den Standardfall, da sie im lokalen Deklarationsbereich der Variablen für den Switchabschnitt eingeführt wird, in dem die Deklaration auftritt.

int x = 1;
switch (x)
{
    case 0:
        int y;
        break;
    case var z when z < 10:
        break;
    default:
        y = 10;
        // Valid: y is in scope
        Console.WriteLine(x + y);
        // Invalid: z is not scope
        Console.WriteLine(x + z);
        break;
}

Endnote

Die Textreihenfolge, in der Namen deklariert werden, ist im Allgemeinen nicht von Bedeutung. Insbesondere ist die Textreihenfolge für die Deklaration und Verwendung von Namespaces, Konstanten, Methoden, Eigenschaften, Ereignissen, Indexern, Operatoren, Instanzkonstruktoren, Finalizern, statischen Konstruktoren und Typen nicht von Bedeutung. Die Deklarationsreihenfolge ist auf folgende Weise von Bedeutung:

  • Die Deklarationsreihenfolge für Felddeklarationen bestimmt die Reihenfolge, in der ihre Initialisierer (falls vorhanden) ausgeführt werden (§15.5.6.2, §15.5.6.3).
  • Lokale Variablen müssen definiert werden, bevor sie verwendet werden (§7.7).
  • Die Deklarationsreihenfolge für Enumerationsmitgliedsdeklarationen (§19.4) ist wichtig, wenn constant_expression Werte weggelassen werden.

Beispiel: Der Deklarationsraum eines Namespaces ist "open ends", und zwei Namespacedeklarationen mit demselben vollqualifizierten Namen tragen zum gleichen Deklarationsraum bei. Beispiel:

namespace Megacorp.Data
{
    class Customer
    {
        ...
    }
}

namespace Megacorp.Data
{
    class Order
    {
        ...
    }
}

Die beiden obigen Namespacedeklarationen tragen zum gleichen Deklarationsraum bei, in diesem Fall zwei Klassen mit den vollqualifizierten Namen Megacorp.Data.Customer und Megacorp.Data.Order. Da die beiden Deklarationen zum gleichen Deklarationsbereich beitragen, hätte sie einen Kompilierungszeitfehler verursacht, wenn jede eine Deklaration einer Klasse mit demselben Namen enthielt.

Endbeispiel

Hinweis: Wie oben angegeben, enthält der Deklarationsbereich eines Blocks alle geschachtelten Blöcke. Daher führen die F Methoden im G folgenden Beispiel zu einem Kompilierungszeitfehler, da der Name i im äußeren Block deklariert und nicht im inneren Block neu deklariert werden kann. Die Methoden und I Methoden H sind jedoch gültig, da die beiden iBlöcke in separaten, nicht geschachtelten Blöcken deklariert werden.

class A
{
    void F()
    {
        int i = 0;
        if (true)
        {
            int i = 1;
        }
    }

    void G()
    {
        if (true)
        {
            int i = 0;
        }
        int i = 1;
    }

    void H()
    {
        if (true)
        {
            int i = 0;
        }
        if (true)
        {
            int i = 1;
        }
    }

    void I()
    {
        for (int i = 0; i < 10; i++)
        {
            H();
        }
        for (int i = 0; i < 10; i++)
        {
            H();
        }
    }
}

Endnote

7.4 Mitglieder

7.4.1 Allgemein

Namespaces und Typen verfügen über Member.

Hinweis: Die Member einer Entität sind allgemein über die Verwendung eines qualifizierten Namens verfügbar, der mit einem Verweis auf die Entität beginnt, gefolgt von einem "."-Token, gefolgt vom Namen des Elements. Endnote

Elemente eines Typs werden entweder in der Typdeklaration deklariert oder von der Basisklasse des Typs geerbt. Wenn ein Typ von einer Basisklasse erbt, werden alle Member der Basisklasse, mit Ausnahme von Instanzkonstruktoren, Finalizern und statischen Konstruktoren, Member des abgeleiteten Typs. Die deklarierte Barrierefreiheit eines Basisklassenmembers steuert nicht, ob das Element geerbt wird. Die Vererbung erstreckt sich auf alle Member, die kein Instanzkonstruktor, statischer Konstruktor oder Finalizer sind.

Hinweis: Auf ein geerbtes Element kann jedoch nicht in einem abgeleiteten Typ zugegriffen werden, z. B. aufgrund seiner deklarierten Barrierefreiheit (§7.5.2). Endnote

7.4.2 Namespace-Member

Namespaces und Typen ohne eingeschlossenen Namespace sind Member des globalen Namespaces. Dies entspricht direkt den Im globalen Deklarationsraum deklarierten Namen.

Namespaces und Typen, die in einem Namespace deklariert sind, sind Member dieses Namespaces. Dies entspricht direkt den Im Deklarationsraum des Namespace deklarierten Namen.

Namespaces haben uneingeschränkten Zugriff. Es ist nicht möglich, private, geschützte oder interne Namespaces zu deklarieren, und Namespacenamen sind immer öffentlich zugänglich.

7.4.3 Strukturmitglieder

Die Member einer Struktur sind die In der Struktur deklarierten Member und die Member, die von der direkten Basisklasse System.ValueType der Struktur und der indirekten Basisklasse objectgeerbt wurden.

Die Member eines einfachen Typs entsprechen direkt den Membern des Strukturtyps, die vom einfachen Typ aliast werden (§8.3.5).

7.4.4 Enumerationsmitglieder

Die Member einer Aufzählung sind die in der Enumeration deklarierten Konstanten und die Member, die von der direkten Basisklasse System.Enum der Enumeration und den indirekten Basisklassen System.ValueType geerbt wurden.object

7.4.5 Klassenmitglieder

Die Member einer Klasse sind die Mitglieder, die in der Klasse deklariert sind, und die Member, die von der Basisklasse geerbt wurden (mit Ausnahme der Klasse object , die keine Basisklasse aufweist). Die von der Basisklasse geerbten Member umfassen die Konstanten, Felder, Methoden, Eigenschaften, Ereignisse, Indexer, Operatoren und Typen der Basisklasse, jedoch nicht die Instanzkonstruktoren, Finalizer und statische Konstruktoren der Basisklasse. Basisklassenmber werden ohne Barrierefreiheit geerbt.

Eine Klassendeklaration kann Deklarationen von Konstanten, Feldern, Methoden, Eigenschaften, Ereignissen, Indexern, Operatoren, Instanzkonstruktoren, Finalizern, statischen Konstruktoren und Typen enthalten.

Die Member von object (§8.2.3) und string (§8.2.5) entsprechen direkt den Mitgliedern der Klassentypen, die sie aliasen.

7.4.6 Schnittstellenmitglieder

Die Member einer Schnittstelle sind die Elemente, die in der Schnittstelle und in allen Basisschnittstellen der Schnittstelle deklariert sind.

Hinweis: Die Mitglieder in der Klasse object sind nicht streng genommen Member einer Schnittstelle (§18.4). Die Member in der Klasse object sind jedoch über die Membersuche in einem beliebigen Schnittstellentyp (§12.5) verfügbar. Endnote

7.4.7 Array-Mitglieder

Die Member eines Arrays sind die Elemente, die von der Klasse System.Arraygeerbt werden.

7.4.8 Delegierte Mitglieder

Eine Stellvertretung erbt Member von der Klasse System.Delegate. Darüber hinaus enthält sie eine Methode Invoke mit demselben Rückgabetyp und derselben Parameterliste, die in der Deklaration angegeben ist (§20.2). Ein Aufruf dieser Methode verhält sich identisch mit einem Stellvertretungsaufruf (§20.6) in derselben Stellvertretungsinstanz.

Eine Implementierung kann zusätzliche Member bereitstellen, entweder durch Vererbung oder direkt in der Stellvertretung selbst.

7.5 Mitgliedszugriff

7.5.1 Allgemein

Deklarationen von Mitgliedern ermöglichen die Kontrolle über den Memberzugriff. Die Barrierefreiheit eines Mitglieds wird durch die deklarierte Barrierefreiheit (§7.5.2) des Mitglieds in Kombination mit der Barrierefreiheit des unmittelbar enthaltenden Typs( falls vorhanden) festgelegt.

Wenn der Zugriff auf ein bestimmtes Mitglied erlaubt ist, wird das Mitglied als barrierefrei bezeichnet. Umgekehrt gilt, dass der Zugriff auf ein bestimmtes Mitglied nicht zulässig ist, auf das Mitglied zugegriffen werden kann. Der Zugriff auf ein Mitglied ist zulässig, wenn der Textspeicherort, an dem der Zugriff stattfindet, in der Barrierefreiheitsdomäne (§7.5.3) des Mitglieds enthalten ist.

7.5.2 Barrierefreiheit deklariert

Die deklarierte Barrierefreiheit eines Mitglieds kann eine der folgenden Sein:

  • Öffentlich, das durch Einschließen eines public Modifizierers in die Memberdeklaration ausgewählt wird. Die intuitive Bedeutung public ist "Zugriff nicht beschränkt".
  • Geschützt, das durch Einschließen eines protected Modifizierers in die Memberdeklaration ausgewählt wird. Die intuitive Bedeutung protected ist "Zugriff auf die enthaltende Klasse oder Typen, die von der enthaltenden Klasse abgeleitet sind".
  • Intern, das durch Einschließen eines internal Modifizierers in die Memberdeklaration ausgewählt wird. Die intuitive Bedeutung internal ist "Zugriff auf diese Montage beschränkt".
  • Geschütztes internes Element, das durch Einschließen eines protected Und eines internal Modifizierers in die Memberdeklaration ausgewählt wird. Die intuitive Bedeutung protected internal ist "innerhalb dieser Assembly zugänglich und Typen, die von der enthaltenden Klasse abgeleitet sind".
  • Privat geschützt, das durch Einschließen eines private protected Modifizierers in die Memberdeklaration ausgewählt wird. Die intuitive Bedeutung private protected ist "zugänglich innerhalb dieser Assembly durch die enthaltende Klasse und Typen, die von der enthaltenden Klasse abgeleitet sind.".
  • Privat, das durch Einschließen eines private Modifizierers in die Memberdeklaration ausgewählt wird. Die intuitive Bedeutung private lautet "Zugriff auf den enthaltenden Typ".

Abhängig vom Kontext, in dem eine Memberdeklaration stattfindet, sind nur bestimmte Typen deklarierter Barrierefreiheit zulässig. Wenn eine Memberdeklaration keine Zugriffsmodifizierer enthält, bestimmt der Kontext, in dem die Deklaration stattfindet, die standardmäßig deklarierte Barrierefreiheit.

  • Namespaces haben public implizit Barrierefreiheit deklariert. Für Namespacedeklarationen sind keine Zugriffsmodifizierer zulässig.
  • Typen, die direkt in Kompilierungseinheiten oder Namespaces deklariert werden (im Gegensatz zu anderen Typen), können Barrierefreiheit und Standard für internal deklarierte Barrierefreiheit aufweisen oder internal deklariert werdenpublic.
  • Klassenmitglieder können über eine der zulässigen Arten von deklarierter Barrierefreiheit verfügen und standardmäßig die private Barrierefreiheit deklariert haben.

    Hinweis: Ein als Mitglied einer Klasse deklarierter Typ kann über eine der zulässigen Arten deklarierter Barrierefreiheit verfügen, während ein typ, der als Mitglied eines Namespace deklariert wurde, nur public über Barrierefreiheit verfügen internal oder deklariert werden kann. Endnote

  • Strukturmmber können Barrierefreiheit oder private deklarierte Barrierefreiheit und standardmäßig private deklarierte Barrierefreiheit aufweisenpublicinternal, da Strukturen implizit versiegelt sind. Strukturmember, die in einer struct (d. h. nicht geerbten Struktur) eingeführt wurden, können weder über Barrierefreiheit noch über deklarierte Barrierefreiheit verfügenprotected internalprotectedprivate protected.

    Hinweis: Ein als Mitglied einer Struktur deklarierter Typ kann Barrierefreiheit aufweisen, internaloder private eine Barrierefreiheit deklariert werdenpublic, während ein typ, der als Mitglied eines Namespace deklariert ist, nur public über Barrierefreiheit verfügen oder internal deklariert werden kann. Endnote

  • Schnittstellenmitglieder haben public implizit Barrierefreiheit deklariert. Für Schnittstellenmembezeichner sind keine Zugriffsmodifizierer zulässig.
  • Enumerationsmitglieder haben public implizit Barrierefreiheit deklariert. Für Enumerationsmememerdeklarationen sind keine Zugriffsmodifizierer zulässig.

7.5.3 Barrierefreiheitsdomänen

Die Barrierefreiheitsdomäne eines Mitglieds besteht aus den (möglicherweise nicht zusammenhängenden) Abschnitten des Programmtexts, in denen der Zugriff auf das Mitglied zulässig ist. Zum Definieren der Barrierefreiheitsdomäne eines Mitglieds wird ein Mitglied als oberste Ebene bezeichnet, wenn es nicht innerhalb eines Typs deklariert ist, und ein Element wird als geschachtelt bezeichnet, wenn es innerhalb eines anderen Typs deklariert wird. Darüber hinaus wird der Programmtext eines Programms als sämtlicher Text definiert, der in allen Kompilierungseinheiten des Programms enthalten ist, und der Programmtext eines Typs wird als sämtlicher Text definiert, der in den type_declarations dieses Typs enthalten ist (einschließlich möglicherweise Typen, die innerhalb des Typs geschachtelt sind).

Die Barrierefreiheitsdomäne eines vordefinierten Typs (z object. B. , intoder double) ist unbegrenzt.

Die Barrierefreiheitsdomäne eines ungebundenen Typs T der obersten Ebene (§8.4.4), die in einem Programm P deklariert ist, ist wie folgt definiert:

  • Wenn die deklarierte Barrierefreiheit öffentlich T ist, ist die Barrierefreiheitsdomäne T der Programmtext und P jedes Programm, auf das verwiesen wird P.
  • Wenn die deklarierte Barrierefreiheit intern ist, ist die Barrierefreiheitsdomäne T T der Programmtext von P.

Hinweis: Aus diesen Definitionen folgt, dass die Barrierefreiheitsdomäne eines ungebundenen Typs der obersten Ebene immer mindestens der Programmtext des Programms ist, in dem dieser Typ deklariert wird. Endnote

Die Barrierefreiheitsdomäne für einen konstruierten Typ T<A₁, ..., Aₑ> ist die Schnittmenge der Barrierefreiheitsdomäne des ungebundenen generischen Typs T und die Barrierefreiheitsdomänen der Typargumente A₁, ..., Aₑ.

Die Barrierefreiheitsdomäne eines geschachtelten Mitglieds M , das in einem Typ T innerhalb eines Programms Pdeklariert ist, wird wie folgt definiert (wobei angegeben wird, dass M es sich möglicherweise um einen Typ handelt):

  • Wenn die deklarierte Zugriffsart von M den Wert public hat, entspricht die Zugriffsdomäne von M der von T.
  • Wenn die deklarierte Barrierefreiheit M lautet protected internal, lassen Sie uns D die Vereinigung des Programmtexts P und des Programmtexts eines beliebigen Typs sein, der von T, der außerhalb Pdeklariert wird. Die Barrierefreiheitsdomäne ist die Schnittmenge der Barrierefreiheitsdomäne M mit DT .
  • Wenn die deklarierte Barrierefreiheit M lautet private protected, lassen Sie uns D die Schnittmenge des Programmtexts P und des Programmtexts und T eines beliebigen Typs sein, der von T. Die Barrierefreiheitsdomäne ist die Schnittmenge der Barrierefreiheitsdomäne M mit DT .
  • Wenn die deklarierte Barrierefreiheit M lautet protected, lassen Sie uns D die Vereinigung des Programmtexts Tund des Programmtexts eines beliebigen Typs sein, der von T. Die Barrierefreiheitsdomäne ist die Schnittmenge der Barrierefreiheitsdomäne M mit DT .
  • Wenn die deklarierte Zugriffsart von M den Wert internal hat, entspricht die Zugriffsdomäne von M der Schnittmenge zwischen der Zugriffsdomäne von T und dem Programmtext von P.
  • Wenn die deklarierte Zugriffsart von M den Wert private hat, entspricht die Zugriffsdomäne von M dem Programmtext von T.

Hinweis: Aus diesen Definitionen folgt, dass die Barrierefreiheitsdomäne eines geschachtelten Elements immer mindestens der Programmtext des Typs ist, in dem das Mitglied deklariert wird. Darüber hinaus ist die Barrierefreiheitsdomäne eines Mitglieds niemals inklusiver als die Barrierefreiheitsdomäne des Typs, in dem das Mitglied deklariert wird. Endnote

Hinweis: Wenn auf einen Typ oder ein Mitglied M zugegriffen wird, werden die folgenden Schritte ausgewertet, um sicherzustellen, dass der Zugriff zulässig ist:

  • M Wenn sie innerhalb eines Typs deklariert wird (im Gegensatz zu einer Kompilierungseinheit oder einem Namespace), tritt zunächst ein Kompilierungszeitfehler auf, wenn auf diesen Typ nicht zugegriffen werden kann.
  • Wenn ja, M ist publicder Zugriff zulässig.
  • Andernfalls ist protected internalder Zugriff zulässig, M wenn er innerhalb des Programms auftritt, in dem M deklariert wird, oder wenn er innerhalb einer von der Klasse abgeleiteten Klasse auftritt, in M der sie deklariert wird und über den abgeleiteten Klassentyp (§7.5.4) erfolgt.
  • Andernfalls ist protectedder Zugriff zulässig, M wenn er innerhalb der klasse auftritt, in M der deklariert wird, oder wenn er innerhalb einer von der Klasse abgeleiteten Klasse auftritt, M in der sie deklariert wird und über den abgeleiteten Klassentyp (§7.5.4) erfolgt.
  • Andernfalls ist internalder Zugriff zulässig, M wenn er innerhalb des Programms auftritt, in dem M deklariert wird.
  • Andernfalls ist privateder Zugriff zulässig, M wenn er innerhalb des Typs auftritt, in dem M deklariert wird.
  • Andernfalls kann auf den Typ oder das Element nicht zugegriffen werden, und ein Kompilierungszeitfehler tritt auf. Endnote

Beispiel: Im folgenden Code

public class A
{
    public static int X;
    internal static int Y;
    private static int Z;
}

internal class B
{
    public static int X;
    internal static int Y;
    private static int Z;

    public class C
    {
        public static int X;
        internal static int Y;
        private static int Z;
    }

    private class D
    {
        public static int X;
        internal static int Y;
        private static int Z;
    }
}

die Klassen und Member verfügen über die folgenden Barrierefreiheitsdomänen:

  • Die Barrierefreiheitsdomäne von A und A.X ist unbegrenzt.
  • Die Barrierefreiheitsdomäne von A.Y, B, B.X, B.CB.Y, B.C.Xund B.C.Y ist der Programmtext des enthaltenden Programms.
  • Die Barrierefreiheitsdomäne ist A.Z der Programmtext von A.
  • Die Barrierefreiheitsdomäne von B.Z und B.D ist der Programmtext von B, einschließlich des Programmtexts von B.C und B.D.
  • Die Barrierefreiheitsdomäne ist B.C.Z der Programmtext von B.C.
  • Die Barrierefreiheitsdomäne von B.D.X und B.D.Y ist der Programmtext von B, einschließlich des Programmtexts von B.C und B.D.
  • Die Barrierefreiheitsdomäne ist B.D.Z der Programmtext von B.D. Wie das Beispiel zeigt, ist die Barrierefreiheitsdomäne eines Elements nie größer als die eines enthaltenden Typs. Auch wenn alle X Mitglieder die Barrierefreiheit öffentlich deklariert haben, verfügen alle Aber A.X über Barrierefreiheitsdomänen, die durch einen enthaltenden Typ eingeschränkt sind.

Endbeispiel

Wie in §7.4 beschrieben, werden alle Member einer Basisklasse, mit Ausnahme von Konstruktoren, Finalizern und statischen Konstruktoren, von abgeleiteten Typen geerbt. Dazu gehören sogar private Member einer Basisklasse. Die Barrierefreiheitsdomäne eines privaten Mitglieds enthält jedoch nur den Programmtext des Typs, in dem das Mitglied deklariert wird.

Beispiel: Im folgenden Code

class A
{
    int x;

    static void F(B b)
    {
        b.x = 1;         // Ok
    }
}

class B : A
{
    static void F(B b)
    {
        b.x = 1;         // Error, x not accessible
    }
}

die B Klasse erbt das private Mitglied x von der A Klasse. Da das Mitglied privat ist, ist es nur innerhalb der class_body von A. Daher ist der Zugriff auf b.x die A.F Methode erfolgreich, schlägt jedoch in der B.F Methode fehl.

Endbeispiel

7.5.4 Geschützter Zugriff

Wenn außerhalb des Programmtexts der Klasse, in der sie deklariert wird, protected internal auf ein protected private protected Instanzmitglied außerhalb des Programmtexts des Programms zugegriffen wird, in dem er deklariert wird, findet der Zugriff innerhalb einer Klassendeklaration statt, die von der Klasse abgeleitet ist, in der es deklariert wird. Darüber hinaus ist der Zugriff über eine Instanz dieses abgeleiteten Klassentyps oder eines daraus erstellten Klassentyps erforderlich. Diese Einschränkung verhindert, dass eine abgeleitete Klasse auf geschützte Member anderer abgeleiteter Klassen zugreift, auch wenn die Member von derselben Basisklasse geerbt werden.

Let B be a base class that declares a protected instance member M, and let D be a class that abgeleitet von B. Innerhalb der class_body von Dkann der Zugriff M auf eine der folgenden Formen erfolgen:

  • Eine nicht qualifizierte type_name oder primary_expression des Formulars M.
  • Eine primary_expression des FormularsE.M, vorausgesetzt, der Typ ist E T oder eine Klasse, die von Tder Klasse abgeleitet ist, wobei T die Klasse Doder ein Klassentyp erstellt wirdD.
  • Eine primary_expression des Formulars base.M.
  • Eine primary_expression des Formularsbase[ argument_list].

Zusätzlich zu diesen Zugriffsformen kann eine abgeleitete Klasse auf einen geschützten Instanzkonstruktor einer Basisklasse in einer constructor_initializer (§15.11.2) zugreifen.

Beispiel: Im folgenden Code

public class A
{
    protected int x;

    static void F(A a, B b)
    {
        a.x = 1; // Ok
        b.x = 1; // Ok
    }
}

public class B : A
{
    static void F(A a, B b)
    {
        a.x = 1; // Error, must access through instance of B
        b.x = 1; // Ok
    }
}

innerhalb A, ist es möglich, über x Instanzen von beiden A und B, da in beiden Fällen der Zugriff über eine Instanz von A oder eine Klasse, die von A. Innerhalb Bist es jedoch nicht möglich x , über eine Instanz von A, da A nicht von B.

Endbeispiel

Beispiel:

class C<T>
{
    protected T x;
}

class D<T> : C<T>
{
    static void F()
    {
        D<T> dt = new D<T>();
        D<int> di = new D<int>();
        D<string> ds = new D<string>();
        dt.x = default(T);
        di.x = 123;
        ds.x = "test";
    }
}

Hier sind die drei Zuordnungen x zulässig, da sie alle über Instanzen von Klassentypen erfolgen, die aus dem generischen Typ erstellt wurden.

Endbeispiel

Hinweis: Die Barrierefreiheitsdomäne (§7.5.3) eines in einer generischen Klasse deklarierten geschützten Elements enthält den Programmtext aller Klassendeklarationen, die von jedem Typ abgeleitet wurden, der aus dieser generischen Klasse erstellt wurde. Im Beispiel:

class C<T>
{
    protected static T x;
}

class D : C<string>
{
    static void Main()
    {
        C<int>.x = 5;
    }
}

der Verweis auf protected member C<int>.x in D ist gültig, obwohl die Klasse D von C<string>. Endnote

7.5.5 Einschränkungen bei der Barrierefreiheit

Mehrere Konstrukte in der C#-Sprache erfordern einen Typ, um mindestens so barrierefrei wie ein Element oder einen anderen Typ zu sein. Ein Typ T soll mindestens so barrierefrei sein wie ein Mitglied oder Typ M , wenn die Barrierefreiheitsdomäne T eine Obermenge der Barrierefreiheitsdomäne von Mist. Mit anderen Worten, T ist mindestens so barrierefrei wie M T in allen Kontexten, in denen M barrierefrei ist.

Die folgenden Einschränkungen für die Barrierefreiheit sind vorhanden:

  • Die direkte Basisklasse eines Klassentyps muss mindestens so zugänglich sein wie der Klassentyp selbst.
  • Die expliziten Basisschnittstellen eines Schnittstellentyps müssen mindestens so barrierefrei sein wie der Schnittstellentyp selbst.
  • Der Rückgabetyp und die Parametertypen eines Stellvertretungstyps müssen mindestens so zugänglich sein wie der Delegattyp selbst.
  • Der Typ einer Konstante muss mindestens so zugänglich sein wie die Konstante selbst.
  • Der Typ eines Felds muss mindestens so zugänglich sein wie das Feld selbst.
  • Der Rückgabetyp und die Parametertypen einer Methode müssen mindestens so zugänglich sein wie die Methode selbst.
  • Der Typ einer Eigenschaft muss mindestens so zugänglich sein wie die Eigenschaft selbst.
  • Der Typ eines Ereignisses muss mindestens so zugänglich sein wie das Ereignis selbst.
  • Der Typ und die Parametertypen eines Indexers müssen mindestens so zugänglich sein wie der Indexer selbst.
  • Der Rückgabetyp und die Parametertypen eines Operators müssen mindestens so zugänglich sein wie der Operator selbst.
  • Die Parametertypen eines Instanzkonstruktors müssen mindestens so zugänglich sein wie der Instanzkonstruktor selbst.
  • Eine Schnittstellen- oder Klassentypeinschränkung für einen Typparameter muss mindestens so barrierefrei sein wie das Element, das die Einschränkung deklariert.

Beispiel: Im folgenden Code

class A {...}
public class B: A {...}

die B Klasse führt zu einem Kompilierungszeitfehler, da A nicht mindestens so zugegriffen werden kann wie B.

Endbeispiel

Beispiel: Ebenso im folgenden Code

class A {...}

public class B
{
    A F() {...}
    internal A G() {...}
    public A H() {...}
}

die H Methode führt zu B einem Kompilierungszeitfehler, da der Rückgabetyp A nicht mindestens so barrierefrei ist wie die Methode.

Endbeispiel

7.6 Signaturen und Überladungen

Methoden, Instanzkonstruktoren, Indexer und Operatoren zeichnen sich durch ihre Signaturen aus:

  • Die Signatur einer Methode besteht aus dem Namen der Methode, der Anzahl der Typparameter und dem Typ- und Parameterübergabemodus der einzelnen Parameter, die in der Reihenfolge von links nach rechts berücksichtigt werden. Zu diesen Zwecken wird jeder Typparameter der Methode, die im Typ eines Parameters auftritt, nicht durch seinen Namen, sondern durch seine Ordnungsposition in der Typparameterliste der Methode identifiziert. Die Signatur einer Methode enthält insbesondere nicht den Rückgabetyp, Parameternamen, Typparameternamen, Typparametereinschränkungen, die params Modifizierer oder this Parametermodifizierer oder ob Parameter erforderlich oder optional sind.
  • Die Signatur eines Instanzkonstruktors besteht aus dem Typ- und Parameterübergabemodus der einzelnen Parameter, die in der Reihenfolge von links nach rechts berücksichtigt werden. Die Signatur eines Instanzkonstruktors enthält insbesondere nicht den Modifizierer, der params für den richtigen Parameter angegeben werden kann, oder ob Parameter erforderlich oder optional sind.
  • Die Signatur eines Indexers besteht aus dem Typ der einzelnen Parameter, die in der Reihenfolge von links nach rechts berücksichtigt werden. Die Signatur eines Indexers enthält insbesondere nicht den Elementtyp, noch enthält er den Modifizierer, der params für den richtigen Parameter angegeben werden kann, oder ob Parameter erforderlich oder optional sind.
  • Die Signatur eines Operators besteht aus dem Namen des Operators und dem Typ der einzelnen Parameter, die in der Reihenfolge von links nach rechts berücksichtigt werden. Die Signatur eines Operators enthält ausdrücklich nicht den Ergebnistyp.
  • Die Signatur eines Konvertierungsoperators besteht aus dem Quelltyp und dem Zieltyp. Die implizite oder explizite Klassifizierung eines Konvertierungsoperators ist nicht Teil der Signatur.
  • Zwei Signaturen derselben Elementart (Methode, Instanzkonstruktor, Indexer oder Operator) werden als die gleichen Signaturen betrachtet, wenn sie denselben Namen, die Anzahl der Typparameter, die Anzahl der Parameter und parameterübergabemodi und eine Identitätskonvertierung zwischen den Typen ihrer entsprechenden Parameter (§10.2.2.2) aufweisen.

Signaturen sind der Aktivierungsmechanismus für die Überladung von Membern in Klassen, Strukturen und Schnittstellen:

  • Durch die Überladung von Methoden kann eine Klasse, Struktur oder Schnittstelle mehrere Methoden mit demselben Namen deklarieren, vorausgesetzt, ihre Signaturen sind innerhalb dieser Klasse, Struktur oder Schnittstelle eindeutig.
  • Die Überladung von Instanzkonstruktoren ermöglicht es einer Klasse oder Struktur, mehrere Instanzkonstruktoren zu deklarieren, vorausgesetzt, ihre Signaturen sind innerhalb dieser Klasse oder Struktur eindeutig.
  • Die Überladung von Indexern ermöglicht es einer Klasse, Struktur oder Schnittstelle, mehrere Indexer zu deklarieren, vorausgesetzt, ihre Signaturen sind innerhalb dieser Klasse, Struktur oder Schnittstelle eindeutig.
  • Durch die Überladung von Operatoren kann eine Klasse oder Struktur mehrere Operatoren mit demselben Namen deklarieren, vorausgesetzt, ihre Signaturen sind innerhalb dieser Klasse oder Struktur eindeutig.

Obwohl in, outund ref Parametermodifizierer als Teil einer Signatur angesehen werden, können elemente, die in einem einzigen Typ deklariert sind, nicht in der Signatur ausschließlich von in, outund ref. Ein Kompilierungszeitfehler tritt auf, wenn zwei Member in demselben Typ mit Signaturen deklariert werden, die identisch wären, wenn alle Parameter in beiden Methoden mit out oder in Modifizierern in ref Modifizierer geändert wurden. Für andere Zwecke des Signaturabgleichs (z. B. Ausblenden oder Überschreiben) inoutwerden , und werden als Teil der Signatur betrachtet und ref nicht miteinander übereinstimmen.

Hinweis: Diese Einschränkung besteht darin, C#-Programme einfach zu übersetzen, um sie auf der Common Language Infrastructure (CLI) auszuführen, die keine Möglichkeit bietet, Methoden zu definieren, die sich ausschließlich in in, , outund ref. Endnote

Die Typen object und dynamic werden beim Vergleichen von Signaturen nicht unterschieden. Daher sind Mitglieder, die in einem einzelnen Typ deklariert sind, deren Signaturen nur durch ersetzen durch ersetzen object dynamic , nicht zulässig.

Beispiel: Das folgende Beispiel zeigt eine Reihe überladener Methodendeklarationen zusammen mit ihren Signaturen.

interface ITest
{
    void F();                   // F()
    void F(int x);              // F(int)
    void F(ref int x);          // F(ref int)
    void F(out int x);          // F(out int) error
    void F(object o);           // F(object)
    void F(dynamic d);          // error.
    void F(int x, int y);       // F(int, int)
    int F(string s);            // F(string)
    int F(int x);               // F(int) error
    void F(string[] a);         // F(string[])
    void F(params string[] a);  // F(string[]) error
    void F<S>(S s);             // F<0>(0)
    void F<T>(T t);             // F<0>(0) error
    void F<S,T>(S s);           // F<0,1>(0)
    void F<T,S>(S s);           // F<0,1>(1) ok
}

Beachten Sie, dass alle inModifizierer out(ref§15.6.2) Teil einer Signatur sind. Daher sind , F(int), F(out int) F(in int)und F(ref int) sind alle einzigartigen Signaturen. F(in int), F(out int) und F(ref int) kann jedoch nicht innerhalb derselben Schnittstelle deklariert werden, da ihre Signaturen ausschließlich von in, out, und ref. Beachten Sie außerdem, dass der Rückgabetyp und der params Modifizierer nicht Teil einer Signatur sind, sodass es nicht möglich ist, ausschließlich auf Der Grundlage des Rückgabetyps oder des Ausschlusses des params Modifizierers zu überladen. Daher führen die Deklarationen der Methoden F(int) und F(params string[]) oben identifizierten Methoden zu einem Kompilierungszeitfehler. Endbeispiel

7.7 Bereiche

7.7.1 Allgemein

Der Bereich eines Namens ist der Bereich des Programmtexts, in dem es möglich ist, auf die Entität zu verweisen, die durch den Namen ohne Qualifikation des Namens deklariert wurde. Bereiche können geschachtelt werden, und ein innerer Bereich kann die Bedeutung eines Namens aus einem äußeren Bereich neu definieren. (Dies entfernt jedoch nicht die Von §7.3 auferlegte Einschränkung, dass es innerhalb eines geschachtelten Blocks nicht möglich ist, eine lokale Variable oder lokale Konstante mit demselben Namen wie eine lokale Variable oder lokale Konstante in einem eingeschlossenen Block zu deklarieren.) Der Name aus dem äußeren Bereich wird dann in dem Bereich des Programmtexts ausgeblendet, der vom inneren Bereich abgedeckt wird, und der Zugriff auf den äußeren Namen ist nur durch Qualifizieren des Namens möglich.

  • Der Bereich eines Namespacemitglieds, das von einem namespace_member_declaration (§14.6) deklariert wurde, ohne namespace_declaration eingeschlossen ist, ist der gesamte Programmtext.

  • Der Bereich eines Namespacemitglieds, das von einer namespace_member_declaration innerhalb einer namespace_declaration deklariert wird, deren vollqualifizierter Name lautetN, ist die namespace_body jedes namespace_declaration, dessen vollqualifizierter Name N mit einem Punkt beginnt oder beginntN.

  • Der Bereich eines durch einen extern_alias_directive (§14.4) definierten Namens erstreckt sich über die using_directives, global_attributes und namespace_member_declarationseiner unmittelbar enthaltenden compilation_unit oder namespace_body. Ein extern_alias_directive trägt keine neuen Member zum zugrunde liegenden Deklarationsbereich bei. Mit anderen Worten, ein extern_alias_directive ist nicht transitiv, sondern wirkt sich vielmehr nur auf die compilation_unit oder namespace_body aus, in der es auftritt.

  • Der Bereich eines durch einen using_directive (§14.5) definierten oder importierten Namens erstreckt sich über die global_attributes und namespace_member_declarationder compilation_unit oder namespace_body, in der die using_directive auftritt. Ein using_directive kann null oder mehr Namespace- oder Typnamen innerhalb eines bestimmten compilation_unit oder namespace_body verfügbar machen, aber keine neuen Member zum zugrunde liegenden Deklarationsbereich beitragen. Mit anderen Worten, ein using_directive ist nicht transitiv, sondern betrifft nur die compilation_unit oder namespace_body , in der es auftritt.

  • Der Bereich eines Typparameters, der von einem type_parameter_list für einen class_declaration (§15.2) deklariert wird, ist die class_base, type_parameter_constraints_clauses und class_body dieses class_declaration.

    Hinweis: Im Gegensatz zu Mitgliedern einer Klasse wird dieser Bereich nicht auf abgeleitete Klassen erweitert. Endnote

  • Der Bereich eines Typparameters, der von einem type_parameter_list für einen struct_declaration (§16.2) deklariert wird, ist die struct_interfaces, type_parameter_constraints_clauseund struct_body dieses struct_declaration.

  • Der Bereich eines Typparameters, der von einem type_parameter_list für eine interface_declaration (§18.2) deklariert wird, ist die interface_base, type_parameter_constraints_clauseund interface_body dieses interface_declaration.

  • Der Bereich eines Typparameters, der von einem type_parameter_list für einen delegate_declaration (§20.2) deklariert wird, ist die return_type, parameter_list und type_parameter_constraints_clausedieser delegate_declaration.

  • Der Bereich eines Typparameters, der von einem type_parameter_list für eine method_declaration (§15.6.1) deklariert wird, ist die method_declaration.

  • Der Von einem class_member_declaration (§15.3.1) deklarierte Gültigkeitsbereich eines Mitglieds ist die class_body , in der die Deklaration erfolgt. Darüber hinaus erstreckt sich der Bereich eines Klassenmememes auf die class_body der abgeleiteten Klassen, die in der Barrierefreiheitsdomäne (§7.5.3) des Mitglieds enthalten sind.

  • Der Von einem struct_member_declaration (§16.3) deklarierte Umfang eines Mitglieds ist die struct_body , in der die Deklaration erfolgt.

  • Der Von einem enum_member_declaration (§19.4) deklarierte Gültigkeitsbereich eines Mitglieds ist die enum_body , in der die Deklaration erfolgt.

  • Der Bereich eines in einem method_declaration (§15.6) deklarierten Parameters ist die method_body oder ref_method_body dieses method_declaration.

  • Der Bereich eines in einem indexer_declaration (§15.9) deklarierten Parameters ist die indexer_body dieses indexer_declaration.

  • Der Bereich eines in einem operator_declaration (§15.10) deklarierten Parameters ist die operator_body dieses operator_declaration.

  • Der Bereich eines in einem constructor_declaration (§15.11) deklarierten Parameters ist der constructor_initializer und Block dieses constructor_declaration.

  • Der Bereich eines in einem lambda_expression (§12.19) deklarierten Parameters ist die lambda_expression_body dieses lambda_expression.

  • Der Bereich eines in einem anonymous_method_expression (§12.19) deklarierten Parameters ist der Block dieses anonymous_method_expression.

  • Der Bereich einer in einem labeled_statement (§13.5) deklarierten Bezeichnung ist der Block , in dem die Deklaration auftritt.

  • Der Bereich einer lokalen Variablen, die in einem local_variable_declaration (§13.6.2) deklariert ist, ist der Block , in dem die Deklaration auftritt.

  • Der Bereich einer lokalen Variablen, die in einer switch_block einer switch Anweisung (§13.8.3) deklariert ist, ist die switch_block.

  • Der Bereich einer lokalen Variablen, die in einer for_initializer einer for Anweisung (§13.9.4) deklariert ist, ist die for_initializer, for_condition, for_iterator und embedded_statement der for Anweisung.

  • Der Bereich einer lokalen Konstanten, die in einem local_constant_declaration (§13.6.3) deklariert ist, ist der Block , in dem die Deklaration auftritt. Es handelt sich um einen Kompilierungsfehler, um auf eine lokale Konstante in einer Textposition zu verweisen, die dem constant_declarator vorausgeht.

  • Der Bereich einer Variablen, die als Teil einer foreach_statement, using_statement, lock_statement oder query_expression deklariert wird, wird durch die Erweiterung des angegebenen Konstrukts bestimmt.

Innerhalb des Bereichs eines Namespaces, einer Klasse, einer Struktur oder eines Enumerationselements kann auf das Element an einer Textposition verwiesen werden, die der Deklaration des Elements vorausgeht.

Beispiel:

class A
{
    void F()
    {
        i = 1;
    }

    int i = 0;
}

Hier ist es gültig F , um darauf zu verweisen i , bevor sie deklariert wird.

Endbeispiel

Innerhalb des Bereichs einer lokalen Variablen ist es ein Kompilierungszeitfehler, um auf die lokale Variable in einer Textposition zu verweisen, die dem Deklarator vorausgeht.

Beispiel:

class A
{
    int i = 0;

    void F()
    {
        i = 1;                // Error, use precedes declaration
        int i;
        i = 2;
    }

    void G()
    {
        int j = (j = 1);     // Valid
    }

    void H()
    {
        int a = 1, b = ++a; // Valid
    }
}

In der F obigen Methode bezieht sich die erste Zuordnung i ausdrücklich nicht auf das feld, das im äußeren Bereich deklariert ist. Stattdessen bezieht es sich auf die lokale Variable und führt zu einem Kompilierungszeitfehler, da sie der Deklaration der Variablen textual vorausgeht. In der G Methode ist die Verwendung j im Initialisierer für die Deklaration j gültig, da die Verwendung dem Deklarator nicht vorausgeht. In der H Methode verweist ein nachfolgendes Deklarator ordnungsgemäß auf eine lokale Variable, die in einem früheren Deklarator innerhalb desselben local_variable_declaration deklariert wurde.

Endbeispiel

Hinweis: Die Bereichsregeln für lokale Variablen und lokale Konstanten sind so konzipiert, dass die Bedeutung eines in einem Ausdruckskontext verwendeten Namens immer gleich innerhalb eines Blocks ist. Wenn der Bereich einer lokalen Variablen nur von der Deklaration bis zum Ende des Blocks erweitert werden soll, würde die erste Zuordnung der Instanzvariablen zugewiesen, und die zweite Zuordnung würde der lokalen Variablen zugewiesen, was möglicherweise zu Kompilierungsfehlern führt, wenn die Anweisungen des Blocks später neu angeordnet wurden.)

Die Bedeutung eines Namens innerhalb eines Blocks kann sich je nach Kontext unterscheiden, in dem der Name verwendet wird. Im Beispiel

class A {}

class Test
{
    static void Main()
    {
        string A = "hello, world";
        string s = A;                      // expression context
        Type t = typeof(A);                // type context
        Console.WriteLine(s);              // writes "hello, world"
        Console.WriteLine(t);              // writes "A"
    }
}

Der Name A wird in einem Ausdruckskontext verwendet, um auf die lokale Variable A und in einem Typkontext zu verweisen, um auf die Klasse Azu verweisen.

Endnote

7.7.2 Name ausblenden

7.7.2.1 Allgemein

Der Bereich einer Entität umfasst in der Regel mehr Programmtext als der Deklarationsbereich der Entität. Insbesondere kann der Umfang einer Entität Deklarationen enthalten, die neue Deklarationsräume mit Entitäten mit demselben Namen einführen. Solche Deklarationen führen dazu, dass die ursprüngliche Entität ausgeblendet wird. Umgekehrt wird eine Entität als sichtbar bezeichnet, wenn sie nicht ausgeblendet ist.

Das Ausblenden von Namen tritt auf, wenn Bereiche sich durch Schachtelung überlappen und Bereiche sich durch Vererbung überlappen. Die Merkmale der beiden Arten des Ausblendens werden in den folgenden Unterclauses beschrieben.

7.7.2.2 Ausblenden durch Schachtelung

Das Ausblenden von Namen durch Schachtelung kann als Ergebnis der Schachtelung von Namespaces oder Typen innerhalb von Namespaces als Ergebnis von Schachtelungstypen innerhalb von Klassen oder Strukturen als Ergebnis einer lokalen Funktion oder einer Lambda-Funktion und als Ergebnis von Parameter-, lokalen Variablen- und lokalen Konstantendeklarationen auftreten.

Beispiel: Im folgenden Code

class A
{
    int i = 0;
    void F()
    {
        int i = 1;

        void M1()
        {
            float i = 1.0f;
            Func<double, double> doubler = (double i) => i * 2.0;
        }
    }

    void G()
    {
        i = 1;
    }
}

innerhalb der F Methode wird die Instanzvariable i durch die lokale Variable iausgeblendet, i aber innerhalb der G Methode bezieht sich immer noch auf die Instanzvariable. Innerhalb der lokalen Funktion blendet die float i direkt äußere iFunktion M1 aus. Der Lambda-Parameter i blendet den float i Lambda-Textkörper aus.

Endbeispiel

Wenn ein Name in einem inneren Bereich einen Namen in einem äußeren Bereich ausblendet, blendet er alle überladenen Vorkommen dieses Namens aus.

Beispiel: Im folgenden Code

class Outer
{
    static void F(int i) {}
    static void F(string s) {}

    class Inner
    {
        static void F(long l) {}

        void G()
        {
            F(1); // Invokes Outer.Inner.F
            F("Hello"); // Error
        }
    }
}

Der Aufruf ruft die F deklarierte Inner Aufschrift F(1) auf, da alle äußeren Vorkommen der inneren F Deklaration ausgeblendet sind. Aus demselben Grund führt der Aufruf F("Hello") zu einem Kompilierungszeitfehler.

Endbeispiel

7.7.2.3 Ausblenden durch Vererbung

Der Name, der durch Vererbung ausgeblendet wird, tritt auf, wenn Klassen oder Strukturen namen neu definieren, die von Basisklassen geerbt wurden. Diese Art des Ausblendens von Namen hat eine der folgenden Formen:

  • Eine Konstante, ein Feld, eine Eigenschaft, ein Ereignis oder ein Typ, die in einer Klasse oder Struktur eingeführt wurden, blendet alle Basisklassenmember mit demselben Namen aus.
  • Eine in einer Klasse oder Struktur eingeführte Methode blendet alle Nicht-Methoden-Basisklassenmember mit demselben Namen und alle Basisklassenmethoden mit derselben Signatur (§7.6) aus.
  • Ein in einer Klasse oder Struktur eingeführter Indexer blendet alle Basisklassenindexer mit derselben Signatur (§7.6) aus.

Die Regeln für Operatordeklarationen (§15.10) ermöglichen es einer abgeleiteten Klasse, einen Operator mit derselben Signatur wie ein Operator in einer Basisklasse zu deklarieren. So blenden sich Operatoren niemals gegenseitig aus.

Im Gegensatz zum Ausblenden eines Namens aus einem äußeren Bereich führt das Ausblenden eines sichtbaren Namens aus einem geerbten Bereich dazu, dass eine Warnung gemeldet wird.

Beispiel: Im folgenden Code

class Base
{
    public void F() {}
}

class Derived : Base
{
    public void F() {} // Warning, hiding an inherited name
}

die Indeklaration F Derived bewirkt, dass eine Warnung gemeldet wird. Das Ausblenden eines geerbten Namens ist ausdrücklich kein Fehler, da dies eine separate Entwicklung von Basisklassen ausschließt. Die oben genannte Situation könnte beispielsweise auftreten, weil eine spätere Version einer Base Methode eingeführt F wurde, die in einer früheren Version der Klasse nicht vorhanden war.

Endbeispiel

Die Warnung, die durch das Ausblenden eines geerbten Namens verursacht wird, kann durch die Verwendung des new Modifizierers eliminiert werden:

Beispiel:

class Base
{
    public void F() {}
}

class Derived : Base
{
    public new void F() {}
}

Der new Modifizierer gibt an, dass das F In Derived -Element "neu" ist und dass es tatsächlich beabsichtigt ist, das geerbte Element auszublenden.

Endbeispiel

Eine Deklaration eines neuen Mitglieds blendet ein geerbtes Element nur innerhalb des Gültigkeitsbereichs des neuen Elements aus.

Beispiel:

class Base
{
    public static void F() {}
}

class Derived : Base
{
    private new static void F() {} // Hides Base.F in Derived only
}

class MoreDerived : Derived
{
    static void G()
    {
        F();                       // Invokes Base.F
    }
}

Im obigen Beispiel blendet die Deklaration von F "In Derived " das F geerbte Objekt aus Base, aber da das neue F In Derived über einen privaten Zugriff verfügt, erstreckt sich der Bereich nicht auf MoreDerived. Daher ist der Aufruf F() MoreDerived.G gültig und wird aufgerufen Base.F.

Endbeispiel

7.8 Namespace- und Typnamen

7.8.1 Allgemein

Für mehrere Kontexte in einem C#-Programm muss eine namespace_name oder ein type_name angegeben werden.

namespace_name
    : namespace_or_type_name
    ;

type_name
    : namespace_or_type_name
    ;
    
namespace_or_type_name
    : identifier type_argument_list?
    | namespace_or_type_name '.' identifier type_argument_list?
    | qualified_alias_member
    ;

Ein namespace_name ist eine namespace_or_type_name, die sich auf einen Namespace bezieht.

Die namespace_or_type_name eines namespace_name beziehen sich auf einen Namespace, oder andernfalls tritt ein Kompilierungsfehler auf. Es können keine Typargumente (§8.4.2) in einem namespace_name vorhanden sein (nur Typen können Typargumente haben).

Ein type_name ist eine namespace_or_type_name, die sich auf einen Typ bezieht. Die namespace_or_type_name eines type_name beziehen sich nach der unten beschriebenen Lösung auf einen Typ, oder andernfalls tritt ein Kompilierungsfehler auf.

Ist die namespace_or_type_name ein qualified_alias_member dessen Bedeutung wie in §14.8.1 beschrieben. Andernfalls weist ein namespace_or_type_name eine von vier Formen auf:

  • I
  • I<A₁, ..., Aₓ>
  • N.I
  • N.I<A₁, ..., Aₓ>

dabei I handelt es sich um einen einzelnen Bezeichner, N eine namespace_or_type_name und <A₁, ..., Aₓ> eine optionale type_argument_list. Wenn kein type_argument_list angegeben ist, sollten Sie als Null betrachten x .

Die Bedeutung eines namespace_or_type_name wird wie folgt bestimmt:

  • Ist die namespace_or_type_name ein qualified_alias_member, ist die Bedeutung wie in §14.8.1 angegeben.
  • Andernfalls ist die namespace_or_type_name der Form I oder des FormularsI<A₁, ..., Aₓ>:
    • Wenn x null ist und die namespace_or_type_name in einer generischen Methodendeklaration (§15.6) angezeigt wird, aber außerhalb der Attribute des Methodenheaders, und wenn diese Deklaration einen Typparameter (§15.2.3) mit Namen Ienthält, bezieht sich die namespace_or_type_name auf diesen Typparameter.
    • Andernfalls wird die namespace_or_type_name in einer Typdeklaration angezeigt, und zwar für jeden Instanztyp T (§15.3.2), beginnend mit dem Instanztyp dieser Typdeklaration und dem Instanztyp jeder eingeschlossenen Klasse oder Strukturdeklaration (falls vorhanden):
      • Wenn x null ist und die Deklaration eines T Typparameters mit Dem Namen Ienthält, bezieht sich die namespace_or_type_name auf diesen Typparameter.
      • Andernfalls, wenn die namespace_or_type_name im Textkörper der Typdeklaration angezeigt wird und T oder eines seiner Basistypen einen geschachtelten barrierefreien Typ mit Namen I und x Typparametern enthält, bezieht sich die namespace_or_type_name auf diesen Typ, der mit den angegebenen Typargumenten erstellt wurde. Wenn mehr als ein solcher Typ vorhanden ist, wird der innerhalb des abgeleiteten Typs deklarierte Typ ausgewählt.

      Hinweis: Elemente ohne Typ (Konstanten, Felder, Methoden, Eigenschaften, Indexer, Operatoren, Instanzkonstruktoren, Finalizer und statische Konstruktoren) und Typmember mit einer anderen Anzahl von Typparametern werden ignoriert, wenn die Bedeutung der namespace_or_type_name bestimmt wird. Endnote

    • Andernfalls werden für jeden Namespace N, beginnend mit dem Namespace, in dem die namespace_or_type_name auftritt, mit jedem eingeschlossenen Namespace (falls vorhanden) fortfahren und mit dem globalen Namespace enden, die folgenden Schritte ausgewertet, bis sich eine Entität befindet:
      • Wenn x null ist und I der Name eines Namespaces in N, dann:
        • Wenn der Speicherort, an dem die namespace_or_type_name auftritt, von einer Namespacedeklaration N eingeschlossen wird und die Namespacedeklaration eine extern_alias_directive oder using_alias_directive enthält, die den Namen I einem Namespace oder Typ zuordnet, ist die namespace_or_type_name mehrdeutig und ein Kompilierungszeitfehler tritt auf.
        • Andernfalls bezieht sich die namespace_or_type_name auf den in I N.
      • Andernfalls, wenn N ein barrierefreier Typ mit Namen I - und x Typparametern enthält, dann:
        • Wenn x null ist und der Speicherort, an dem die namespace_or_type_name auftritt, von einer Namespacedeklaration N eingeschlossen wird und die Namespacedeklaration eine extern_alias_directive oder using_alias_directive enthält, die den Namen I einem Namespace oder Typ zuordnet, dann ist die namespace_or_type_name mehrdeutig und ein Kompilierungszeitfehler tritt auf.
        • Andernfalls bezieht sich die namespace_or_type_name auf den Typ, der mit den angegebenen Typargumenten erstellt wurde.
      • Andernfalls wird der Speicherort, an dem die namespace_or_type_name auftritt, durch eine Namespacedeklaration eingeschlossen für N:
        • Wenn x null ist und die Namespacedeklaration eine extern_alias_directive oder using_alias_directive enthält, die den Namen I einem importierten Namespace oder Typ zuordnet, bezieht sich die namespace_or_type_name auf diesen Namespace oder diesen Typ.
        • Wenn andernfalls die durch die using_namespace_directives der Namespacedeklaration importierten Namespaces genau einen Typ mit Namen I - und x Typparametern enthalten, bezieht sich die namespace_or_type_name auf diesen Typ, der mit den angegebenen Typargumenten erstellt wurde.
        • Andernfalls, wenn die durch die using_namespace_directive s der Namespacedeklaration importierten Namespaces mehr als einen Typ mit NamenI- und x Typparametern enthalten, ist die namespace_or_type_name mehrdeutig und ein Fehlerauftritt.
    • Andernfalls ist die namespace_or_type_name nicht definiert, und ein Kompilierungszeitfehler tritt auf.
  • Andernfalls ist die namespace_or_type_name der Form N.I oder des FormularsN.I<A₁, ..., Aₓ>. N wird zuerst als namespace_or_type_name aufgelöst. Wenn die Auflösung N nicht erfolgreich ist, tritt ein Kompilierungszeitfehler auf. N.I Andernfalls oder N.I<A₁, ..., Aₓ> wird wie folgt aufgelöst:
    • Wenn x null ist und sich auf einen Namespace bezieht und N N einen geschachtelten Namespace mit Dem Namen Ienthält, bezieht sich die namespace_or_type_name auf diesen geschachtelten Namespace.
    • Andernfalls bezieht sich die namespace_or_type_name auf diesen Typ, N der mit den angegebenen Typargumenten erstellt wurde, wenn er auf einen Namespace verweist und N einen barrierefreien Typ mit NamenI- und x Typparametern enthält.
    • N Andernfalls bezieht sich die namespace_or_type_name auf eine (möglicherweise konstruierte) Klasse oder einen Strukturtyp und N eine seiner Basisklassen mit einem geschachtelten barrierefreien Typ mit Namen I und x Typparametern, dann bezieht sich die namespace_or_type_name auf diesen Typ, der mit den angegebenen Typargumenten erstellt wurde. Wenn mehr als ein solcher Typ vorhanden ist, wird der innerhalb des abgeleiteten Typs deklarierte Typ ausgewählt.

      Hinweis: Wenn die Bedeutung N.I der Auflösung der Basisklassenspezifikation N bestimmt wird, gilt object die direkte Basisklasse als N (§15.2.4.2). Endnote

    • N.I Andernfalls ist ein ungültiger namespace_or_type_name, und ein Kompilierungszeitfehler tritt auf.

Ein namespace_or_type_name darf nur dann auf eine statische Klasse (§15.2.2.4) verweisen, wenn

  • Die namespace_or_type_name ist die T in einer namespace_or_type_name des Formulars T.I oder
  • Die namespace_or_type_name ist die T in einem typeof_expression (§12.8.17) des Formulars. typeof(T)

7.8.2 Nicht qualifizierte Namen

Jede Namespacedeklaration und Typdeklaration weist einen nicht qualifizierten Namen auf:

  • Bei einer Namespacedeklaration ist der nicht qualifizierte Name die in der Deklaration angegebene qualified_identifier .
  • Bei einer Typdeklaration ohne type_parameter_list ist der nicht qualifizierte Name der in der Deklaration angegebene Bezeichner .
  • Bei einer Typdeklaration mit K-Typparametern ist der nicht qualifizierte Name der in der Deklaration angegebene Bezeichner , gefolgt von der generic_dimension_specifier (§12.8.17) für K-Typparameter.

7.8.3 Vollqualifizierte Namen

Jeder Namespace und jede Typdeklaration hat einen vollqualifizierten Namen, der den Namespace oder die Typdeklaration unter allen anderen innerhalb des Programms eindeutig identifiziert. Der vollqualifizierte Name eines Namespaces oder einer Typdeklaration mit nicht qualifiziertem Namen N wird wie folgt bestimmt:

  • Wenn N es sich um ein Mitglied des globalen Namespace handelt, lautet Nder vollqualifizierte Name .
  • Andernfalls lautet S.Nder vollqualifizierte Name , wobei S der vollqualifizierte Name des Namespaces oder der Typdeklaration, in der N deklariert wird.

Mit anderen Worten: Der vollqualifizierte Name N ist der vollständige hierarchische Pfad von Bezeichnern und generic_dimension_specifier, die ab dem globalen Namespace führen N. Da jedes Mitglied eines Namespaces oder Typs einen eindeutigen Namen hat, folgt, dass der vollqualifizierte Name eines Namespaces oder einer Typdeklaration immer eindeutig ist. Es handelt sich um einen Kompilierungsfehler für denselben vollqualifizierten Namen, der auf zwei unterschiedliche Entitäten verweist. Dies gilt insbesondere für:

  • Es ist ein Fehler für eine Namespacedeklaration und eine Typdeklaration, um denselben vollqualifizierten Namen zu haben.
  • Es handelt sich um einen Fehler für zwei verschiedene Typen von Typdeklarationen, um denselben vollqualifizierten Namen zu haben (z. B. wenn sowohl eine Struktur als auch eine Klassendeklaration denselben vollqualifizierten Namen haben).
  • Es handelt sich um einen Fehler für eine Typdeklaration ohne den teilweisen Modifizierer, um denselben vollqualifizierten Namen wie eine andere Typdeklaration (§15.2.7) zu haben.

Beispiel: Das folgende Beispiel zeigt mehrere Namespace- und Typdeklarationen zusammen mit den zugehörigen vollqualifizierten Namen.

class A {}                 // A
namespace X                // X
{
    class B                // X.B
    {
        class C {}         // X.B.C
    }
    namespace Y            // X.Y
    {
        class D {}         // X.Y.D
    }
}
namespace X.Y              // X.Y
{
    class E {}             // X.Y.E
    class G<T>             // X.Y.G<>
    {           
        class H {}         // X.Y.G<>.H
    }
    class G<S,T>           // X.Y.G<,>
    {         
        class H<U> {}      // X.Y.G<,>.H<>
    }
}

Endbeispiel

7.9 Automatische Speicherverwaltung

C# verwendet die automatische Speicherverwaltung, die Entwicklern das manuelle Zuweisen und Freigeben des von Objekten belegten Speichers freigibt. Automatische Speicherverwaltungsrichtlinien werden von einem Garbage Collector implementiert. Der Lebenszyklus der Speicherverwaltung eines Objekts lautet wie folgt:

  1. Wenn das Objekt erstellt wird, wird ihm Speicher zugewiesen, der Konstruktor ausgeführt, und das Objekt wird als live betrachtet.
  2. Wenn weder auf das Objekt noch auf eines seiner Instanzfelder durch eine mögliche Fortsetzung der Ausführung zugegriffen werden kann, abgesehen von der Ausführung von Finalizern, wird das Objekt nicht mehr verwendet , und es kann zur Finalisierung berechtigt werden.

    Hinweis: Der C#-Compiler und der Garbage Collector können code analysieren, um zu bestimmen, welche Verweise auf ein Objekt in Zukunft verwendet werden können. Wenn beispielsweise eine lokale Variable, die sich im Bereich befindet, der einzige vorhandene Verweis auf ein Objekt ist, aber diese lokale Variable niemals in einer möglichen Fortsetzung der Ausführung vom aktuellen Ausführungspunkt in der Prozedur referenziert wird, behandelt der Garbage Collector das Objekt möglicherweise (aber nicht erforderlich), da es nicht mehr verwendet wird. Endnote

  3. Sobald das Objekt zur Fertigstellung berechtigt ist, wird zu einem späteren Zeitpunkt, zu dem der Finalizer (§15.13) (falls vorhanden) für das Objekt ausgeführt wird, nicht angegeben. Unter normalen Umständen wird der Finalizer für das Objekt nur einmal ausgeführt, obwohl implementierungsdefinierte APIs dieses Verhalten möglicherweise außer Kraft setzen können.
  4. Sobald der Finalizer für ein Objekt ausgeführt wird, kann, wenn weder auf das Objekt noch auf eines seiner Instanzfelder durch eine mögliche Fortsetzung der Ausführung zugegriffen werden kann, einschließlich der Ausführung von Finalizern, das Objekt als nicht zugänglich angesehen wird und das Objekt für die Auflistung berechtigt wird.

    Hinweis: Ein Objekt, auf das zuvor nicht zugegriffen werden konnte, kann aufgrund seines Finalizers wieder zugänglich werden. Nachfolgend finden Sie ein Beispiel dafür. Endnote

  5. Schließlich gibt der Garbage Collector zu einem bestimmten Zeitpunkt, nachdem das Objekt für die Sammlung berechtigt ist, den Speicher frei, der diesem Objekt zugeordnet ist.

Der Garbage Collector verwaltet Informationen zur Objektverwendung und verwendet diese Informationen, um Speicherverwaltungsentscheidungen zu treffen, z. B. wo im Speicher ein neu erstelltes Objekt gefunden werden soll, wann ein Objekt verschoben werden soll und wann ein Objekt nicht mehr verwendet oder nicht mehr zugegriffen werden kann.

Wie andere Sprachen, die davon ausgehen, dass ein Garbage Collector vorhanden ist, ist C# so konzipiert, dass der Garbage Collector möglicherweise eine Vielzahl von Speicherverwaltungsrichtlinien implementiert. C# gibt weder eine Zeiteinschränkung innerhalb dieser Zeitspanne noch eine Reihenfolge an, in der Finalizer ausgeführt werden. Unabhängig davon, ob Finalizer als Teil der Beendigung der Anwendung ausgeführt werden, ist implementierungsdefiniert (§7.2).

Das Verhalten des Garbage Collectors kann bis zu einem gewissen Grad über statische Methoden für die Klasse System.GCgesteuert werden. Diese Klasse kann verwendet werden, um eine Auflistung anzufordern, Endizer auszuführen (oder nicht auszuführen), usw.

Beispiel: Da der Garbage Collector bei der Entscheidung, wann Objekte gesammelt und Finalizer ausgeführt werden sollen, eine konforme Implementierung eine Ausgabe erzeugen kann, die sich von dem durch den folgenden Code unterscheidet. Das Programm

class A
{
    ~A()
    {
        Console.WriteLine("Finalize instance of A");
    }
}

class B
{
    object Ref;
    public B(object o)
    {
        Ref = o;
    }

    ~B()
    {
        Console.WriteLine("Finalize instance of B");
    }
}

class Test
{
    static void Main()
    {
        B? b = new B(new A());
        b = null;
        GC.Collect();
        GC.WaitForPendingFinalizers();
    }
}

erstellt eine Instanz der Klasse A und eine Instanz der Klasse B. Diese Objekte sind für die Garbage Collection berechtigt, wenn der Wert nullder Variable b zugewiesen wird, da nach diesem Zeitpunkt kein benutzergeschriebener Code darauf zugreifen kann. Die Ausgabe kann eine der beiden sein.

Finalize instance of A
Finalize instance of B

or

Finalize instance of B
Finalize instance of A

da die Sprache keine Einschränkungen für die Reihenfolge angibt, in der Objekte garbage collection sind.

In subtilen Fällen kann die Unterscheidung zwischen "berechtigter Abschluss" und "berechtigter Sammlung" wichtig sein. Beispiel:

class A
{
    ~A()
    {
        Console.WriteLine("Finalize instance of A");
    }

    public void F()
    {
        Console.WriteLine("A.F");
        Test.RefA = this;
    }
}

class B
{
    public A? Ref;

    ~B()
    {
        Console.WriteLine("Finalize instance of B");
        Ref?.F();
    }
}

class Test
{
    public static A? RefA;
    public static B? RefB;

    static void Main()
    {
        RefB = new B();
        RefA = new A();
        RefB.Ref = RefA;
        RefB = null;
        RefA = null;
        // A and B now eligible for finalization
        GC.Collect();
        GC.WaitForPendingFinalizers();
        // B now eligible for collection, but A is not
        if (RefA != null)
        {
            Console.WriteLine("RefA is not null");
        }
    }
}

Wenn der Garbage Collector im obigen Programm den Finalizer vor A dem Finalizer Bausführen möchte, kann die Ausgabe dieses Programms wie folgt lauten:

Finalize instance of A
Finalize instance of B
A.F
RefA is not null

Beachten Sie, dass zwar die Instanz nicht A verwendet wurde und A's finalizer ausgeführt wurde, aber es ist weiterhin möglich, FMethoden von A (in diesem Fall) von einem anderen Finalizer aufgerufen zu werden. Beachten Sie außerdem, dass das Ausführen eines Finalizers dazu führen kann, dass ein Objekt erneut aus dem Hauptzeilenprogramm verwendet werden kann. In diesem Fall verursachte die Ausführung des BFinalizers eine Instanz der A zuvor nicht verwendeten, um über die Livereferenz Test.RefAzugänglich zu werden. Nach dem Aufruf WaitForPendingFinalizersist die Instanz der B Sammlung berechtigt, aber die Instanz ist A nicht, aufgrund des Verweises Test.RefA.

Endbeispiel

7.10 Ausführungsreihenfolge

Die Ausführung eines C#-Programms wird so fortgesetzt, dass die Nebenwirkungen jedes ausgeführten Threads an kritischen Ausführungspunkten beibehalten werden. Ein Nebeneffekt wird als Lese- oder Schreibzugriff eines veränderlichen Felds definiert, ein Schreibvorgang in eine nicht veränderliche Variable, ein Schreibvorgang in eine externe Ressource und das Auslösen einer Ausnahme. Die kritischen Ausführungspunkte, an denen die Reihenfolge dieser Nebenwirkungen erhalten bleiben soll, sind Verweise auf veränderliche Felder (§15.5.4), lock Anweisungen (§13.13) und Threaderstellung und Beendigung. Die Ausführungsumgebung kann die Reihenfolge der Ausführung eines C#-Programms ändern, vorbehaltlich der folgenden Einschränkungen:

  • Die Datenabhängigkeit wird innerhalb eines Ausführungsthreads beibehalten. Das heißt, der Wert jeder Variablen wird berechnet, als ob alle Anweisungen im Thread in der ursprünglichen Programmreihenfolge ausgeführt wurden.
  • Initialisierungsregeln bleiben erhalten (§15.5.5, §15.5.6).
  • Die Reihenfolge der Nebenwirkungen wird im Hinblick auf veränderliche Lese- und Schreibvorgänge beibehalten (§15.5.4). Darüber hinaus muss die Ausführungsumgebung keinen Teil eines Ausdrucks auswerten, wenn er ableiten kann, dass der Wert dieses Ausdrucks nicht verwendet wird und dass keine erforderlichen Nebenwirkungen erzeugt werden (einschließlich aller, die durch Aufrufen einer Methode oder zugreifen auf ein veränderliches Feld verursacht werden). Wenn die Programmausführung durch ein asynchrones Ereignis (z. B. eine Ausnahme, die von einem anderen Thread ausgelöst wird) unterbrochen wird, ist nicht sichergestellt, dass die feststellbaren Nebenwirkungen in der ursprünglichen Programmreihenfolge sichtbar sind.