Udostępnij za pośrednictwem


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 to System.Threading.Tasks.Task lub System.Threading.Tasks.Task<int>.
  • Typ zwracany to void, , intSystem.Threading.Tasks.Tasklub System.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 lub async metoda zwracająca inny oczekiwany typ, taki jak ValueTask lub ValueTask<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 voidSystem.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 typ System.Threading.Tasks.Task metody syntetyzowanej tovoid
  • Jeśli zwracany typ metody to Main, zwracany typ System.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 jej string[] wartość parametru jako argument, jeśli Main 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 typem System.Threading.Tasks.Task<int>metody , jeśli zadanie zakończy się pomyślnie, int zwracana GetResult() 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ść nullodwoł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.SuppressFinalizebiblioteki ). 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. Zmienna z 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 i Megacorp.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ż nazwa i jest zadeklarowana w bloku zewnętrznym i nie można ponownie zadeklarować w bloku wewnętrznym. Metody i H są jednak prawidłowe, I ponieważ oba ite 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 objectbazowej .

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 klasie object 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 jest public "dostęp nie ograniczony".
  • Chroniony, który jest wybierany przez dołączenie protected modyfikatora w deklaracji elementu członkowskiego. Intuicyjne znaczenie protected 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 znaczenie internal jest "dostęp ograniczony do tego zestawu".
  • Chroniony element wewnętrzny, który jest wybierany przez dołączenie zarówno obiektu , jak protected i internal modyfikatora w deklaracji elementu członkowskiego. Intuicyjne znaczenie protected 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 i protected modyfikatora w deklaracji elementu członkowskiego. Intuicyjne znaczenie private 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 znaczenie private 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 lub internal 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 lub internal zadeklarowane ułatwienia dostępu. notatka końcowa

  • Elementy członkowskie struktury mogą mieć publicinternal, lub private zadeklarowane ułatwienia dostępu i domyślnie zadeklarowane private ułatwienia dostępu, ponieważ struktury są niejawnie zapieczętowane. Elementy członkowskie struktury wprowadzone w obiekcie struct (czyli nie dziedziczone przez strukturę) nie mogą mieć protected, protected internalani private protected zadeklarowanych ułatwień dostępu.

    Uwaga: typ zadeklarowany jako element członkowski struktury może mieć publicwartość , internallub private zadeklarowaną dostępność, natomiast typ zadeklarowany jako element członkowski przestrzeni nazw może mieć tylko public lub internal 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, intlub 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 tekst T programu i dowolny program, który odwołuje się do P.P
  • Jeśli zadeklarowana dostępność elementu jest wewnętrzna, domena ułatwień dostępu T jest tekstem Tprogramu .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 Pjest 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 jest public domeną ułatwień dostępu M .T
  • Jeśli zadeklarowane ułatwienia dostępu M to protected internal, niech D będzie unii tekstu P programu i tekstu programu dowolnego typu pochodzącego z T, który jest zadeklarowany poza P. Domena ułatwień dostępu programu M to skrzyżowanie domeny ułatwień dostępu w usłudze TD.
  • Jeśli zadeklarowana dostępność M elementu to private protected, pozwól D na przecięcie tekstu P programu i tekstu T programu i dowolnego typu pochodzącego z Tklasy . Domena ułatwień dostępu programu M to skrzyżowanie domeny ułatwień dostępu w usłudze TD.
  • Jeśli zadeklarowane ułatwienia dostępu M to protected, niech D będzie unii tekstu Tprogramu i tekstu programu dowolnego typu pochodzącego z T. Domena ułatwień dostępu programu M to skrzyżowanie domeny ułatwień dostępu w usłudze TD.
  • Jeśli zadeklarowane ułatwienia dostępu to M, domena ułatwień dostępu jest internal skrzyżowaniem domeny ułatwień dostępu M w T pliku z tekstem Pprogramu .
  • Jeśli zadeklarowane ułatwienia dostępu to M, domena ułatwień dostępu private programu M to tekst Tprogramu .

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 ma publicwartość , dostęp jest dozwolony.
  • W przeciwnym razie, jeśli M ma wartość , dostęp jest dozwolony, jeśli występuje w programie, w którym protected internal jest zadeklarowany, lub jeśli występuje w klasie pochodzącej z klasy, w której M jest zadeklarowany i odbywa się za pośrednictwem typu klasy pochodnej (M).
  • W przeciwnym razie, jeśli parametr ma Mwartość , 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órej protectedM jest zadeklarowany i odbywa się za pośrednictwem typu klasy pochodnej (M).
  • W przeciwnym razie, jeśli M ma internalwartość , dostęp jest dozwolony, jeśli występuje w programie, w którym M jest zadeklarowany.
  • W przeciwnym razie, jeśli M ma privatewartość , dostęp jest dozwolony, jeśli występuje w typie, w którym M 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.XB.YB.C, B.C.Xi B.C.Y jest tekstem programu zawierającym program.
  • Domena ułatwień dostępu programu A.Z to tekst Aprogramu .
  • Domena B.Z ułatwień dostępu i B.D jest tekstem Bprogramu , w tym tekstem B.C programu i B.D.
  • Domena ułatwień dostępu programu B.C.Z to tekst B.Cprogramu .
  • Domena B.D.X ułatwień dostępu i B.D.Y jest tekstem Bprogramu , w tym tekstem B.C programu i B.D.
  • Domena ułatwień dostępu programu B.D.Z to tekst B.Dprogramu . 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 wszyscy X członkowie mają publiczne zadeklarowane ułatwienia dostępu, wszystkie, ale A.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łonkowski x z A klasy. Ponieważ element członkowski jest prywatny, jest dostępny tylko w class_body .A W związku z tym dostęp do b.x metody zakończy się powodzeniem A.F , ale nie powiedzie się w metodzie B.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 Mwystąpienia chronionego i niech D będzie klasą pochodzącą z Bklasy . W class_bodyDprogramu 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 Aistnieje możliwość uzyskania dostępu x za pośrednictwem wystąpień obu A systemów i B, ponieważ w obu przypadkach dostęp odbywa się za pośrednictwem wystąpienia klasy lub klasy pochodzącej A z Aklasy . Jednak w programie Bnie można uzyskać dostępu x za pośrednictwem wystąpienia Aklasy , ponieważ A nie pochodzi z klasy B.

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łonkowskiego C<int>.x w D elemencie jest prawidłowe, mimo że klasa D pochodzi z klasy C<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 MT 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 jak B.

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 w B wyniku błędu czasu kompilacji, ponieważ zwracany typ A 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 lub this , 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 inmodyfikatory parametrów , outi ref są uważane za część podpisu, składowe zadeklarowane w jednym typie nie mogą różnić się podpisem wyłącznie według in, outi 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, outi 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 insystemach , outi ref. 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 inmodyfikatory parametrów , outi ref (§15.6.2) są częścią podpisu. W związku z tym wszystkie F(int)podpisy, F(in int)F(out int) i F(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 od F(out int), F(ref int)i in.outref Należy również pamiętać, że typ zwracany i params modyfikator nie są częścią podpisu, więc nie można przeciążyć wyłącznie na podstawie typu zwracanego lub włączenia lub wykluczenia params modyfikatora. W związku z tym deklaracje metod F(int) i F(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 to N lub zaczyna się od Nkropki.

  • 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_blockswitch 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ę do i 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 nie i 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 metodzie G użycie j elementu w inicjatorze deklaracji j jest prawidłowe, ponieważ użycie nie poprzedza deklaratora. W metodzie H 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 zmiennej A lokalnej i w kontekście typu, aby odwołać się do klasy A.

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ą iilokalną , ale w ramach G metody i nadal odwołuje się do zmiennej wystąpienia. Wewnątrz funkcji M1 lokalnej funkcja float i ukrywa bezpośrednio zewnętrzne i. Parametr i lambda ukrywa float 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 zadeklarowane F w Inner , ponieważ wszystkie wystąpienia F zewnętrzne obiektu są ukryte przez deklarację wewnętrzną. Z tego samego powodu wywołanie F("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 in Derived 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 wersja Base metody F , 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 element F in Derived 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 Fklasy , ale ponieważ nowy Derived w systemie F ma dostęp prywatny, jego zakres nie rozciąga się na Basewartość .FDerivedMoreDerived W związku z tym wywołanie F() metody w pliku MoreDerived.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 formularza I<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 nazwie I, 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 deklaracja T zawiera parametr typu o nazwie I, 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 nazwie I i x 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

    • W przeciwnym razie dla każdej przestrzeni Nnazw, 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 i I jest nazwą przestrzeni nazw w Npliku , 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_directiveItypem, 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 w Npliku .
      • W przeciwnym razie, jeśli N zawiera dostępny typ o nazwie I i x 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_directiveItypem, 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.
      • 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 i x 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 i x typu, namespace_or_type_name jest niejednoznaczny i wystąpi błąd.
    • W przeciwnym razie namespace_or_type_name jest niezdefiniowany i występuje błąd czasu kompilacji.
  • W przeciwnym razie namespace_or_type_name ma postać N.I lub formularz N.I<A₁, ..., Aₓ>. N jest najpierw rozpoznawany jako namespace_or_type_name. Jeśli rozwiązanie polecenia N nie powiedzie się, wystąpi błąd czasu kompilacji. N.I W przeciwnym razie rozwiązanie lub N.I<A₁, ..., Aₓ> jest rozwiązywane w następujący sposób:
    • Jeśli x ma wartość zero i N odwołuje się do przestrzeni nazw i N zawiera zagnieżdżonych przestrzeni nazw o nazwie I, 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 i N zawiera dostępny typ o parametrach nazwy I i x 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 i N lub dowolnej z jej klas bazowych zawierają zagnieżdżony dostępny typ o nazwie I i x 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 bazowej N klasy podstawowej, uważa się, że bezpośrednia klasa bazowa N klasy jest uważana object 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.

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 formularza T.Ilub
  • namespace_or_type_name jest T w wyrażeniu typeof (§12.8.18) w formie typeof(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 to N.
  • W przeciwnym razie jej w pełni kwalifikowana nazwa to S.N, gdzie S jest w pełni kwalifikowaną nazwą przestrzeni nazw lub deklaracji typu, w której N 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:

  1. Po utworzeniu obiektu zostanie przydzielona pamięć, zostanie uruchomiona konstruktor, a obiekt jest uznawany za żywy.
  2. 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

  3. 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.
  4. 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

  5. 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 klasy B. Te obiekty kwalifikują się do odzyskiwania pamięci, gdy zmienna b 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ć albo

Finalize 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 ABprogramu , 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 i Afinalizator został uruchomiony, nadal jest możliwe wywołanie metody A (w tym przypadku F) 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 uruchomienie Bfinalizatora spowodowało, że wystąpienie A elementu , które wcześniej nie było używane, stało się dostępne z poziomu odwołania Test.RefAna żywo. Po wywołaniu WaitForPendingFinalizersmetody , wystąpienie B klasy kwalifikuje się do kolekcji, ale wystąpienie elementu A nie jest spowodowane odwołaniem Test.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.