7 Podstawowe pojęcia
7.1 Uruchamianie aplikacji
Program można skompilować jako bibliotekę klas, która będzie używana jako część innych aplikacji lub jako aplikacja, która może być uruchamiana bezpośrednio. Mechanizm określania tego trybu kompilacji jest definiowany przez implementację i zewnętrzny dla tej specyfikacji.
Program skompilowany jako aplikacja zawiera co najmniej jedną metodę kwalifikującą się jako punkt wejścia, spełniając następujące wymagania:
- Ma ona nazwę
Main
. - Powinien to być
static
. - Nie jest to rodzaj ogólny.
- Jest on deklarowany w typie niegeneryjnym. Jeśli typ deklarujący metodę jest typem zagnieżdżonym, żaden z jego ujęć nie może być ogólny.
- Może mieć
async
modyfikator pod warunkiem, że zwracany typ metody toSystem.Threading.Tasks.Task
lubSystem.Threading.Tasks.Task<int>
. - Typ zwracany to
void
, ,int
System.Threading.Tasks.Task
lubSystem.Threading.Tasks.Task<int>
. - Nie jest to metoda częściowa (§15.6.9) bez zastosowania.
- Lista parametrów jest pusta lub ma jeden parametr wartości typu
string[]
.
Uwaga: Metody z modyfikatorem muszą mieć dokładnie jeden z
async
dwóch typów zwracanych określonych powyżej w celu zakwalifikowania się jako punkt wejścia.async void
Metoda lubasync
metoda zwracająca inny oczekiwany typ, taki jakValueTask
lubValueTask<int>
nie kwalifikuje się jako punkt wejścia. notatka końcowa
Jeśli w programie zadeklarowano więcej niż jedną metodę kwalifikującą się jako punkt wejścia, można użyć mechanizmu zewnętrznego do określenia, która metoda jest uważana za rzeczywisty punkt wejścia dla aplikacji. Jeśli metoda kwalifikująca o zwracanym typie lub int
zostanie znaleziona, każda metoda kwalifikująca o zwracanym typie void
System.Threading.Tasks.Task
lub System.Threading.Tasks.Task<int>
nie jest uznawana za metodę punktu wejścia. Jest to błąd czasu kompilacji dla programu, który ma zostać skompilowany jako aplikacja bez dokładnie jednego punktu wejścia. Program skompilowany jako biblioteka klas może zawierać metody, które kwalifikują się jako punkty wejścia aplikacji, ale wynikowa biblioteka nie ma punktu wejścia.
Zazwyczaj deklarowana dostępność (§7.5.2) metody jest określana przez modyfikatory dostępu (§15.3.6) określone w deklaracji, a podobnie zadeklarowane ułatwienia dostępu typu są określane przez modyfikatory dostępu określone w jego deklaracji. Aby dana metoda danego typu była wywoływana, zarówno typ, jak i element członkowski są dostępne. Jednak punkt wejścia aplikacji jest szczególnym przypadkiem. W szczególności środowisko wykonawcze może uzyskać dostęp do punktu wejścia aplikacji niezależnie od zadeklarowanej dostępności i niezależnie od zadeklarowanej dostępności deklaracji typu otaczającego.
Gdy metoda punktu wejścia zwraca wartość typu System.Threading.Tasks.Task
lub System.Threading.Tasks.Task<int>
, kompilator powinien zsyntetyzować synchroniczną metodę punktu wejścia, która wywołuje odpowiednią metodę Main
. Metoda syntetyzowana zawiera parametry i typy zwracane na Main
podstawie metody :
- Lista parametrów metody syntetyzowanej jest taka sama jak lista parametrów
Main
metody - Jeśli zwracany typ metody to
Main
, zwracany typSystem.Threading.Tasks.Task
metody syntetyzowanej tovoid
- Jeśli zwracany typ metody to
Main
, zwracany typSystem.Threading.Tasks.Task<int>
metody syntetyzowanej toint
Wykonanie syntetyzowanej metody przebiega w następujący sposób:
- Metoda syntetyzowana wywołuje metodę
Main
, przekazując jejstring[]
wartość parametru jako argument, jeśliMain
metoda ma taki parametr. -
Main
Jeśli metoda zgłasza wyjątek, wyjątek jest propagowany przez metodę syntetyzowaną. - W przeciwnym razie syntetyzowany punkt wejścia czeka na ukończenie zwróconego zadania, wywołując
GetAwaiter().GetResult()
zadanie przy użyciu metody wystąpienia bez parametrów lub metody rozszerzenia opisanej przez §C.3. Jeśli zadanie zakończy się niepowodzeniem,GetResult()
zgłosi wyjątek, a ten wyjątek jest propagowany przez metodę syntetyzowaną. -
Main
W przypadku metody z zwracanym typemSystem.Threading.Tasks.Task<int>
metody , jeśli zadanie zakończy się pomyślnie,int
zwracanaGetResult()
jest wartość zwrócona przez metodę syntetyzowaną.
Efektywny punkt wejścia aplikacji to punkt wejścia zadeklarowany w programie lub metoda syntetyzowana, jeśli jest wymagana zgodnie z powyższym opisem. Zwracany typ obowiązującego punktu wejścia jest zawsze void
lub int
.
Po uruchomieniu aplikacji zostanie utworzona nowa domena aplikacji. Na tym samym komputerze może istnieć kilka różnych wystąpień aplikacji, a każda z nich ma własną domenę aplikacji. Domena aplikacji umożliwia izolację aplikacji, działając jako kontener dla stanu aplikacji. Domena aplikacji działa jako kontener i granica dla typów zdefiniowanych w aplikacji i używanych przez nią bibliotek klas. Typy załadowane do jednej domeny aplikacji różnią się od tych samych typów załadowanych do innej domeny aplikacji, a wystąpienia obiektów nie są bezpośrednio współużytkowane między domenami aplikacji. Na przykład każda domena aplikacji ma własną kopię zmiennych statycznych dla tych typów, a konstruktor statyczny dla typu jest uruchamiany co najwyżej raz dla domeny aplikacji. Implementacje są bezpłatne, aby zapewnić zdefiniowane przez implementację zasady lub mechanizmy tworzenia i niszczenia domen aplikacji.
Uruchamianie aplikacji odbywa się, gdy środowisko wykonywania wywołuje skuteczny punkt wejścia aplikacji. Jeśli skuteczny punkt wejścia deklaruje parametr, podczas uruchamiania aplikacji implementacja zapewnia, że początkowa wartość tego parametru jest odwołaniem niepustym do tablicy ciągów. Ta tablica składa się z odwołań innych niż null do ciągów, nazywanych parametrami aplikacji, które otrzymują wartości zdefiniowane przez implementację przez środowisko hosta przed uruchomieniem aplikacji. Celem jest dostarczenie informacji o aplikacji określonych przed uruchomieniem aplikacji z innego miejsca w środowisku hostowanym.
Uwaga: W systemach obsługujących wiersz polecenia parametry aplikacji odpowiadają powszechnie znanym argumentom wiersza polecenia. notatka końcowa
Jeśli efektywny typ zwracany punktu wejścia to int
, wartość zwracana ze wywołania metody przez środowisko wykonawcze jest używana w zakończeniu działania aplikacji (§7.2).
Poza sytuacjami wymienionymi powyżej metody punktu wejścia zachowują się tak jak te, które nie są punktami wejścia pod każdym względem. W szczególności, jeśli punkt wejścia jest wywoływany w dowolnym innym punkcie okresu istnienia aplikacji, na przykład przez wywołanie metody regularnej, nie ma specjalnej obsługi metody: jeśli istnieje parametr, może mieć początkową wartość lub innąnull
wartość null
odwołującą się do tablicy zawierającej odwołania o wartości null. Podobnie wartość zwracana punktu wejścia nie ma specjalnego znaczenia innego niż wywołanie ze środowiska wykonawczego.
7.2 Zakończenie aplikacji
Zakończenie aplikacji zwraca kontrolę w środowisku wykonywania.
Jeśli zwracany typ efektywnego punktu wejścia aplikacji to int
i wykonanie zakończy się bez wystąpienia wyjątku, wartość int
zwróconego elementu służy jako kod stanu zakończenia aplikacji. Celem tego kodu jest umożliwienie komunikacji z powodzeniem lub niepowodzeniem w środowisku wykonywania. Jeśli zwracany typ metody efektywnego punktu wejścia to void
i wykonanie zakończy się bez wystąpienia wyjątku, kod stanu zakończenia to 0
.
Jeśli efektywna metoda punktu wejścia zakończy się z powodu wyjątku (§21.4), kod zakończenia jest zdefiniowany przez implementację. Ponadto implementacja może udostępniać alternatywne interfejsy API do określania kodu zakończenia.
Określa, czy finalizatory (§15.13) są uruchamiane w ramach kończenia działania aplikacji.
Uwaga: Implementacja programu .NET Framework podejmuje wszelkie uzasadnione wysiłki w celu wywołania finalizatorów (§15.13) dla wszystkich obiektów, które nie zostały jeszcze zebrane, chyba że takie czyszczenie zostało pominięte (na przykład przez wywołanie metody
GC.SuppressFinalize
biblioteki ). notatka końcowa
7.3 Deklaracje
Deklaracje w programie języka C# definiują elementy składowe programu. Programy języka C# są zorganizowane przy użyciu przestrzeni nazw. Są one wprowadzane przy użyciu deklaracji przestrzeni nazw (§14), które mogą zawierać deklaracje typów i zagnieżdżone deklaracje przestrzeni nazw. Deklaracje typów (§14.7) służą do definiowania klas (§15), struktur (§16), interfejsów (§18), wyliczenia (§19) i delegatów (§20). Rodzaje elementów członkowskich dozwolonych w deklaracji typu zależą od formy deklaracji typu. Na przykład deklaracje klas mogą zawierać deklaracje stałych (§15.4), pól (§15.5), metod (§15.6), właściwości (§15.7), zdarzeń (§15.8), indeksatorów (§15.9), operatory (§15.10), konstruktory wystąpień (§15.11), konstruktory statyczne (§15.12), finalizatory (§15.13) i typy zagnieżdżone (§15.3.9).
Deklaracja definiuje nazwę w przestrzeni deklaracji, do której należy deklaracja. Jest to błąd czasu kompilacji, który zawiera co najmniej dwie deklaracje, które wprowadzają elementy członkowskie o tej samej nazwie w przestrzeni deklaracji, z wyjątkiem następujących przypadków:
- Co najmniej dwie deklaracje przestrzeni nazw o tej samej nazwie są dozwolone w tej samej przestrzeni deklaracji. Takie deklaracje przestrzeni nazw są agregowane w celu utworzenia pojedynczej logicznej przestrzeni nazw i współużytkowania pojedynczej przestrzeni deklaracji.
- Deklaracje w oddzielnych programach, ale w tej samej przestrzeni deklaracji przestrzeni nazw mogą mieć taką samą nazwę.
Uwaga: jednak te deklaracje mogą wprowadzać niejednoznaczności, jeśli zostały uwzględnione w tej samej aplikacji. notatka końcowa
- Co najmniej dwie metody o tej samej nazwie, ale różne sygnatury są dozwolone w tej samej przestrzeni deklaracji (§7.6).
- Co najmniej dwie deklaracje typu o tej samej nazwie, ale różne liczby parametrów typu są dozwolone w tej samej przestrzeni deklaracji (§7.8.2).
- Co najmniej dwie deklaracje typów z modyfikatorem częściowym w tej samej przestrzeni deklaracji mogą mieć taką samą nazwę, taką samą liczbę parametrów typu i tę samą klasyfikację (klasa, struktura lub interfejs). W tym przypadku deklaracje typów przyczyniają się do pojedynczego typu i są agregowane w celu utworzenia pojedynczej przestrzeni deklaracji (§15.2.7).
- Deklaracja przestrzeni nazw i deklaracja typu w tej samej przestrzeni deklaracji może mieć taką samą nazwę, o ile deklaracja typu ma co najmniej jeden parametr typu (§7.8.2).
Istnieje kilka różnych typów spacji deklaracji, zgodnie z opisem w poniższym artykule.
- We wszystkich jednostkach kompilacji programu namespace_member_declaration s bez ujętych namespace_declaration są członkami jednej połączonej przestrzeni deklaracji nazywanej przestrzenią deklaracji globalnej.
- We wszystkich jednostkach kompilacji programu namespace_member_declarationw namespace_declaration s, które mają tę samą w pełni kwalifikowaną nazwę przestrzeni nazw, są elementami członkowskimi pojedynczej połączonej przestrzeni deklaracji.
- Każda compilation_unit i namespace_body ma spację deklaracji aliasu. Każda extern_alias_directive i using_alias_directivecompilation_unit lub namespace_body przyczynia się do przestrzeni deklaracji aliasu (§14.5.2).
- Każda nieodparta klasa, struktura lub deklaracja interfejsu tworzy nową przestrzeń deklaracji. Każda klasa częściowa, struktura lub deklaracja interfejsu przyczynia się do przestrzeni deklaracji współużytkowanej przez wszystkie pasujące części w tym samym programie (§16.2.4). Nazwy są wprowadzane do tej przestrzeni deklaracji przez class_member_declarations, struct_member_declarations, interface_member_declaration s lub type_parameters. Z wyjątkiem przeciążonych deklaracji konstruktora wystąpienia i deklaracji konstruktora statycznego, klasa lub struktura nie może zawierać deklaracji składowej o takiej samej nazwie jak klasa lub struktura. Klasa, struktura lub interfejs zezwala na deklarację przeciążonych metod i indeksatorów. Ponadto klasa lub struktura zezwala na deklarację przeciążonych konstruktorów wystąpień i operatorów. Na przykład klasa, struktura lub interfejs mogą zawierać wiele deklaracji metod o tej samej nazwie, pod warunkiem, że te deklaracje metod różnią się podpisem (§7.6). Należy pamiętać, że klasy bazowe nie przyczyniają się do przestrzeni deklaracji klasy, a interfejsy podstawowe nie przyczyniają się do przestrzeni deklaracji interfejsu. W związku z tym klasa pochodna lub interfejs może zadeklarować składową o takiej samej nazwie jak dziedziczona składowa. Mówi się, że taki element członkowski ukrywa dziedziczony element członkowski.
- Każda deklaracja delegata tworzy nową przestrzeń deklaracji. Nazwy są wprowadzane do tej przestrzeni deklaracji za pomocą parametrów (fixed_parameters i parameter_array) i type_parameters.
- Każda deklaracja wyliczenia tworzy nową przestrzeń deklaracji. Nazwy są wprowadzane do tej przestrzeni deklaracji za pośrednictwem enum_member_declarations.
- Każda deklaracja metody, deklaracja właściwości, deklaracja metody dostępu, deklaracja indeksatora, deklaracja metody indeksatora, deklaracja operatora, deklaracja konstruktora wystąpienia, funkcja anonimowa i funkcja lokalna tworzy nową przestrzeń deklaracji nazywaną przestrzenią deklaracji zmiennej lokalnej. Nazwy są wprowadzane do tej przestrzeni deklaracji za pomocą parametrów (fixed_parameters i parameter_array) i type_parameters. Metodę dostępu zestawu dla właściwości lub indeksatora wprowadza nazwę
value
jako parametr. Treść elementu członkowskiego funkcji, funkcji anonimowej lub funkcji lokalnej, jeśli istnieje, jest uważana za zagnieżdżona w przestrzeni deklaracji zmiennej lokalnej. Gdy przestrzeń deklaracji zmiennej lokalnej i zagnieżdżonej przestrzeni deklaracji zmiennej lokalnej zawierają elementy o tej samej nazwie, w zakresie zagnieżdżonej nazwy lokalnej zewnętrzna nazwa lokalna jest ukryta (§7.7.1) przez zagnieżdżonej nazwy lokalnej. - Dodatkowe przestrzenie deklaracji zmiennych lokalnych mogą wystąpić w deklaracjach składowych, funkcjach anonimowych i funkcjach lokalnych. Nazwy są wprowadzane do tych przestrzeni deklaracji za pomocą wzorców, declaration_expression s, declaration_statements i exception_specifiers. Miejsca deklaracji zmiennych lokalnych mogą być zagnieżdżone, ale jest to błąd dla przestrzeni deklaracji zmiennej lokalnej i zagnieżdżonej przestrzeni deklaracji zmiennej lokalnej zawierającej elementy o tej samej nazwie. W związku z tym w przestrzeni deklaracji zagnieżdżonej nie można zadeklarować zmiennej lokalnej, funkcji lokalnej lub stałej o takiej samej nazwie jak parametr, parametr typu, zmienna lokalna, funkcja lokalna lub stała w otaczającej przestrzeni deklaracji. Istnieje możliwość, aby dwie spacje deklaracji zawierały elementy o tej samej nazwie, o ile żadna spacja deklaracji nie zawiera drugiej. Przestrzenie deklaracji lokalnych są tworzone przez następujące konstrukcje:
- Każda variable_initializer w polu i deklaracji właściwości wprowadza własną przestrzeń deklaracji zmiennej lokalnej, która nie jest zagnieżdżona w żadnej innej przestrzeni deklaracji zmiennej lokalnej.
- Treść elementu członkowskiego funkcji, funkcji anonimowej lub funkcji lokalnej, jeśli istnieje, tworzy lokalną przestrzeń deklaracji zmiennej, która jest uważana za zagnieżdżona w przestrzeni deklaracji zmiennej lokalnej funkcji.
- Każda constructor_initializer tworzy przestrzeń deklaracji zmiennej lokalnej zagnieżdżona w deklaracji konstruktora wystąpienia. Lokalna przestrzeń deklaracji zmiennej dla treści konstruktora jest z kolei zagnieżdżona w tej przestrzeni deklaracji zmiennej lokalnej.
- Każdy blok, switch_block, specific_catch_clause, iteration_statement i using_statement tworzy zagnieżdżone miejsce deklaracji zmiennej lokalnej.
- Każda embedded_statement , która nie jest bezpośrednio częścią statement_list tworzy zagnieżdżone miejsce deklaracji zmiennej lokalnej.
- Każda switch_section tworzy zagnieżdżone miejsce deklaracji zmiennej lokalnej. Jednak zmienne zadeklarowane bezpośrednio w statement_list switch_section (ale nie w zagnieżdżonej przestrzeni deklaracji zmiennej lokalnej wewnątrz statement_list) są dodawane bezpośrednio do przestrzeni deklaracji zmiennej lokalnej otaczającej switch_block, zamiast switch_section.
- Translacja składniowa query_expression (§12.20.3) może wprowadzać co najmniej jedno wyrażenie lambda. Jako funkcje anonimowe każda z nich tworzy lokalną przestrzeń deklaracji zmiennej zgodnie z powyższym opisem.
- Każdy blok lub switch_block tworzy oddzielną przestrzeń deklaracji dla etykiet. Nazwy są wprowadzane do tej przestrzeni deklaracji za pośrednictwem labeled_statements, a nazwy są przywołyne za pośrednictwem goto_statements. Przestrzeń deklaracji etykiety bloku zawiera wszystkie zagnieżdżone bloki. W związku z tym w zagnieżdżonym bloku nie można zadeklarować etykiety o takiej samej nazwie jak etykieta w otaczającym bloku.
Uwaga: fakt, że zmienne zadeklarowane bezpośrednio w ramach switch_section są dodawane do przestrzeni deklaracji zmiennej lokalnej switch_block zamiast switch_section może prowadzić do zaskakującego kodu. W poniższym przykładzie zmienna
y
lokalna znajduje się w zakresie w sekcji przełącznika dla przypadku domyślnego, pomimo deklaracji wyświetlanej w sekcji przełącznika dla przypadku 0. Zmiennaz
lokalna nie znajduje się w zakresie w sekcji przełącznika dla przypadku domyślnego, ponieważ jest wprowadzana w przestrzeni deklaracji zmiennej lokalnej dla sekcji przełącznika, w której występuje deklaracja.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; }
notatka końcowa
Kolejność tekstowa, w której są deklarowane nazwy, nie ma zwykle znaczenia. W szczególności kolejność tekstowa nie jest znacząca dla deklaracji i użycia przestrzeni nazw, stałych, metod, właściwości, zdarzeń, indeksatorów, operatorów, konstruktorów wystąpień, finalizatorów, konstruktorów statycznych i typów. Kolejność deklaracji jest znacząca w następujący sposób:
- Kolejność deklaracji dla deklaracji pól określa kolejność wykonania ich inicjatorów (jeśli istnieją) (§15.5.6.2, §15.5.6.3).
- Zmienne lokalne należy zdefiniować przed ich zastosowaniem (§7.7).
- Kolejność deklaracji deklaracji deklaracji składowych (§19.4) jest znacząca, gdy pominięto constant_expression wartości.
Przykład: Przestrzeń deklaracji przestrzeni nazw jest "otwarta" i dwie deklaracje przestrzeni nazw o tej samej w pełni kwalifikowanej nazwie przyczyniają się do tej samej przestrzeni deklaracji. Na przykład
namespace Megacorp.Data { class Customer { ... } } namespace Megacorp.Data { class Order { ... } }
Dwa powyższe deklaracje przestrzeni nazw przyczyniają się do tej samej przestrzeni deklaracji, w tym przypadku deklarując dwie klasy z w pełni kwalifikowanymi nazwami
Megacorp.Data.Customer
iMegacorp.Data.Order
. Ponieważ dwie deklaracje przyczyniają się do tej samej przestrzeni deklaracji, spowodowałoby to błąd czasu kompilacji, jeśli każda zawierała deklarację klasy o tej samej nazwie.przykład końcowy
Uwaga: Zgodnie z powyższym opisem miejsce deklaracji bloku zawiera wszystkie zagnieżdżone bloki. W związku z tym w poniższym przykładzie metody i
F
powodują błąd czasu kompilacji,G
ponieważ nazwai
jest zadeklarowana w bloku zewnętrznym i nie można ponownie zadeklarować w bloku wewnętrznym. Metody iH
są jednak prawidłowe,I
ponieważ obai
te metody są deklarowane w oddzielnych blokach niezagnieżdżonych.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(); } } }
notatka końcowa
7.4 Członkowie
7.4.1 Ogólne
Przestrzenie nazw i typy mają elementy członkowskie.
Uwaga: Elementy członkowskie jednostki są ogólnie dostępne za pomocą kwalifikowanej nazwy, która rozpoczyna się od odwołania do jednostki, a następnie tokenu "
.
", a następnie nazwy elementu członkowskiego. notatka końcowa
Elementy członkowskie typu są deklarowane w deklaracji typu lub dziedziczone z klasy bazowej typu. Gdy typ dziedziczy z klasy bazowej, wszystkie elementy członkowskie klasy bazowej, z wyjątkiem konstruktorów wystąpień, finalizatorów i konstruktorów statycznych stają się elementami członkowskimi typu pochodnego. Zadeklarowana dostępność składowej klasy bazowej nie kontroluje, czy element członkowski jest dziedziczony — dziedziczenie rozciąga się na dowolny element członkowski, który nie jest konstruktorem wystąpienia, konstruktorem statycznym lub finalizatorem.
Uwaga: jednak dziedziczony element członkowski może nie być dostępny w typie pochodnym, na przykład ze względu na zadeklarowaną dostępność (§7.5.2). notatka końcowa
7.4.2 Elementy członkowskie przestrzeni nazw
Przestrzenie nazw i typy, które nie mają otaczającej przestrzeni nazw, są elementami członkowskimi globalnej przestrzeni nazw. Odpowiada to bezpośrednio nazw zadeklarowanych w przestrzeni deklaracji globalnej.
Przestrzenie nazw i typy zadeklarowane w przestrzeni nazw są elementami członkowskimi tej przestrzeni nazw. Odpowiada to bezpośrednio nazw zadeklarowanych w przestrzeni deklaracji przestrzeni nazw.
Przestrzenie nazw nie mają ograniczeń dostępu. Nie można zadeklarować prywatnych, chronionych ani wewnętrznych przestrzeni nazw, a nazwy przestrzeni nazw są zawsze dostępne publicznie.
7.4.3, składowe struktury
Składowe struktury są składowe zadeklarowane w struktury, a składowe dziedziczone z bezpośredniej klasy System.ValueType
bazowej struktury i pośredniej klasy object
bazowej .
Elementy członkowskie prostego typu odpowiadają bezpośrednio członkom typu struktury aliasowi typu prostego (§8.3.5).
7.4.4 Elementy członkowskie wyliczenia
Składowe wyliczenia są stałe zadeklarowane w wyliczeniem, a składowe dziedziczone z bezpośredniej klasy System.Enum
bazowej wyliczenia oraz pośrednie klasy System.ValueType
bazowe i object
.
7.4.5 Składowe klasy
Składowe klasy są składowe zadeklarowane w klasie, a składowe dziedziczone z klasy bazowej (z wyjątkiem klasy object
, która nie ma klasy bazowej). Składowe dziedziczone z klasy bazowej obejmują stałe, pola, metody, właściwości, zdarzenia, indeksatory, operatory i typy klasy bazowej, ale nie konstruktory wystąpień, finalizatory i konstruktory statyczne klasy bazowej. Składowe klasy bazowej są dziedziczone bez względu na ich dostępność.
Deklaracja klasy może zawierać deklaracje stałych, pól, metod, właściwości, zdarzeń, indeksatorów, operatorów, konstruktorów wystąpień, finalizatorów, konstruktorów statycznych i typów.
Składowe object
(§8.2.3) i string
(§8.2.5) odpowiadają bezpośrednio członkom typów klas, które aliasują.
7.4.6 Elementy członkowskie interfejsu
Elementy członkowskie interfejsu są elementami członkowskimi zadeklarowanym w interfejsie i we wszystkich podstawowych interfejsach interfejsu.
Uwaga: Składowe w klasie
object
nie są ściśle rzecz biorąc członkami żadnego interfejsu (§18.4). Jednak składowe w klasieobject
są dostępne za pośrednictwem wyszukiwania składowego w dowolnym typie interfejsu (§12.5). notatka końcowa
7.4.7 Elementy członkowskie tablicy
Składowe tablicy są składowe dziedziczone z klasy System.Array
.
7.4.8 Delegowanie członków
Delegat dziedziczy elementy członkowskie z klasy System.Delegate
. Ponadto zawiera metodę o nazwie Invoke
o tym samym typie zwrotnym i liście parametrów określonej w deklaracji (§20.2). Wywołanie tej metody zachowuje się identycznie jak wywołanie delegata (§20.6) w tym samym wystąpieniu delegata.
Implementacja może zapewnić dodatkowe elementy członkowskie za pośrednictwem dziedziczenia lub bezpośrednio w samym delegatu.
7.5 Dostęp do składowych
7.5.1 Ogólne
Deklaracje członków umożliwiają kontrolę nad dostępem do składowych. Dostępność elementu członkowskiego jest ustanawiana przez zadeklarowaną dostępność (§7.5.2) elementu członkowskiego w połączeniu z dostępnością natychmiast zawierającego typ, jeśli istnieje.
Gdy dostęp do określonego członka jest dozwolony, mówi się, że jest dostępny. Z drugiej strony, gdy dostęp do określonego członka jest niedozwolony, mówi się, że członek jest niedostępny. Dostęp do elementu członkowskiego jest dozwolony, gdy lokalizacja tekstowa, w której odbywa się dostęp, znajduje się w domenie ułatwień dostępu (§7.5.3) elementu członkowskiego.
7.5.2 Zadeklarowana dostępność
Zadeklarowane ułatwienia dostępu elementu członkowskiego mogą być jednym z następujących elementów:
- Publiczny, który jest wybierany przez dołączenie
public
modyfikatora w deklaracji elementu członkowskiego. Intuicyjne znaczenie jestpublic
"dostęp nie ograniczony". - Chroniony, który jest wybierany przez dołączenie
protected
modyfikatora w deklaracji elementu członkowskiego. Intuicyjne znaczenieprotected
jest "dostęp ograniczony do zawierającej klasy lub typów pochodzących z zawierającej klasy". - Wewnętrzny, który jest wybierany przez dołączenie
internal
modyfikatora w deklaracji elementu członkowskiego. Intuicyjne znaczenieinternal
jest "dostęp ograniczony do tego zestawu". - Chroniony element wewnętrzny, który jest wybierany przez dołączenie zarówno obiektu , jak
protected
iinternal
modyfikatora w deklaracji elementu członkowskiego. Intuicyjne znaczenieprotected internal
jest "dostępne w tym zestawie, a także typy pochodzące z klasy zawierającej". - Ochrona prywatna, która jest wybierana przez dołączenie zarówno klasy a, jak
private
iprotected
modyfikatora w deklaracji elementu członkowskiego. Intuicyjne znaczenieprivate protected
elementu jest "dostępne w tym zestawie przez klasę zawierającą i typy pochodzące z klasy zawierającej". - Prywatny, który jest wybierany przez dołączenie
private
modyfikatora w deklaracji elementu członkowskiego. Intuicyjne znaczenieprivate
jest "dostęp ograniczony do typu zawierającego".
W zależności od kontekstu, w którym odbywa się deklaracja elementu członkowskiego, dozwolone są tylko niektóre typy zadeklarowanych ułatwień dostępu. Ponadto, gdy deklaracja elementu członkowskiego nie zawiera żadnych modyfikatorów dostępu, kontekst, w którym odbywa się deklaracja, określa domyślną zadeklarowaną dostępność.
- Przestrzenie nazw niejawnie zadeklarowały
public
ułatwienia dostępu. Żadne modyfikatory dostępu nie są dozwolone w deklaracjach przestrzeni nazw. - Typy zadeklarowane bezpośrednio w jednostkach kompilacji lub przestrzeniach nazw (w przeciwieństwie do innych typów) mogą mieć
public
lubinternal
zadeklarować ułatwienia dostępu i domyślnie zadeklarowaćinternal
dostępność. - Składowe klasy mogą mieć dowolny z dozwolonych rodzajów zadeklarowanych ułatwień dostępu i domyślnie zadeklarowanych
private
ułatwień dostępu.Uwaga: typ zadeklarowany jako element członkowski klasy może mieć dowolny z dozwolonych rodzajów zadeklarowanych ułatwień dostępu, natomiast typ zadeklarowany jako element członkowski przestrzeni nazw może mieć tylko
public
lubinternal
zadeklarowane ułatwienia dostępu. notatka końcowa - Elementy członkowskie struktury mogą mieć
public
internal
, lubprivate
zadeklarowane ułatwienia dostępu i domyślnie zadeklarowaneprivate
ułatwienia dostępu, ponieważ struktury są niejawnie zapieczętowane. Elementy członkowskie struktury wprowadzone w obiekciestruct
(czyli nie dziedziczone przez strukturę) nie mogą miećprotected
,protected internal
aniprivate protected
zadeklarowanych ułatwień dostępu.Uwaga: typ zadeklarowany jako element członkowski struktury może mieć
public
wartość ,internal
lubprivate
zadeklarowaną dostępność, natomiast typ zadeklarowany jako element członkowski przestrzeni nazw może mieć tylkopublic
lubinternal
zadeklarowane ułatwienia dostępu. notatka końcowa - Elementy członkowskie interfejsu niejawnie zadeklarowały
public
ułatwienia dostępu. Modyfikatory dostępu nie są dozwolone w deklaracjach składowych interfejsu. - Elementy członkowskie wyliczenia niejawnie zadeklarowały
public
ułatwienia dostępu. W deklaracjach składowych wyliczania nie są dozwolone żadne modyfikatory dostępu.
7.5.3 Domeny ułatwień dostępu
Domena ułatwień dostępu elementu członkowskiego składa się z (prawdopodobnie rozłącznych) sekcji tekstu programu, w których jest dozwolony dostęp do elementu członkowskiego. Do celów definiowania domeny ułatwień dostępu elementu członkowskiego mówi się, że element członkowski jest najwyższego poziomu , jeśli nie jest zadeklarowany w typie, a element członkowski jest podobno zagnieżdżony, jeśli jest zadeklarowany w ramach innego typu. Ponadto tekst programu jest definiowany jako cały tekst zawarty we wszystkich jednostkach kompilacji programu, a tekst programu typu jest definiowany jako cały tekst zawarty w type_declarationtego typu (w tym, ewentualnie, typy zagnieżdżone w typie).
Domena ułatwień dostępu wstępnie zdefiniowanego typu (na przykład object
, int
lub double
) jest nieograniczona.
Domena ułatwień dostępu typu T
niezwiązanego najwyższego poziomu (§8.4.4), który jest zadeklarowany w programie P
, jest definiowana w następujący sposób:
- Jeśli zadeklarowana dostępność jest publiczna, domena ułatwień dostępu
T
to tekstT
programu i dowolny program, który odwołuje się doP
.P
- Jeśli zadeklarowana dostępność elementu jest wewnętrzna, domena ułatwień dostępu
T
jest tekstemT
programu .P
Uwaga: Z tych definicji wynika, że domena ułatwień dostępu typu niezwiązanego najwyższego poziomu jest zawsze co najmniej tekstem programu, w którym ten typ jest zadeklarowany. notatka końcowa
Domena ułatwień dostępu dla typu skonstruowanego T<A₁, ..., Aₑ>
jest skrzyżowaniem domeny ułatwień dostępu typu T
ogólnego bez ruchu przychodzącego i domen ułatwień dostępu argumentów A₁, ..., Aₑ
typu .
Domena ułatwień dostępu zagnieżdżonego elementu członkowskiego M
zadeklarowanego w typie T
w programie P
jest zdefiniowana w następujący sposób (zauważając, że M
może to być typ):
- Jeśli zadeklarowane ułatwienia dostępu to
M
, domena ułatwień dostępu jestpublic
domeną ułatwień dostępuM
.T
- Jeśli zadeklarowane ułatwienia dostępu
M
toprotected internal
, niechD
będzie unii tekstuP
programu i tekstu programu dowolnego typu pochodzącego zT
, który jest zadeklarowany pozaP
. Domena ułatwień dostępu programuM
to skrzyżowanie domeny ułatwień dostępu w usłudzeT
D
. - Jeśli zadeklarowana dostępność
M
elementu toprivate protected
, pozwólD
na przecięcie tekstuP
programu i tekstuT
programu i dowolnego typu pochodzącego zT
klasy . Domena ułatwień dostępu programuM
to skrzyżowanie domeny ułatwień dostępu w usłudzeT
D
. - Jeśli zadeklarowane ułatwienia dostępu
M
toprotected
, niechD
będzie unii tekstuT
programu i tekstu programu dowolnego typu pochodzącego zT
. Domena ułatwień dostępu programuM
to skrzyżowanie domeny ułatwień dostępu w usłudzeT
D
. - Jeśli zadeklarowane ułatwienia dostępu to
M
, domena ułatwień dostępu jestinternal
skrzyżowaniem domeny ułatwień dostępuM
wT
pliku z tekstemP
programu . - Jeśli zadeklarowane ułatwienia dostępu to
M
, domena ułatwień dostępuprivate
programuM
to tekstT
programu .
Uwaga: Z tych definicji wynika, że domena ułatwień dostępu zagnieżdżonego elementu członkowskiego jest zawsze co najmniej tekstem programu typu, w którym element członkowski jest zadeklarowany. Ponadto wynika z tego, że domena ułatwień dostępu elementu członkowskiego nigdy nie jest bardziej inkluzywna niż domena ułatwień dostępu typu, w którym element członkowski jest zadeklarowany. notatka końcowa
Uwaga: W intuicyjnych warunkach, gdy dostęp do typu lub elementu członkowskiego
M
jest uzyskiwany, następujące kroki są oceniane w celu zapewnienia, że dostęp jest dozwolony:
- Po pierwsze, jeśli
M
jest zadeklarowany w ramach typu (w przeciwieństwie do jednostki kompilacji lub przestrzeni nazw), błąd czasu kompilacji występuje, jeśli ten typ nie jest dostępny.- Następnie, jeśli
M
mapublic
wartość , dostęp jest dozwolony.- W przeciwnym razie, jeśli
M
ma wartość , dostęp jest dozwolony, jeśli występuje w programie, w którymprotected internal
jest zadeklarowany, lub jeśli występuje w klasie pochodzącej z klasy, w którejM
jest zadeklarowany i odbywa się za pośrednictwem typu klasy pochodnej (M
).- W przeciwnym razie, jeśli parametr ma
M
wartość , dostęp jest dozwolony, jeśli występuje w klasie, w której jest zadeklarowany, lub jeśli występuje w klasie pochodzącej z klasy, w którejprotected
M
jest zadeklarowany i odbywa się za pośrednictwem typu klasy pochodnej (M
).- W przeciwnym razie, jeśli
M
mainternal
wartość , dostęp jest dozwolony, jeśli występuje w programie, w którymM
jest zadeklarowany.- W przeciwnym razie, jeśli
M
maprivate
wartość , dostęp jest dozwolony, jeśli występuje w typie, w którymM
jest zadeklarowany.- W przeciwnym razie typ lub element członkowski jest niedostępny i występuje błąd czasu kompilacji. notatka końcowa
Przykład: w poniższym kodzie
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; } }
klasy i składowe mają następujące domeny ułatwień dostępu:
- Domena ułatwień dostępu i
A
jest nieograniczonaA.X
.- Domena ułatwień dostępu ,
A.Y
,B
,B.X
B.Y
B.C
,B.C.X
iB.C.Y
jest tekstem programu zawierającym program.- Domena ułatwień dostępu programu
A.Z
to tekstA
programu .- Domena
B.Z
ułatwień dostępu iB.D
jest tekstemB
programu , w tym tekstemB.C
programu iB.D
.- Domena ułatwień dostępu programu
B.C.Z
to tekstB.C
programu .- Domena
B.D.X
ułatwień dostępu iB.D.Y
jest tekstemB
programu , w tym tekstemB.C
programu iB.D
.- Domena ułatwień dostępu programu
B.D.Z
to tekstB.D
programu . Jak pokazano w przykładzie, domena ułatwień dostępu elementu członkowskiego nigdy nie jest większa niż domena zawierająca. Na przykład, mimo że wszyscyX
członkowie mają publiczne zadeklarowane ułatwienia dostępu, wszystkie, aleA.X
mają domeny ułatwień dostępu ograniczone przez typ zawierający.przykład końcowy
Zgodnie z opisem w §7.4 wszystkie elementy członkowskie klasy bazowej, z wyjątkiem konstruktorów wystąpień, finalizatorów i konstruktorów statycznych, są dziedziczone przez typy pochodne. Obejmuje to nawet prywatne elementy członkowskie klasy bazowej. Jednak domena ułatwień dostępu prywatnego elementu członkowskiego zawiera tylko tekst programu typu, w którym element członkowski jest zadeklarowany.
Przykład: w poniższym kodzie
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 } }
B
klasa dziedziczy prywatny element członkowskix
zA
klasy. Ponieważ element członkowski jest prywatny, jest dostępny tylko w class_body .A
W związku z tym dostęp dob.x
metody zakończy się powodzeniemA.F
, ale nie powiedzie się w metodzieB.F
.przykład końcowy
7.5.4 Chroniony dostęp
protected
Gdy dostęp do elementu członkowskiego lub private protected
wystąpienia jest uzyskiwany poza tekstem programu klasy, w której jest zadeklarowany, oraz gdy protected internal
dostęp do elementu członkowskiego wystąpienia jest uzyskiwany poza tekstem programu, w którym jest zadeklarowany, dostęp odbywa się w deklaracji klasy pochodzącej z klasy, w której jest zadeklarowany. Ponadto dostęp jest wymagany do przeprowadzenia za pośrednictwem wystąpienia tego typu klasy pochodnej lub typu klasy skonstruowanej z niego. To ograniczenie uniemożliwia jednej klasy pochodnej dostęp do chronionych składowych innych klas pochodnych, nawet jeśli składowe są dziedziczone z tej samej klasy bazowej.
Niech B
będzie klasą bazową, która deklaruje element członkowski M
wystąpienia chronionego i niech D
będzie klasą pochodzącą z B
klasy .
W class_bodyD
programu dostęp M
do programu może mieć jedną z następujących form:
- Niekwalifikowana type_name lub primary_expression formularza
M
. - Primary_expression
D
- Primary_expression formularza
base.M
. - Primary_expression formularza
base[
argument_list]
.
Oprócz tych form dostępu klasa pochodna może uzyskać dostęp do konstruktora wystąpienia chronionego klasy bazowej w constructor_initializer (§15.11.2).
Przykład: w poniższym kodzie
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 } }
w programie
A
istnieje możliwość uzyskania dostępux
za pośrednictwem wystąpień obuA
systemów iB
, ponieważ w obu przypadkach dostęp odbywa się za pośrednictwem wystąpienia klasy lub klasy pochodzącejA
zA
klasy . Jednak w programieB
nie można uzyskać dostępux
za pośrednictwem wystąpieniaA
klasy , ponieważA
nie pochodzi z klasyB
.przykład końcowy
Przykład:
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"; } }
W tym miejscu dozwolone są trzy przypisania
x
, ponieważ wszystkie mają miejsce za pośrednictwem wystąpień typów klas skonstruowanych z typu ogólnego.przykład końcowy
Uwaga: domena ułatwień dostępu (§7.5.3) chronionej składowej zadeklarowanej w klasie ogólnej zawiera tekst programu wszystkich deklaracji klas pochodzących z dowolnego typu skonstruowanego z tej klasy ogólnej. W przykładzie:
class C<T> { protected static T x; } class D : C<string> { static void Main() { C<int>.x = 5; } }
odwołanie do
protected
elementu członkowskiegoC<int>.x
wD
elemencie jest prawidłowe, mimo że klasaD
pochodzi z klasyC<string>
. notatka końcowa
Ograniczenia ułatwień dostępu w wersji 7.5.5
Kilka konstrukcji w języku C# wymaga, aby typ był co najmniej tak dostępny jako element członkowski lub inny typ.
T
Typ jest uważany za co najmniej tak dostępny jako element członkowski lub typM
, jeśli domena T
ułatwień dostępu jest nadzbiorem domeny ułatwień dostępu .M
Innymi słowy, jest co najmniej tak dostępny, T
jakby M
T
był dostępny we wszystkich kontekstach, w których M
jest dostępny.
Istnieją następujące ograniczenia ułatwień dostępu:
- Bezpośrednia klasa bazowa typu klasy jest co najmniej tak dostępna, jak sam typ klasy.
- Jawne interfejsy podstawowe typu interfejsu są co najmniej tak dostępne, jak sam typ interfejsu.
- Zwracany typ i typy parametrów typu delegata są co najmniej tak dostępne, jak sam typ delegata.
- Typ stałej jest co najmniej tak dostępny, jak sama stała.
- Typ pola jest co najmniej tak dostępny, jak samo pole.
- Typ zwracany i typy parametrów metody są co najmniej tak dostępne, jak sama metoda.
- Typ nieruchomości jest co najmniej tak dostępny, jak sama właściwość.
- Rodzaj zdarzenia jest co najmniej tak dostępny, jak samo wydarzenie.
- Typ i typy parametrów indeksatora są co najmniej tak dostępne, jak sam indeksator.
- Typ zwracany i typy parametrów operatora są co najmniej tak dostępne, jak sam operator.
- Typy parametrów konstruktora wystąpienia są co najmniej tak dostępne, jak sam konstruktor wystąpienia.
- Ograniczenie interfejsu lub typu klasy dla parametru typu jest co najmniej tak dostępne, jak element członkowski, który deklaruje ograniczenie.
Przykład: w poniższym kodzie
class A {...} public class B: A {...}
B
klasa powoduje błąd w czasie kompilacji, ponieważA
nie jest co najmniej tak dostępny jakB
.przykład końcowy
Przykład: podobnie w poniższym kodzie
class A {...} public class B { A F() {...} internal A G() {...} public A H() {...} }
H
metoda wB
wyniku błędu czasu kompilacji, ponieważ zwracany typA
nie jest co najmniej tak dostępny, jak metoda.przykład końcowy
7.6 Podpisy i przeciążenie
Metody, konstruktory wystąpień, indeksatory i operatory charakteryzują się ich podpisami:
- Podpis metody składa się z nazwy metody, liczby parametrów typu oraz trybu przekazywania parametrów typu i trybu przekazywania parametrów każdego z jego parametrów w kolejności od lewej do prawej. W tych celach każdy parametr typu metody, która występuje w typie parametru, nie jest identyfikowany przez jego nazwę, ale według pozycji porządkowej na liście parametrów typu metody. Podpis metody nie zawiera w szczególności zwracanego typu, nazw parametrów, nazw parametrów typu, ograniczeń parametrów typu,
params
modyfikatorów parametrów lubthis
, ani parametrów wymaganych lub opcjonalnych. - Podpis konstruktora wystąpienia składa się z typu i trybu przekazywania parametrów każdego z jego parametrów, uważanych w kolejności od lewej do prawej. Podpis konstruktora wystąpienia nie zawiera
params
w szczególności modyfikatora, który może być określony dla najbardziej odpowiedniego parametru, ani tego, czy parametry są wymagane, czy opcjonalne. - Podpis indeksatora składa się z typu każdego z jego parametrów, które są brane pod uwagę w kolejności od lewej do prawej. Podpis indeksatora w szczególności nie zawiera typu elementu, ani nie zawiera
params
modyfikatora, który może być określony dla najbardziej odpowiedniego parametru, ani czy parametry są wymagane, czy opcjonalne. - Podpis operatora składa się z nazwy operatora i typu każdego z jego parametrów, które są brane pod uwagę w kolejności od lewej do prawej. Podpis operatora nie zawiera w szczególności typu wyniku.
- Podpis operatora konwersji składa się z typu źródłowego i typu docelowego. Niejawna lub jawna klasyfikacja operatora konwersji nie jest częścią podpisu.
- Dwa podpisy tego samego rodzaju elementu członkowskiego (metoda, konstruktor wystąpienia, indeksator lub operator) są uważane za te same podpisy , jeśli mają taką samą nazwę, liczbę parametrów typu, liczbę parametrów i tryby przekazywania parametrów, a konwersja tożsamości istnieje między typami odpowiednich parametrów (§10.2.2).
Podpisy są mechanizmem włączania przeciążenia składowych w klasach, strukturach i interfejsach:
- Przeciążenie metod umożliwia klasom, strukturom lub interfejsowi deklarowanie wielu metod o tej samej nazwie, pod warunkiem że ich podpisy są unikatowe w tej klasie, struktury lub interfejsie.
- Przeciążenie konstruktorów wystąpień umożliwia klasom lub strukturom deklarowanie wielu konstruktorów wystąpień, pod warunkiem że ich podpisy są unikatowe w ramach tej klasy lub struktury.
- Przeciążenie indeksatorów umożliwia deklarowanie wielu indeksatorów klasy, struktury lub interfejsu, pod warunkiem, że ich podpisy są unikatowe w ramach tej klasy, struktury lub interfejsu.
- Przeciążenie operatorów zezwala klasie lub struktury na deklarowanie wielu operatorów o tej samej nazwie, pod warunkiem, że ich podpisy są unikatowe w tej klasie lub struktur.
Mimo że in
modyfikatory parametrów , out
i ref
są uważane za część podpisu, składowe zadeklarowane w jednym typie nie mogą różnić się podpisem wyłącznie według in
, out
i ref
. Błąd czasu kompilacji występuje, jeśli dwa elementy członkowskie są zadeklarowane w tym samym typie z podpisami, które byłyby takie same, jeśli wszystkie parametry w obu metodach z modyfikatorami out
lub in
modyfikatory zostały zmienione na ref
modyfikatory. W innych celach dopasowywania podpisów (np. ukrywania lub zastępowania), in
, out
i ref
są traktowane jako część podpisu i nie są ze sobą zgodne.
Uwaga: To ograniczenie polega na umożliwieniu łatwego tłumaczenia programów w języku C# na potrzeby uruchamiania w infrastrukturze common language infrastructure (CLI), która nie zapewnia sposobu definiowania metod, które różnią się wyłącznie w
in
systemach ,out
iref
. notatka końcowa
Typy object
i dynamic
nie są rozróżniane podczas porównywania podpisów. W związku z tym elementy członkowskie zadeklarowane w jednym typie, których podpisy różnią się tylko od zamiany object
na dynamic
nie są dozwolone.
Przykład: Poniższy przykład przedstawia zestaw przeciążonych deklaracji metod wraz z ich podpisami.
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 }
Należy pamiętać, że wszystkie
in
modyfikatory parametrów ,out
iref
(§15.6.2) są częścią podpisu. W związku z tym wszystkieF(int)
podpisy,F(in int)
F(out int)
iF(ref int)
są unikatowe. Jednak , i nie można zadeklarować w tym samym interfejsie,F(in int)
ponieważ ich podpisy różnią się wyłącznie odF(out int)
,F(ref int)
iin
.out
ref
Należy również pamiętać, że typ zwracany iparams
modyfikator nie są częścią podpisu, więc nie można przeciążyć wyłącznie na podstawie typu zwracanego lub włączenia lub wykluczeniaparams
modyfikatora. W związku z tym deklaracje metodF(int)
iF(params string[])
zidentyfikowane powyżej powodują błąd czasu kompilacji. przykład końcowy
7.7 Zakresy
7.7.1 Ogólne
Zakres nazwy to region tekstu programu, w którym można odwoływać się do jednostki zadeklarowanej przez nazwę bez kwalifikacji nazwy. Zakresy można zagnieżdżać, a zakres wewnętrzny może ponownie określić znaczenie nazwy z zakresu zewnętrznego. (Nie powoduje to jednak usunięcia ograniczenia nałożonego przez §7.3 , że w zagnieżdżonym bloku nie można zadeklarować zmiennej lokalnej lub stałej lokalnej o tej samej nazwie co zmienna lokalna lub stała lokalna w otaczającym bloku). Nazwa z zakresu zewnętrznego jest następnie określana jako ukryta w regionie tekstu programu objętego zakresem wewnętrznym, a dostęp do nazwy zewnętrznej jest możliwy tylko przez kwalifikowanie nazwy.
Zakres elementu członkowskiego przestrzeni nazw zadeklarowany przez namespace_member_declaration (§14.6) bez otaczającego namespace_declaration jest całym tekstem programu.
Zakres elementu członkowskiego przestrzeni nazw zadeklarowany przez namespace_member_declaration w namespace_declaration, którego w pełni kwalifikowana nazwa to
N
, jest namespace_body każdego namespace_declaration, którego w pełni kwalifikowana nazwa toN
lub zaczyna się odN
kropki.Zakres nazwy zdefiniowanej przez extern_alias_directive (§14.4) rozciąga się na using_directives, global_attributes i namespace_member_declarations od razu zawierające compilation_unit lub namespace_body. Extern_alias_directive nie przyczynia się do przestrzeni deklaracji bazowej żadnych nowych elementów członkowskich. Innymi słowy, extern_alias_directive nie jest przechodnia, ale raczej wpływa tylko na compilation_unit lub namespace_body , w którym występuje.
Zakres nazwy zdefiniowanej lub zaimportowanej przez using_directive (§14.5) rozciąga się na global_attributes i namespace_member_declarations compilation_unit lub namespace_body, w których występuje using_directive. Using_directive może udostępnić zero lub więcej nazw przestrzeni nazw lub typów w ramach określonej compilation_unit lub namespace_body, ale nie przyczynia się do żadnych nowych elementów członkowskich do bazowej przestrzeni deklaracji. Innymi słowy, using_directive nie jest przechodnia, ale raczej wpływa tylko na compilation_unit lub namespace_body, w którym występuje.
Zakres parametru typu zadeklarowanego przez type_parameter_list w class_declaration (§15.2) to class_base, type_parameter_constraints_clauses i class_body tego class_declaration.
Uwaga: w przeciwieństwie do składowych klasy ten zakres nie rozszerza się na klasy pochodne. notatka końcowa
Zakres parametru typu zadeklarowanego przez type_parameter_list w struct_declaration (§16.2) to struct_interfaces, type_parameter_constraints_clauses i struct_body tego struct_declaration.
Zakres parametru typu zadeklarowanego przez type_parameter_list w interface_declaration (§18.2) to interface_base, type_parameter_constraints_clauses i interface_body tego interface_declaration.
Zakres parametru typu zadeklarowanego przez type_parameter_list w delegate_declaration (§20.2) to return_type, parameter_list i type_parameter_constraints_clausetego delegate_declaration.
Zakres parametru typu zadeklarowanego przez type_parameter_list w method_declaration (§15.6.1) jest method_declaration.
Zakres elementu członkowskiego zadeklarowanego przez class_member_declaration (§15.3.1) jest class_body , w którym występuje deklaracja. Ponadto zakres składowej klasy rozciąga się na class_body tych klas pochodnych, które są uwzględnione w domenie ułatwień dostępu (§7.5.3) składowej.
Zakres elementu członkowskiego zadeklarowanego przez struct_member_declaration (§16.3) jest struct_body , w którym następuje deklaracja.
Zakres elementu członkowskiego zadeklarowanego przez enum_member_declaration (§19.4) jest enum_body , w którym następuje deklaracja.
Zakres parametru zadeklarowanego w method_declaration (§15.6) to method_body lub ref_method_body tego method_declaration.
Zakres parametru zadeklarowanego w indexer_declaration (§15.9) jest indexer_body tego indexer_declaration.
Zakres parametru zadeklarowanego w operator_declaration (§15.10) jest operator_body tego operator_declaration.
Zakres parametru zadeklarowanego w constructor_declaration (§15.11) to constructor_initializer i blok tego constructor_declaration.
Zakres parametru zadeklarowanego w lambda_expression (§12.19) jest lambda_expression_body tego lambda_expression.
Zakres parametru zadeklarowanego w anonymous_method_expression (§12.19) jest blokiem tego anonymous_method_expression.
Zakres etykiety zadeklarowanej w labeled_statement (§13.5) to blok , w którym występuje deklaracja.
Zakres zmiennej lokalnej zadeklarowanej w local_variable_declaration (§13.6.2) to blok , w którym występuje deklaracja.
Zakres zmiennej lokalnej zadeklarowanej w switch_block
switch
instrukcji (§13.8.3) jest switch_block.Zakres zmiennej lokalnej zadeklarowanej w
for
instrukcji (§13.9.4) to for_initializer, for_condition, for_iterator i embedded_statement instrukcjifor
.Zakres stałej lokalnej zadeklarowanej w local_constant_declaration (§13.6.3) to blok , w którym występuje deklaracja. Jest to błąd czasu kompilacji, który odwołuje się do stałej lokalnej w pozycji tekstowej, która poprzedza jego constant_declarator.
Zakres zmiennej zadeklarowanej w ramach foreach_statement, using_statement, lock_statement lub query_expression jest określany przez rozszerzenie danej konstrukcji.
W zakresie przestrzeni nazw, klasy, struktury lub składowej wyliczenia można odwołać się do składowej w pozycji tekstowej poprzedzającej deklarację elementu członkowskiego.
Przykład:
class A { void F() { i = 1; } int i = 0; }
Tutaj ważne jest
F
, aby odwołać się doi
przed jego zadeklarowanie.przykład końcowy
W zakresie zmiennej lokalnej jest to błąd czasu kompilacji, który odwołuje się do zmiennej lokalnej w pozycji tekstowej poprzedzającej deklarator.
Przykład:
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 } }
W powyższej metodzie
F
pierwsze przypisanie niei
odnosi się do pola zadeklarowane w zakresie zewnętrznym. Zamiast tego odwołuje się do zmiennej lokalnej i powoduje błąd w czasie kompilacji, ponieważ tekstowo poprzedza deklarację zmiennej. W metodzieG
użyciej
elementu w inicjatorze deklaracjij
jest prawidłowe, ponieważ użycie nie poprzedza deklaratora. W metodzieH
kolejna deklarator poprawnie odwołuje się do zmiennej lokalnej zadeklarowanej we wcześniejszym deklaratorze w ramach tego samego local_variable_declaration.przykład końcowy
Uwaga: reguły określania zakresu zmiennych lokalnych i stałych lokalnych są przeznaczone do zagwarantowania, że znaczenie nazwy używanej w kontekście wyrażenia jest zawsze takie same w bloku. Jeśli zakres zmiennej lokalnej miał zostać rozszerzony tylko z jego deklaracji na koniec bloku, w powyższym przykładzie pierwsze przypisanie przypisze do zmiennej wystąpienia, a drugie przypisanie przypisze do zmiennej lokalnej, co może prowadzić do błędów czasu kompilacji, jeśli instrukcje bloku zostaną później rozmieszczone).
Znaczenie nazwy w bloku może się różnić w zależności od kontekstu, w którym jest używana nazwa. W przykładzie
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" } }
nazwa
A
jest używana w kontekście wyrażenia, aby odwoływać się do zmiennejA
lokalnej i w kontekście typu, aby odwołać się do klasyA
.notatka końcowa
7.7.2 Ukrywanie nazwy
7.7.2.1 Ogólne
Zakres jednostki zwykle obejmuje więcej tekstu programu niż przestrzeń deklaracji jednostki. W szczególności zakres jednostki może obejmować deklaracje, które wprowadzają nowe przestrzenie deklaracji zawierające jednostki o tej samej nazwie. Takie deklaracje powodują ukrycie oryginalnej jednostki. Z drugiej strony mówi się, że jednostka jest widoczna , gdy nie jest ukryta.
Ukrywanie nazwy występuje, gdy zakresy nakładają się na zagnieżdżanie i gdy zakresy nakładają się na dziedziczenie. Cechy dwóch typów ukrywania są opisane w poniższych podklasach.
7.7.2.2 Ukrywanie zagnieżdżania
Ukrywanie nazw przez zagnieżdżanie może wystąpić w wyniku zagnieżdżania przestrzeni nazw lub typów w przestrzeniach nazw, w wyniku zagnieżdżania typów w klasach lub strukturach, w wyniku funkcji lokalnej lub lambdy, a w wyniku parametru, zmiennej lokalnej i deklaracji stałych lokalnych.
Przykład: w poniższym kodzie
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; } }
w metodzie
F
zmienna wystąpienia jest ukryta przez zmiennąi
i
lokalną , ale w ramachG
metodyi
nadal odwołuje się do zmiennej wystąpienia. Wewnątrz funkcjiM1
lokalnej funkcjafloat i
ukrywa bezpośrednio zewnętrznei
. Parametri
lambda ukrywafloat i
wewnątrz treści lambda.przykład końcowy
Gdy nazwa w zakresie wewnętrznym ukrywa nazwę w zakresie zewnętrznym, ukrywa wszystkie przeciążone wystąpienia tej nazwy.
Przykład: w poniższym kodzie
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 } } }
wywołanie
F(1)
wywołuje zadeklarowaneF
wInner
, ponieważ wszystkie wystąpieniaF
zewnętrzne obiektu są ukryte przez deklarację wewnętrzną. Z tego samego powodu wywołanieF("Hello")
powoduje błąd czasu kompilacji.przykład końcowy
7.7.2.3 Ukrywanie przez dziedziczenie
Ukrywanie nazw przez dziedziczenie występuje, gdy klasy lub struktury ponownie deklarują nazwy dziedziczone z klas bazowych. Ten typ ukrywania nazwy przyjmuje jedną z następujących form:
- Stała, pole, właściwość, zdarzenie lub typ wprowadzony w klasie lub strukturę ukrywa wszystkie składowe klasy bazowej o tej samej nazwie.
- Metoda wprowadzona w klasie lub struct ukrywa wszystkie elementy członkowskie klasy bazowej innej niż metoda o tej samej nazwie, a wszystkie metody klasy bazowej z tym samym podpisem (§7.6).
- Indeksator wprowadzony w klasie lub struct ukrywa wszystkie indeksatory klas bazowych z tym samym podpisem (§7.6).
Reguły rządzące deklaracjami operatorów (§15.10) uniemożliwiają klasę pochodną deklarowanie operatora z tym samym podpisem co operator w klasie bazowej. W związku z tym operatory nigdy się nie ukrywają.
W przeciwieństwie do ukrywania nazwy z zakresu zewnętrznego, ukrycie widocznej nazwy z dziedziczonego zakresu powoduje zgłaszanie ostrzeżenia.
Przykład: w poniższym kodzie
class Base { public void F() {} } class Derived : Base { public void F() {} // Warning, hiding an inherited name }
deklaracji
F
inDerived
powoduje zgłoszenie ostrzeżenia. Ukrywanie dziedziczonej nazwy nie jest w szczególności błędem, ponieważ wykluczałoby to osobną ewolucję klas bazowych. Na przykład w powyższej sytuacji może wystąpić, ponieważ nowsza wersjaBase
metodyF
, która nie była obecna we wcześniejszej wersji klasy.przykład końcowy
Ostrzeżenie spowodowane ukrywanie dziedziczonej nazwy można wyeliminować za pomocą new
modyfikatora:
Przykład:
class Base { public void F() {} } class Derived : Base { public new void F() {} }
new
Modyfikator wskazuje, że elementF
inDerived
jest "nowy", i że rzeczywiście ma na celu ukrycie dziedziczonego elementu członkowskiego.przykład końcowy
Deklaracja nowego elementu członkowskiego ukrywa dziedziczony element członkowski tylko w zakresie nowego elementu członkowskiego.
Przykład:
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 } }
W powyższym przykładzie deklaracja in ukrywa dziedziczone z
F
klasy , ale ponieważ nowyDerived
w systemieF
ma dostęp prywatny, jego zakres nie rozciąga się naBase
wartość .F
Derived
MoreDerived
W związku z tym wywołanieF()
metody w plikuMoreDerived.G
jest prawidłowe i wywoła metodęBase.F
.przykład końcowy
7.8 Przestrzeń nazw i nazwy typów
7.8.1 Ogólne
Kilka kontekstów w programie języka C# wymaga określenia namespace_name lub type_name .
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
;
Namespace_name to namespace_or_type_name odwołujący się do przestrzeni nazw.
Po rozwiązaniu, jak opisano poniżej, namespace_or_type_namenamespace_name odnosi się do przestrzeni nazw lub w przeciwnym razie występuje błąd czasu kompilacji. Argumenty typu (§8.4.2) nie mogą być obecne w namespace_name (tylko typy mogą mieć argumenty typu).
Type_name to namespace_or_type_name, która odwołuje się do typu. Po rozwiązaniu, jak opisano poniżej, namespace_or_type_nametype_name odnosi się do typu lub w przeciwnym razie występuje błąd czasu kompilacji.
Jeśli namespace_or_type_name jest qualified_alias_member jego znaczenie jest zgodnie z opisem w §14.8.1. W przeciwnym razie namespace_or_type_name ma jedną z czterech form:
I
I<A₁, ..., Aₓ>
N.I
N.I<A₁, ..., Aₓ>
gdzie I
jest pojedynczym identyfikatorem, N
jest namespace_or_type_name i <A₁, ..., Aₓ>
jest opcjonalnym type_argument_list. Jeśli nie określono type_argument_list , rozważ x
wartość zero.
Znaczenie namespace_or_type_name jest określane w następujący sposób:
- Jeśli namespace_or_type_name jest qualified_alias_member, znaczenie jest określone w §14.8.1.
- W przeciwnym razie, jeśli namespace_or_type_name ma postać
I
lub formularzaI<A₁, ..., Aₓ>
:- Jeśli
x
ma wartość zero, a namespace_or_type_name pojawia się w deklaracji metody ogólnej (§15.6), ale poza atrybutami nagłówka metody, a jeśli ta deklaracja zawiera parametr typu (§15.2.3) o nazwieI
, namespace_or_type_name odnosi się do tego parametru typu. - W przeciwnym razie, jeśli namespace_or_type_name pojawia się w deklaracji typu, dla każdego typu wystąpienia (
T
), począwszy od typu wystąpienia deklaracji tego typu i kontynuując typ wystąpienia każdej otaczającej klasy lub deklaracji struktury (jeśli istnieją):- Jeśli
x
ma wartość zero, a deklaracjaT
zawiera parametr typu o nazwieI
, namespace_or_type_name odnosi się do tego parametru typu. - W przeciwnym razie, jeśli namespace_or_type_name pojawia się w treści deklaracji typu, a
T
dowolny z jego typów podstawowych zawiera zagnieżdżony typ dostępny o nazwieI
ix
parametrach typu, namespace_or_type_name odnosi się do tego typu skonstruowanego z podanymi argumentami typu. Jeśli istnieje więcej niż jeden taki typ, wybrano typ zadeklarowany w obrębie bardziej pochodnego typu.
Uwaga: nietypowe elementy członkowskie (stałe, pola, metody, właściwości, indeksatory, operatory, konstruktory wystąpień, finalizatory i konstruktory statyczne) i składowe typu z inną liczbą parametrów typu są ignorowane podczas określania znaczenia namespace_or_type_name. notatka końcowa
- Jeśli
- W przeciwnym razie dla każdej przestrzeni
N
nazw, począwszy od przestrzeni nazw, w której występuje namespace_or_type_name , kontynuując każdą otaczającą przestrzeń nazw (jeśli istnieje), a kończąc na globalnej przestrzeni nazw, następujące kroki są oceniane do momentu zlokalizowania jednostki:- Jeśli
x
ma wartość zero iI
jest nazwą przestrzeni nazw wN
pliku , wówczas:- Jeśli lokalizacja, w której występuje namespace_or_type_name, jest ujęta przez deklarację
N
przestrzeni nazw, a deklaracja przestrzeni nazw zawiera extern_alias_directive lub using_alias_directiveI
typem, namespace_or_type_name jest niejednoznaczny i występuje błąd czasu kompilacji. -
W przeciwnym razie namespace_or_type_name odnosi się do przestrzeni nazw o nazwie
I
wN
pliku .
- Jeśli lokalizacja, w której występuje namespace_or_type_name, jest ujęta przez deklarację
- W przeciwnym razie, jeśli
N
zawiera dostępny typ o nazwieI
ix
parametrach typu, wówczas:- Jeśli
x
ma wartość zero, a lokalizacja, w której występuje namespace_or_type_name, jest ujęta przez deklaracjęN
przestrzeni nazw, a deklaracja przestrzeni nazw zawiera extern_alias_directive lub using_alias_directiveI
typem, namespace_or_type_name jest niejednoznaczna i występuje błąd czasu kompilacji. - W przeciwnym razie namespace_or_type_name odnosi się do typu skonstruowanego przy użyciu podanych argumentów typu.
- Jeśli
- W przeciwnym razie, jeśli lokalizacja, w której występuje namespace_or_type_name , jest ujęta przez deklarację przestrzeni nazw dla
N
:- Jeśli
x
ma wartość zero, a deklaracja przestrzeni nazw zawiera extern_alias_directive lub using_alias_directive , która kojarzy nazwęI
z zaimportowaną przestrzenią nazw lub typem, namespace_or_type_name odnosi się do tej przestrzeni nazw lub typu. - W przeciwnym razie, jeśli przestrzenie nazw zaimportowane przez using_namespace_directives deklaracji przestrzeni nazw zawierają dokładnie jeden typ o parametrach nazwy
I
ix
typu, namespace_or_type_name odnosi się do tego typu skonstruowanego z podanymi argumentami typu. - W przeciwnym razie, jeśli przestrzenie nazw importowane przez using_namespace_directivedeklaracji przestrzeni nazw zawierają więcej niż jeden typ o parametrach nazwy
I
ix
typu, namespace_or_type_name jest niejednoznaczny i wystąpi błąd.
- Jeśli
- Jeśli
- W przeciwnym razie namespace_or_type_name jest niezdefiniowany i występuje błąd czasu kompilacji.
- Jeśli
-
W przeciwnym razie namespace_or_type_name ma postać
N.I
lub formularzN.I<A₁, ..., Aₓ>
.N
jest najpierw rozpoznawany jako namespace_or_type_name. Jeśli rozwiązanie poleceniaN
nie powiedzie się, wystąpi błąd czasu kompilacji.N.I
W przeciwnym razie rozwiązanie lubN.I<A₁, ..., Aₓ>
jest rozwiązywane w następujący sposób:- Jeśli
x
ma wartość zero iN
odwołuje się do przestrzeni nazw iN
zawiera zagnieżdżonych przestrzeni nazw o nazwieI
, namespace_or_type_name odnosi się do tej zagnieżdżonej przestrzeni nazw. - W przeciwnym razie jeśli
N
odwołuje się do przestrzeni nazw iN
zawiera dostępny typ o parametrach nazwyI
ix
typu, namespace_or_type_name odnosi się do tego typu skonstruowanego z podanymi argumentami typu. - W przeciwnym razie, jeśli
N
odwołuje się do (prawdopodobnie skonstruowanej) klasy lub typu struktury iN
lub dowolnej z jej klas bazowych zawierają zagnieżdżony dostępny typ o nazwieI
ix
parametrach typu, namespace_or_type_name odnosi się do tego typu skonstruowanego z podanymi argumentami typu. Jeśli istnieje więcej niż jeden taki typ, wybrano typ zadeklarowany w obrębie bardziej pochodnego typu.Uwaga: Jeśli znaczenie
N.I
jest określane w ramach rozpoznawania specyfikacji klasy bazowejN
klasy podstawowej, uważa się, że bezpośrednia klasa bazowaN
klasy jest uważanaobject
za (§15.2.4.2). notatka końcowa -
N.I
W przeciwnym razie jest nieprawidłowym namespace_or_type_name, a występuje błąd czasu kompilacji.
- Jeśli
Namespace_or_type_name może odwoływać się do klasy statycznej (§15.2.2.4) tylko wtedy, gdy
- Namespace_or_type_name jest
T
w namespace_or_type_name formularzaT.I
lub -
namespace_or_type_name jest
T
w wyrażeniu typeof (§12.8.18) w formietypeof(T)
7.8.2 Niekwalifikowane nazwy
Każda deklaracja przestrzeni nazw i deklaracja typu ma niekwalifikowaną nazwę określoną w następujący sposób:
- W przypadku deklaracji przestrzeni nazw niekwalifikowana nazwa to qualified_identifier określona w deklaracji.
- W przypadku deklaracji typu bez type_parameter_list niekwalifikowana nazwa jest identyfikatorem określonym w deklaracji.
- W przypadku deklaracji typu z K parametrami typu, niekwalifikowana nazwa to identyfikator określony w deklaracji, a następnie określenie wymiaru ogólnego (§12.8.18) dla K parametrów typu.
7.8.3 W pełni kwalifikowane nazwy
Każda przestrzeń nazw i deklaracja typu ma w pełni kwalifikowaną nazwę, która jednoznacznie identyfikuje przestrzeń nazw lub deklarację typu między wszystkimi innymi w programie. W pełni kwalifikowana nazwa przestrzeni nazw lub deklaracji typu o niekwalifikowanej nazwie N
jest określana w następujący sposób:
- Jeśli
N
jest członkiem globalnej przestrzeni nazw, jej w pełni kwalifikowana nazwa toN
. - W przeciwnym razie jej w pełni kwalifikowana nazwa to
S.N
, gdzieS
jest w pełni kwalifikowaną nazwą przestrzeni nazw lub deklaracji typu, w którejN
jest zadeklarowana.
Innymi słowy, w pełni kwalifikowana nazwa N
to pełna hierarchiczna ścieżka identyfikatorów i generic_dimension_specifier, które prowadzą do N
, począwszy od globalnej przestrzeni nazw. Ponieważ każdy element członkowski przestrzeni nazw lub typu ma unikatową nazwę, wynika z tego, że w pełni kwalifikowana nazwa przestrzeni nazw lub deklaracji typu jest zawsze unikatowa. Jest to błąd czasu kompilacji dla tej samej w pełni kwalifikowanej nazwy, aby odwoływać się do dwóch odrębnych jednostek. W szczególności:
- Jest to błąd zarówno dla deklaracji przestrzeni nazw, jak i deklaracji typu, aby mieć taką samą w pełni kwalifikowaną nazwę.
- Jest to błąd dla dwóch różnych rodzajów deklaracji typów, aby mieć taką samą w pełni kwalifikowaną nazwę (na przykład jeśli zarówno struktura, jak i deklaracja klasy mają taką samą w pełni kwalifikowaną nazwę).
- Jest to błąd deklaracji typu bez modyfikatora częściowego, który ma taką samą w pełni kwalifikowaną nazwę jak inna deklaracja typu (§15.2.7).
Przykład: Poniższy przykład przedstawia kilka deklaracji przestrzeni nazw i typów wraz ze skojarzonymi w pełni kwalifikowanymi nazwami.
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<> } }
przykład końcowy
7.9 Automatyczne zarządzanie pamięcią
Język C# wykorzystuje automatyczne zarządzanie pamięcią, co zwalnia deweloperów z ręcznego przydzielania i zwalniania pamięci zajmowanej przez obiekty. Zasady automatycznego zarządzania pamięcią są implementowane przez moduł odśmiecający pamięć. Cykl życia zarządzania pamięcią obiektu jest następujący:
- Po utworzeniu obiektu zostanie przydzielona pamięć, zostanie uruchomiona konstruktor, a obiekt jest uznawany za żywy.
- Jeśli ani obiekt, ani żadne z jego pól wystąpienia nie mogą być dostępne przez dowolną możliwą kontynuację wykonywania, poza uruchomieniem finalizatorów, obiekt jest uznawany za nieumyślnie używany i kwalifikuje się do finalizacji.
Uwaga: kompilator języka C# i moduł odśmieceń pamięci mogą zdecydować się na analizę kodu w celu określenia, które odwołania do obiektu mogą być używane w przyszłości. Jeśli na przykład zmienna lokalna, która znajduje się w zakresie, jest jedynym istniejącym odwołaniem do obiektu, ale ta zmienna lokalna nigdy nie jest określana w żadnej możliwej kontynuacji wykonywania z bieżącego punktu wykonywania w procedurze, moduł odśmieceń pamięci może (ale nie jest wymagany) traktować obiekt jako nieużytkowy. notatka końcowa
- Gdy obiekt kwalifikuje się do finalizacji, po upływie określonego później finalizatora (§15.13) (jeśli istnieje) dla obiektu jest uruchamiany. W normalnych okolicznościach finalizator obiektu jest uruchamiany tylko raz, chociaż interfejsy API zdefiniowane przez implementację mogą zezwalać na zastąpienie tego zachowania.
- Po uruchomieniu finalizatora dla obiektu, jeśli ani obiekt, ani żadne z jego pól wystąpienia nie mogą być dostępne przez dowolną możliwą kontynuację wykonywania, w tym uruchomienie finalizatorów, obiekt jest uważany za niedostępny, a obiekt kwalifikuje się do kolekcji.
Uwaga: obiekt, do którego wcześniej nie można uzyskać dostępu, może zostać ponownie udostępniony ze względu na jego finalizator. Poniżej przedstawiono przykład. notatka końcowa
- Na koniec po pewnym czasie, gdy obiekt kwalifikuje się do zbierania, moduł odśmiecający elementy bezużyteczne zwalnia pamięć skojarzona z tym obiektem.
Moduł odśmiecanie pamięci przechowuje informacje o użyciu obiektu i używa tych informacji do podejmowania decyzji dotyczących zarządzania pamięcią, takich jak miejsce w pamięci w celu zlokalizowania nowo utworzonego obiektu, czasu przeniesienia obiektu i gdy obiekt nie jest już używany lub niedostępny.
Podobnie jak w przypadku innych języków, które zakładają istnienie modułu odśmiecającego pamięć, język C# został zaprojektowany tak, aby moduł odśmiecenia pamięci mógł implementować szeroką gamę zasad zarządzania pamięcią. Język C# nie określa ani ograniczenia czasu w tym zakresie, ani kolejności uruchamiania finalizatorów. Określa, czy finalizatory są uruchamiane w ramach zakończenia aplikacji, jest definiowane przez implementację (§7.2).
Zachowanie modułu odśmiecania pamięci można kontrolować w pewnym stopniu za pomocą metod statycznych w klasie System.GC
. Ta klasa może służyć do żądania wystąpienia kolekcji, finalizatorów do uruchomienia (lub nie uruchamiania) itd.
Przykład: Ponieważ moduł odśmiecania pamięci jest dozwolony dla szerokiej szerokości geograficznej w podejmowaniu decyzji o tym, kiedy zbierać obiekty i uruchamiać finalizatory, zgodna implementacja może wygenerować dane wyjściowe, które różnią się od tych pokazanych przez poniższy kod. Program
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(); } }
tworzy wystąpienie klasy
A
i wystąpienie klasyB
. Te obiekty kwalifikują się do odzyskiwania pamięci, gdy zmiennab
ma przypisaną wartośćnull
, ponieważ po tym czasie nie można uzyskać dostępu do żadnego kodu napisanego przez użytkownika. Dane wyjściowe mogą być alboFinalize instance of A Finalize instance of B
lub
Finalize instance of B Finalize instance of A
ponieważ język nie nakłada żadnych ograniczeń na kolejność, w której obiekty są wyrzucane śmieci.
W subtelnych przypadkach rozróżnienie między "kwalifikującą się do finalizacji" i "kwalifikujące się do kolekcji" może być ważne. Na przykład:
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"); } } }
W powyższym programie, jeśli moduł odśmiecania pamięci zdecyduje się uruchomić finalizator przed finalizatorem
A
B
programu , dane wyjściowe tego programu mogą być następujące:Finalize instance of A Finalize instance of B A.F RefA is not null
Należy pamiętać, że mimo że wystąpienie elementu
A
nie było używane iA
finalizator został uruchomiony, nadal jest możliwe wywołanie metodyA
(w tym przypadkuF
) z innego finalizatora. Należy również pamiętać, że uruchomienie finalizatora może spowodować ponowne użycie obiektu z poziomu programu głównego. W tym przypadku uruchomienieB
finalizatora spowodowało, że wystąpienieA
elementu , które wcześniej nie było używane, stało się dostępne z poziomu odwołaniaTest.RefA
na żywo. Po wywołaniuWaitForPendingFinalizers
metody , wystąpienieB
klasy kwalifikuje się do kolekcji, ale wystąpienie elementuA
nie jest spowodowane odwołaniemTest.RefA
.przykład końcowy
7.10 Kolejność wykonywania
Wykonanie programu w języku C# jest kontynuowane w taki sposób, że skutki uboczne każdego wykonywanego wątku są zachowywane w krytycznych punktach wykonywania.
Efekt uboczny jest definiowany jako odczyt lub zapis pola lotnego, zapis w zmiennej nietrwałej, zapis w zasobie zewnętrznym i zgłaszanie wyjątku. Krytyczne punkty wykonywania, w których kolejność tych skutków ubocznych jest zachowywana, są odwołaniami do pól lotnych (§15.5.4), instrukcji (§13.13) oraz tworzenia i kończenia wątków. lock
Środowisko wykonywania jest bezpłatne, aby zmienić kolejność wykonywania programu w języku C#, z zastrzeżeniem następujących ograniczeń:
- Zależność od danych jest zachowywana w wątku wykonywania. Oznacza to, że wartość każdej zmiennej jest obliczana tak, jakby wszystkie instrukcje w wątku były wykonywane w oryginalnej kolejności programu.
- Reguły porządkowania inicjowania są zachowywane (§15.5.5, §15.5.6).
- Kolejność skutków ubocznych jest zachowywana w odniesieniu do nietrwałych odczytów i zapisów (§15.5.4). Ponadto środowisko wykonawcze nie musi oceniać części wyrażenia, jeśli może on wyłudzać, że wartość tego wyrażenia nie jest używana i że nie są generowane żadne potrzebne efekty uboczne (w tym żadne spowodowane wywołaniem metody lub uzyskaniem dostępu do pola lotnego). Gdy wykonywanie programu zostanie przerwane przez zdarzenie asynchroniczne (takie jak wyjątek zgłoszony przez inny wątek), nie ma gwarancji, że obserwowane efekty uboczne są widoczne w oryginalnej kolejności programu.
ECMA C# draft specification