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 aufweistSystem.Threading.Tasks.Task
oderSystem.Threading.Tasks.Task<int>
. - Der Rückgabetyp muss
void
,int
, ,System.Threading.Tasks.Task
oderSystem.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. Eineasync void
Methode oder eineasync
Methode, die einen anderen awaitablen Typ zurückgibt, zValueTask
. B. oderValueTask<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 lautetSystem.Threading.Tasks.Task
, ist der Rückgabetyp der synthetisierten Methodevoid
- Wenn der Rückgabetyp der
Main
Methode lautetSystem.Threading.Tasks.Task<int>
, ist der Rückgabetyp der synthetisierten Methodeint
Die Ausführung der synthetisierten Methode wird wie folgt fortgesetzt:
- Die synthetisierte Methode ruft die
Main
Methode auf und übergibt denstring[]
Parameterwert als Argument, wenn dieMain
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 aufruft
GetAwaiter().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 vonSystem.Threading.Tasks.Task<int>
, wenn die Aufgabe erfolgreich abgeschlossen wird, wird derint
vonGetResult()
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 null
oder 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 0
der 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 Variablez
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
undMegacorp.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 imG
folgenden Beispiel zu einem Kompilierungszeitfehler, da der Namei
im äußeren Block deklariert und nicht im inneren Block neu deklariert werden kann. Die Methoden undI
MethodenH
sind jedoch gültig, da die beideni
Blö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 object
geerbt 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 Klasseobject
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.Array
geerbt 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 Bedeutungpublic
ist "Zugriff nicht beschränkt". - Geschützt, das durch Einschließen eines
protected
Modifizierers in die Memberdeklaration ausgewählt wird. Die intuitive Bedeutungprotected
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 Bedeutunginternal
ist "Zugriff auf diese Montage beschränkt". - Geschütztes internes Element, das durch Einschließen eines
protected
Und einesinternal
Modifizierers in die Memberdeklaration ausgewählt wird. Die intuitive Bedeutungprotected 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 Bedeutungprivate 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 Bedeutungprivate
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 oderinternal
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ügeninternal
oder deklariert werden kann. Endnote - Strukturmmber können Barrierefreiheit oder
private
deklarierte Barrierefreiheit und standardmäßigprivate
deklarierte Barrierefreiheit aufweisenpublic
internal
, da Strukturen implizit versiegelt sind. Strukturmember, die in einerstruct
(d. h. nicht geerbten Struktur) eingeführt wurden, können weder über Barrierefreiheit noch über deklarierte Barrierefreiheit verfügenprotected internal
protected
private protected
.Hinweis: Ein als Mitglied einer Struktur deklarierter Typ kann Barrierefreiheit aufweisen,
internal
oderprivate
eine Barrierefreiheit deklariert werdenpublic
, während ein typ, der als Mitglied eines Namespace deklariert ist, nurpublic
über Barrierefreiheit verfügen oderinternal
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. , int
oder 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äneT
der Programmtext undP
jedes Programm, auf das verwiesen wirdP
. - Wenn die deklarierte Barrierefreiheit intern ist, ist die Barrierefreiheitsdomäne
T
T
der Programmtext vonP
.
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 P
deklariert 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 Wertpublic
hat, entspricht die Zugriffsdomäne vonM
der vonT
. - Wenn die deklarierte Barrierefreiheit
M
lautetprotected internal
, lassen Sie unsD
die Vereinigung des ProgrammtextsP
und des Programmtexts eines beliebigen Typs sein, der vonT
, der außerhalbP
deklariert wird. Die Barrierefreiheitsdomäne ist die Schnittmenge der BarrierefreiheitsdomäneM
mitD
T
. - Wenn die deklarierte Barrierefreiheit
M
lautetprivate protected
, lassen Sie unsD
die Schnittmenge des ProgrammtextsP
und des Programmtexts undT
eines beliebigen Typs sein, der vonT
. Die Barrierefreiheitsdomäne ist die Schnittmenge der BarrierefreiheitsdomäneM
mitD
T
. - Wenn die deklarierte Barrierefreiheit
M
lautetprotected
, lassen Sie unsD
die Vereinigung des ProgrammtextsT
und des Programmtexts eines beliebigen Typs sein, der vonT
. Die Barrierefreiheitsdomäne ist die Schnittmenge der BarrierefreiheitsdomäneM
mitD
T
. - Wenn die deklarierte Zugriffsart von
M
den Wertinternal
hat, entspricht die Zugriffsdomäne vonM
der Schnittmenge zwischen der Zugriffsdomäne vonT
und dem Programmtext vonP
. - Wenn die deklarierte Zugriffsart von
M
den Wertprivate
hat, entspricht die Zugriffsdomäne vonM
dem Programmtext vonT
.
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
istpublic
der Zugriff zulässig.- Andernfalls ist
protected internal
der Zugriff zulässig,M
wenn er innerhalb des Programms auftritt, in demM
deklariert wird, oder wenn er innerhalb einer von der Klasse abgeleiteten Klasse auftritt, inM
der sie deklariert wird und über den abgeleiteten Klassentyp (§7.5.4) erfolgt.- Andernfalls ist
protected
der Zugriff zulässig,M
wenn er innerhalb der klasse auftritt, inM
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
internal
der Zugriff zulässig,M
wenn er innerhalb des Programms auftritt, in demM
deklariert wird.- Andernfalls ist
private
der Zugriff zulässig,M
wenn er innerhalb des Typs auftritt, in demM
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
undA.X
ist unbegrenzt.- Die Barrierefreiheitsdomäne von
A.Y
,B
,B.X
,B.C
B.Y
,B.C.X
undB.C.Y
ist der Programmtext des enthaltenden Programms.- Die Barrierefreiheitsdomäne ist
A.Z
der Programmtext vonA
.- Die Barrierefreiheitsdomäne von
B.Z
undB.D
ist der Programmtext vonB
, einschließlich des Programmtexts vonB.C
undB.D
.- Die Barrierefreiheitsdomäne ist
B.C.Z
der Programmtext vonB.C
.- Die Barrierefreiheitsdomäne von
B.D.X
undB.D.Y
ist der Programmtext vonB
, einschließlich des Programmtexts vonB.C
undB.D
.- Die Barrierefreiheitsdomäne ist
B.D.Z
der Programmtext vonB.D
. Wie das Beispiel zeigt, ist die Barrierefreiheitsdomäne eines Elements nie größer als die eines enthaltenden Typs. Auch wenn alleX
Mitglieder die Barrierefreiheit öffentlich deklariert haben, verfügen alle AberA.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 Mitgliedx
von derA
Klasse. Da das Mitglied privat ist, ist es nur innerhalb der class_body vonA
. Daher ist der Zugriff aufb.x
dieA.F
Methode erfolgreich, schlägt jedoch in derB.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 D
kann der Zugriff M
auf eine der folgenden Formen erfolgen:
- Eine nicht qualifizierte type_name oder primary_expression des Formulars
M
. - Eine primary_expression des Formulars
E.M
, vorausgesetzt, der Typ istE
T
oder eine Klasse, die vonT
der Klasse abgeleitet ist, wobeiT
die KlasseD
oder ein Klassentyp erstellt wirdD
. - Eine primary_expression des Formulars
base.M
. - Eine primary_expression des Formulars
base[
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, überx
Instanzen von beidenA
undB
, da in beiden Fällen der Zugriff über eine Instanz vonA
oder eine Klasse, die vonA
. InnerhalbB
ist es jedoch nicht möglichx
, über eine Instanz vonA
, daA
nicht vonB
.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
memberC<int>.x
inD
ist gültig, obwohl die KlasseD
vonC<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 M
ist. 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, daA
nicht mindestens so zugegriffen werden kann wieB
.Endbeispiel
Beispiel: Ebenso im folgenden Code
class A {...} public class B { A F() {...} internal A G() {...} public A H() {...} }
die
H
Methode führt zuB
einem Kompilierungszeitfehler, da der RückgabetypA
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 oderthis
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
, out
und 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
, out
und 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) in
out
werden , 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
, ,out
undref
. 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
in
Modifiziererout
(ref
§15.6.2) Teil einer Signatur sind. Daher sind ,F(int)
,F(out int)
F(in int)
undF(ref int)
sind alle einzigartigen Signaturen.F(in int)
,F(out int)
undF(ref int)
kann jedoch nicht innerhalb derselben Schnittstelle deklariert werden, da ihre Signaturen ausschließlich vonin
,out
, undref
. Beachten Sie außerdem, dass der Rückgabetyp und derparams
Modifizierer nicht Teil einer Signatur sind, sodass es nicht möglich ist, ausschließlich auf Der Grundlage des Rückgabetyps oder des Ausschlusses desparams
Modifizierers zu überladen. Daher führen die Deklarationen der MethodenF(int)
undF(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 lautet
N
, ist die namespace_body jedes namespace_declaration, dessen vollqualifizierter NameN
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 derfor
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 verweiseni
, 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 Zuordnungi
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 derG
Methode ist die Verwendungj
im Initialisierer für die Deklarationj
gültig, da die Verwendung dem Deklarator nicht vorausgeht. In derH
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 VariableA
und in einem Typkontext zu verweisen, um auf die KlasseA
zu 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 Instanzvariablei
durch die lokale Variablei
ausgeblendet,i
aber innerhalb derG
Methode bezieht sich immer noch auf die Instanzvariable. Innerhalb der lokalen Funktion blendet diefloat i
direkt äußerei
FunktionM1
aus. Der Lambda-Parameteri
blendet denfloat 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
deklarierteInner
AufschriftF(1)
auf, da alle äußeren Vorkommen der innerenF
Deklaration ausgeblendet sind. Aus demselben Grund führt der AufrufF("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 einerBase
Methode eingeführtF
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 dasF
InDerived
-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
"InDerived
" dasF
geerbte Objekt ausBase
, aber da das neueF
InDerived
über einen privaten Zugriff verfügt, erstreckt sich der Bereich nicht aufMoreDerived
. Daher ist der AufrufF()
MoreDerived.G
gültig und wird aufgerufenBase.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 NamenI
enthä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 einesT
Typparameters mit Dem NamenI
enthä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 NamenI
undx
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
- Wenn
- 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 undI
der Name eines Namespaces inN
, 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 NamenI
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
.
- Wenn der Speicherort, an dem die namespace_or_type_name auftritt, von einer Namespacedeklaration
- Andernfalls, wenn
N
ein barrierefreier Typ mit NamenI
- undx
Typparametern enthält, dann:- Wenn
x
null ist und der Speicherort, an dem die namespace_or_type_name auftritt, von einer NamespacedeklarationN
eingeschlossen wird und die Namespacedeklaration eine extern_alias_directive oder using_alias_directive enthält, die den NamenI
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.
- Wenn
- 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 NamenI
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
- undx
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 Namen
I
- undx
Typparametern enthalten, ist die namespace_or_type_name mehrdeutig und ein Fehlerauftritt.
- Wenn
- Wenn
- Andernfalls ist die namespace_or_type_name nicht definiert, und ein Kompilierungszeitfehler tritt auf.
- Wenn
- 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ösungN
nicht erfolgreich ist, tritt ein Kompilierungszeitfehler auf.N.I
Andernfalls oderN.I<A₁, ..., Aₓ>
wird wie folgt aufgelöst:- Wenn
x
null ist und sich auf einen Namespace bezieht undN
N
einen geschachtelten Namespace mit Dem NamenI
enthä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 undN
einen barrierefreien Typ mit NamenI
- undx
Typparametern enthält. N
Andernfalls bezieht sich die namespace_or_type_name auf eine (möglicherweise konstruierte) Klasse oder einen Strukturtyp undN
eine seiner Basisklassen mit einem geschachtelten barrierefreien Typ mit NamenI
undx
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 BasisklassenspezifikationN
bestimmt wird, giltobject
die direkte Basisklasse alsN
(§15.2.4.2). EndnoteN.I
Andernfalls ist ein ungültiger namespace_or_type_name, und ein Kompilierungszeitfehler tritt auf.
- Wenn
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 FormularsT.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, lautetN
der vollqualifizierte Name . - Andernfalls lautet
S.N
der vollqualifizierte Name , wobeiS
der vollqualifizierte Name des Namespaces oder der Typdeklaration, in derN
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:
- Wenn das Objekt erstellt wird, wird ihm Speicher zugewiesen, der Konstruktor ausgeführt, und das Objekt wird als live betrachtet.
- 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
- 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.
- 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
- 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.GC
gesteuert 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 KlasseB
. Diese Objekte sind für die Garbage Collection berechtigt, wenn der Wertnull
der Variableb
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 FinalizerB
ausfü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 undA
's finalizer ausgeführt wurde, aber es ist weiterhin möglich,F
Methoden vonA
(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 desB
Finalizers eine Instanz derA
zuvor nicht verwendeten, um über die LivereferenzTest.RefA
zugänglich zu werden. Nach dem AufrufWaitForPendingFinalizers
ist die Instanz derB
Sammlung berechtigt, aber die Instanz istA
nicht, aufgrund des VerweisesTest.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.
ECMA C# draft specification