Udostępnij za pośrednictwem


6 Struktura leksykalna

6.1 Programy

Program w języku C# składa się z co najmniej jednego pliku źródłowego, znanego formalnie jako jednostki kompilacji (§14.2). Chociaż jednostka kompilacji może mieć korespondencję jeden do jednego z plikiem w systemie plików, taka korespondencja nie jest wymagana.

Mówiąc koncepcyjnie, program jest kompilowany przy użyciu trzech kroków:

  1. Przekształcenie, które konwertuje plik z określonego repertuaru znaków i schematu kodowania na sekwencję znaków Unicode.
  2. Analiza leksykalna, która tłumaczy strumień znaków wejściowych Unicode na strumień tokenów.
  3. Analiza składniowa, która tłumaczy strumień tokenów na kod wykonywalny.

Zgodne implementacje akceptują jednostki kompilacji Unicode zakodowane przy użyciu formularza kodowania UTF-8 (zgodnie z definicją standardu Unicode) i przekształcają je w sekwencję znaków Unicode. Implementacje mogą akceptować i przekształcać dodatkowe schematy kodowania znaków (takie jak UTF-16, UTF-32 lub mapowania znaków innych niż Unicode).

Uwaga: obsługa znaku NULL Unicode (U+0000) jest zdefiniowana przez implementację. Zdecydowanie zaleca się, aby deweloperzy unikali używania tego znaku w kodzie źródłowym ze względu na przenośność i czytelność. Jeśli znak jest wymagany w literału znaku lub ciągu, zamiast tego mogą być używane sekwencje ucieczki \0 \u0000 . notatka końcowa

Uwaga: Wykracza poza zakres tej specyfikacji, aby zdefiniować sposób przekształcania pliku przy użyciu reprezentacji znaków innych niż Unicode w sekwencję znaków Unicode. Podczas takiej transformacji zaleca się jednak, aby zwykły znak rozdzielania wierszy (lub sekwencja) w innym zestawie znaków został przetłumaczony na dwuznakową sekwencję składającą się z znaku powrotu karetki Unicode (U+000D), po którym następuje znak kanału liniowego Unicode (U+000A). W przeważającej części ta transformacja nie będzie miała widocznych efektów; jednak będzie to miało wpływ na interpretację tokenów literału ciągu dosłownego (§6.4.5.6). Celem tego zalecenia jest umożliwienie literału ciągu dosłownego utworzenia tej samej sekwencji znaków, gdy jednostka kompilacji jest przenoszona między systemami obsługującymi różne zestawy znaków innych niż Unicode, w szczególności tych, które używają różnych sekwencji znaków do separacji wierszy. notatka końcowa

6.2 Gramatyki

6.2.1 Ogólne

Ta specyfikacja przedstawia składnię języka programowania C# przy użyciu dwóch gramatyki. Gramatyka leksykalna (§6.2.3) definiuje sposób łączenia znaków Unicode z terminatorami wierszy, odstępami, komentarzami, tokenami i dyrektywami przetwarzania wstępnego. Gramatyka składniowa (§6.2.4) definiuje sposób łączenia tokenów wynikających z gramatyki leksykalnej w celu utworzenia programów w języku C#.

Wszystkie znaki terminalu należy rozumieć jako odpowiedni znak Unicode z zakresu U+0020 do U+007F, w przeciwieństwie do dowolnych podobnych znaków z innych zakresów znaków Unicode.

6.2.2 Notacja gramatyki

Gramatyki leksykalne i składniowe są prezentowane w formularzu Extended Backus-Naur narzędzia gramatyki ANTLR.

Chociaż jest używana notacja ANTLR, ta specyfikacja nie przedstawia kompletnej "gramatyki referencyjnej" dla języka C#; pisanie leksykatora i analizatora, ręcznie lub przy użyciu narzędzia takiego jak ANTLR, wykracza poza zakres specyfikacji języka. Z tą kwalifikacją ta specyfikacja próbuje zminimalizować lukę między określoną gramatyką a wymaganą do utworzenia leksykatora i analizatora w ANTLR.

ANTLR rozróżnia leksykalne i składniowe, terminowane analizatory przez ANTLR, gramatyki w notacji, rozpoczynając reguły leksykalne z wielką literą i regułami analizatora z małą literą.

Uwaga: Gramatyka leksykalna języka C# (§6.2.3) i gramatyka składniowa (§6.2.4) nie są dokładnie zgodne z podziałem ANTLR na grammery leksykalne i analizatora. Ta mała niezgodność oznacza, że niektóre reguły analizatora ANTLR są używane podczas określania gramatyki leksykalnej języka C#. notatka końcowa

6.2.3 Gramatyka leksykalna

Gramatyka leksykalna języka C# jest przedstawiona w §6.3, §6.4 i §6.5. Symbole końcowe gramatyki leksykalnej są znakami zestawu znaków Unicode, a gramatyka leksykalna określa, jak znaki są łączone z tokenami formularza (§6.4), odstępem (§6.3.4), komentarzami (§6.3.3) i dyrektywami przetwarzania wstępnego (§6.5).

Wiele symboli terminalu gramatyki składniowej nie jest zdefiniowanych jawnie jako tokeny w gramatyce leksykalnej. Zamiast tego zaletą jest zachowanie ANTLR, że ciągi literału w gramatyce są wyodrębniane jako niejawne tokeny leksykalne; Dzięki temu słowa kluczowe, operatory itp. mogą być reprezentowane w gramatyce przez ich reprezentację literału, a nie nazwę tokenu.

Każda jednostka kompilacji w programie C# jest zgodna z produkcją danych wejściowych gramatyki leksykalnej (§6.3.1).

6.2.4 Gramatyka składniowa

Gramatyka składni języka C# jest przedstawiona w klauzulach, podklasach i załącznikach, które są zgodne z tą podkatauzą. Symbole terminalu gramatyki składniowej to tokeny zdefiniowane jawnie przez gramatykę leksykatyczną i niejawnie przez ciągi literału w samej gramatyce (§6.2.3). Gramatyka składniowa określa sposób łączenia tokenów w celu tworzenia programów w języku C#.

Każda jednostka kompilacji w programie C# jest zgodna z compilation_unit produkcji (§14.2) gramatyki składniowej.

6.2.5 Niejednoznaczności gramatyczne

Produkcje dla simple_name (§12.8.4) i member_access (§12.8.7) mogą prowadzić do niejednoznaczności gramatyki w wyrażeniach.

Przykład: instrukcja :

F(G<A, B>(7));

można interpretować jako wywołanie metody F z dwoma argumentami i G < A B > (7). Alternatywnie można go interpretować jako wywołanie F z jednym argumentem, który jest wywołaniem metody G ogólnej z dwoma argumentami typu i jednym zwykłym argumentem.

przykład końcowy

Jeśli sekwencja tokenów może być analizowana (w kontekście) jako simple_name (§12.8.4), member_access (§12.8.7) lub pointer_member_access (§23.6.3) kończąca się type_argument_list (§8.4.2), token bezpośrednio po przeanalizowaniu tokenu zamykającego>, aby sprawdzić, czy jest to

  • ( ) ] } : ; , . ? == != | ^ && || & [Jeden z ; lub
  • Jeden z operatorów < <= >= is asrelacyjnych ; lub
  • Kontekstowe słowo kluczowe zapytania wyświetlane wewnątrz wyrażenia zapytania; lub
  • W niektórych kontekstach identyfikator jest traktowany jako niejednoznaczny token. Te konteksty to miejsce, w których sekwencja tokenów jest natychmiast poprzedzona jednym ze słów kluczowych islub case out, lub pojawia się podczas analizowania pierwszego elementu literału krotki (w tym przypadku tokeny są poprzedzone ( znakiem ,lub : i identyfikator następuje ) lub kolejny element literału krotki.

Jeśli następujący token znajduje się na tej liście lub identyfikator w takim kontekście, type_argument_list zostanie zachowana w ramach simple_name, member_access lub pointer_member dostępu , a każda inna możliwa analiza sekwencji tokenów zostanie odrzucona. W przeciwnym razie type_argument_list nie jest uważany za część simple_name, member_access lub pointer_member_access, nawet jeśli nie ma innej możliwej analizy sekwencji tokenów.

Uwaga: Te reguły nie są stosowane podczas analizowania type_argument_list w namespace_or_type_name (§7.8). notatka końcowa

Przykład: instrukcja :

F(G<A, B>(7));

zgodnie z tą regułą zostanie zinterpretowana jako wywołanie z jednym argumentem, który jest wywołaniem F metody G ogólnej z dwoma argumentami typu i jednym zwykłym argumentem. Instrukcje

F(G<A, B>7);
F(G<A, B>>7);

każda z nich będzie interpretowana jako wywołanie metody F z dwoma argumentami. Instrukcja

x = F<A> + y;

będzie interpretowany jako operator less-than, operator greater-than i jednoargumentowy plus, tak jakby instrukcja została napisana x = (F < A) > (+y), a nie jako simple_name z type_argument_list a następnie operatorem binarnym plus. W instrukcji

x = y is C<T> && z;

tokeny C<T> są interpretowane jako namespace_or_type_name z type_argument_list ze względu na obecność niejednoznacznego tokenu && po type_argument_list.

Wyrażenie (A < B, C > D) jest krotką z dwoma elementami, z których każde jest porównywane.

Wyrażenie (A<B,C> D, E) jest krotką z dwoma elementami, z których pierwszy jest wyrażeniem deklaracji.

Wywołanie M(A < B, C > D, E) ma trzy argumenty.

Wywołanie M(out A<B,C> D, E) ma dwa argumenty, z których pierwsza jest deklaracją out .

e is A<B> C Wyrażenie używa wzorca deklaracji.

Etykieta case A<B> C: wielkości liter używa wzorca deklaracji.

przykład końcowy

W przypadku rozpoznawania relational_expression (§12.12.1) w przypadku wyboru alternatywnej alternatywy "isrelational_expression" i "relational_expression constant_pattern is" i typ jest rozpoznawany jako dostępny typ, wybiera się alternatywę "relational_expression is typu".

6.3 Analiza leksykalna

6.3.1 Ogólne

Dla wygody gramatyka leksykalna definiuje i odwołuje się do następujących nazwanych tokenów leksykalnych:

DEFAULT  : 'default' ;
NULL     : 'null' ;
TRUE     : 'true' ;
FALSE    : 'false' ;
ASTERISK : '*' ;
SLASH    : '/' ;

Chociaż są to reguły leksykatora, te nazwy są pisane we wszystkich wielkich literach, aby odróżnić je od zwykłych nazw reguł leksykatora.

Uwaga: Te reguły wygody są wyjątkami od zwykłej praktyki nie podawania jawnych nazw tokenów dla tokenów zdefiniowanych przez ciągi literału. notatka końcowa

Produkcja wejściowa definiuje strukturę leksykalną jednostki kompilacji języka C#.

input
    : input_section?
    ;

input_section
    : input_section_part+
    ;

input_section_part
    : input_element* New_Line
    | PP_Directive
    ;

input_element
    : Whitespace
    | Comment
    | token
    ;

Uwaga: Powyższa gramatyka jest opisana przez reguły analizowania ANTLR, definiuje strukturę leksykatyczną jednostki kompilacji języka C#, a nie tokeny leksykalne. notatka końcowa

Pięć podstawowych elementów składa się ze struktury leksykalnej jednostki kompilacji języka C#: terminatory wierszy (§6.3.2), odstępy (§6.3.4), komentarze (§6.3.3), tokeny (§6.4) i dyrektywy przetwarzania wstępnego (§6.5). Spośród tych podstawowych elementów tylko tokeny są istotne w gramatyki składni programu C# (§6.2.4).

Przetwarzanie leksykalne jednostki kompilacji języka C# polega na zmniejszeniu pliku do sekwencji tokenów, która staje się danymi wejściowymi analizy składniowej. Terminatory wierszy, białe znaki i komentarze mogą służyć do oddzielania tokenów, a dyrektywy przetwarzania wstępnego mogą powodować pomijanie sekcji jednostki kompilacji, ale w przeciwnym razie te elementy leksykalne nie mają wpływu na strukturę składniową programu języka C#.

Gdy kilka produkcji gramatyki leksykalnej pasuje do sekwencji znaków w jednostce kompilacji, przetwarzanie leksykalne zawsze tworzy najdłuższy możliwy element leksykacyjny.

Przykład: Sekwencja // znaków jest przetwarzana jako początek komentarza jednowierszowego, ponieważ ten element leksyktyczny jest dłuższy niż pojedynczy / token. przykład końcowy

Niektóre tokeny są definiowane przez zestaw reguł leksykalnych; główna reguła i co najmniej jedna reguła podrzędna. Te ostatnie są oznaczone w gramatyce, fragment aby wskazać, że reguła definiuje część innego tokenu. Reguły fragmentów nie są uwzględniane w kolejności od góry do dołu reguł leksykalnych.

Uwaga: W ANTLR fragment jest słowem kluczowym, które generuje to samo zachowanie zdefiniowane tutaj. notatka końcowa

6.3.2 Terminatory wierszy

Terminatory wierszy dzielą znaki jednostki kompilacji języka C# na wiersze.

New_Line
    : New_Line_Character
    | '\u000D\u000A'    // carriage return, line feed 
    ;

Aby zapewnić zgodność z narzędziami do edycji kodu źródłowego, które dodają znaczniki końca pliku, i umożliwić wyświetlanie jednostki kompilacji jako sekwencja prawidłowo zakończonych wierszy, następujące przekształcenia są stosowane w celu wykonania każdej jednostki kompilacji w programie C#:

  • Jeśli ostatnim znakiem jednostki kompilacji jest znak Control-Z (U+001A), ten znak zostanie usunięty.
  • Znak powrotu karetki (U+000D) jest dodawany na końcu jednostki kompilacji, jeśli ta jednostka kompilacji jest niepusta i jeśli ostatni znak jednostki kompilacji nie jest znakiem powrotu karetki (U+000D), kanałem informacyjnym wiersza (U+000A), znakiem następnego wiersza (U+0085), separatorem wiersza (U+2028) lub separatorem akapitu (U+2029).

Uwaga: Dodatkowy zwrot karetki umożliwia programowi zakończenie PP_Directive (§6.5), który nie ma New_Line zakończenia. notatka końcowa

Komentarze 6.3.3

Obsługiwane są dwie formy komentarzy: komentarze rozdzielane i komentarze jednowierszowe.

Rozdzielany komentarz zaczyna się od znaków /* i kończy się znakami */. Komentarze rozdzielone mogą zajmować część wiersza, pojedynczego wiersza lub wielu wierszy.

Przykład: przykład

/* Hello, world program
   This program writes "hello, world" to the console
*/
class Hello
{
    static void Main()
    {
        System.Console.WriteLine("hello, world");
    }
}

zawiera rozdzielany komentarz.

przykład końcowy

Komentarz jednowierszowy zaczyna się od znaków // i rozciąga się na koniec wiersza.

Przykład: przykład

// Hello, world program
// This program writes "hello, world" to the console
//
class Hello // any name will do for this class
{
    static void Main() // this method must be named "Main"
    {
        System.Console.WriteLine("hello, world");
    }
}

pokazuje kilka komentarzy jednowierszowych.

przykład końcowy

Comment
    : Single_Line_Comment
    | Delimited_Comment
    ;

fragment Single_Line_Comment
    : '//' Input_Character*
    ;

fragment Input_Character
    // anything but New_Line_Character
    : ~('\u000D' | '\u000A'   | '\u0085' | '\u2028' | '\u2029')
    ;
    
fragment New_Line_Character
    : '\u000D'  // carriage return
    | '\u000A'  // line feed
    | '\u0085'  // next line
    | '\u2028'  // line separator
    | '\u2029'  // paragraph separator
    ;
    
fragment Delimited_Comment
    : '/*' Delimited_Comment_Section* ASTERISK+ '/'
    ;
    
fragment Delimited_Comment_Section
    : SLASH
    | ASTERISK* Not_Slash_Or_Asterisk
    ;

fragment Not_Slash_Or_Asterisk
    : ~('/' | '*')    // Any except SLASH or ASTERISK
    ;

Komentarze nie są zagnieżdżone. Sekwencje znaków /* i */ nie mają specjalnego znaczenia w komentarzu jednowierszowym, a sekwencje znaków // i /* nie mają specjalnego znaczenia w komentarzu z ogranicznikami.

Komentarze nie są przetwarzane w literałach znaków i ciągów.

Uwaga: te reguły muszą być dokładnie interpretowane. Na przykład w poniższym przykładzie rozdzielany komentarz, który rozpoczyna się przed końcem A między B i C(). Przyczyną jest to, że

// B */ C();

nie jest w rzeczywistości komentarzem jednowierszowym, ponieważ // nie ma specjalnego znaczenia w rozdzielonym komentarzu, a więc */ ma swoje zwykłe specjalne znaczenie w tej linii.

Podobnie rozdzielany komentarz rozpoczynający się przed końcem przed końcem D .E Przyczyną jest to, że "D */ " w rzeczywistości nie jest literałem ciągu, ponieważ początkowy znak podwójnego cudzysłowu pojawia się wewnątrz rozdzielanego komentarza.

Przydatną konsekwencją /* i */ bez specjalnego znaczenia w komentarzu jednowierszowym jest to, że blok wierszy kodu źródłowego można komentować, umieszczając // na początku każdego wiersza. Ogólnie rzecz biorąc, nie działa, aby umieścić /* przed tymi wierszami i */ po nich, ponieważ nie jest to poprawnie hermetyzowane komentarze w bloku, a ogólnie może całkowicie zmienić strukturę takich rozdzielonych komentarzy.

Przykładowy kod:

static void Main()
{
    /* A
    // B */ C();
    Console.WriteLine(/* "D */ "E");
}

notatka końcowa

Single_Line_Comment s i Delimited_Commento określonych formatach mogą być używane jako komentarze do dokumentacji, zgodnie z opisem w §D.

6.3.4 Biały znak

Odstęp jest definiowany jako dowolny znak z klasą Unicode Zs (w tym znak spacji), a także znak tabulatora poziomego, znak pionowej karty i znak kanału informacyjnego formularza.

Whitespace
    : [\p{Zs}]  // any character with Unicode class Zs
    | '\u0009'  // horizontal tab
    | '\u000B'  // vertical tab
    | '\u000C'  // form feed
    ;

6.4 Tokeny

6.4.1 Ogólne

Istnieje kilka rodzajów tokenów: identyfikatory, słowa kluczowe, literały, operatory i znaki interpunkcyjne. Białe znaki i komentarze nie są tokenami, choć działają jako separatory tokenów.

token
    : identifier
    | keyword
    | Integer_Literal
    | Real_Literal
    | Character_Literal
    | String_Literal
    | operator_or_punctuator
    ;

Uwaga: jest to reguła analizatora ANTLR, która nie definiuje tokenu leksyktycznego, ale raczej kolekcji rodzajów tokenów. notatka końcowa

6.4.2 Sekwencje ucieczki znaków Unicode

Sekwencja ucieczki Unicode reprezentuje punkt kodu Unicode. Sekwencje ucieczki Unicode są przetwarzane w identyfikatorach (§6.4.3), literały znaków (§6.4.5.5), zwykłe literały ciągu (§6.4.5.6) i interpolowane wyrażenia ciągów regularnych (§12.8.3). Sekwencja ucieczki Unicode nie jest przetwarzana w żadnej innej lokalizacji (na przykład w celu utworzenia operatora, interpunkcyjnego lub słowa kluczowego).

fragment Unicode_Escape_Sequence
    : '\\u' Hex_Digit Hex_Digit Hex_Digit Hex_Digit
    | '\\U' Hex_Digit Hex_Digit Hex_Digit Hex_Digit
            Hex_Digit Hex_Digit Hex_Digit Hex_Digit
    ;

Sekwencja ucieczki znaków Unicode reprezentuje pojedynczy punkt kodu Unicode utworzony przez liczbę szesnastkową po znakach "\u" lub "\U". Ponieważ język C# używa 16-bitowego kodowania punktów kodu Unicode w wartościach znaków i ciągów, punkt kodu Unicode w zakresie U+10000 U+10FFFF jest reprezentowany przy użyciu dwóch jednostek kodu zastępczego Unicode. Powyższe U+FFFF punkty kodu Unicode nie są dozwolone w literałach znaków. Powyższe U+10FFFF punkty kodu Unicode są nieprawidłowe i nie są obsługiwane.

Nie wykonano wielu tłumaczeń. Na przykład literał "\u005Cu005C" ciągu jest odpowiednikiem, a "\u005C" nie "\".

Uwaga: Wartość \u005C Unicode jest znakiem "\". notatka końcowa

Przykład: przykład

class Class1
{
    static void Test(bool \u0066)
    {
        char c = '\u0066';
        if (\u0066)
        {
            System.Console.WriteLine(c.ToString());
        }
    }
}

pokazuje kilka zastosowań \u0066, które są sekwencją ucieczki dla litery "f". Program jest odpowiednikiem

class Class1
{
    static void Test(bool f)
    {
        char c = 'f';
        if (f)
        {
            System.Console.WriteLine(c.ToString());
        }
    }
}

przykład końcowy

6.4.3 Identyfikatory

Reguły dotyczące identyfikatorów podanych w tym podklasie odpowiadają dokładnie tym, które są zalecane przez załącznik Standard Unicode 15 z wyjątkiem tego, że podkreślenie jest dozwolone jako znak początkowy (podobnie jak w języku programowania C), sekwencje ucieczki Unicode są dozwolone w identyfikatorach, a znak "@" jest dozwolony jako prefiks, aby umożliwić używanie słów kluczowych jako identyfikatorów.

identifier
    : Simple_Identifier
    | contextual_keyword
    ;

Simple_Identifier
    : Available_Identifier
    | Escaped_Identifier
    ;

fragment Available_Identifier
    // excluding keywords or contextual keywords, see note below
    : Basic_Identifier
    ;

fragment Escaped_Identifier
    // Includes keywords and contextual keywords prefixed by '@'.
    // See note below.
    : '@' Basic_Identifier 
    ;

fragment Basic_Identifier
    : Identifier_Start_Character Identifier_Part_Character*
    ;

fragment Identifier_Start_Character
    : Letter_Character
    | Underscore_Character
    ;

fragment Underscore_Character
    : '_'               // underscore
    | '\\u005' [fF]     // Unicode_Escape_Sequence for underscore
    | '\\U0000005' [fF] // Unicode_Escape_Sequence for underscore
    ;

fragment Identifier_Part_Character
    : Letter_Character
    | Decimal_Digit_Character
    | Connecting_Character
    | Combining_Character
    | Formatting_Character
    ;

fragment Letter_Character
    // Category Letter, all subcategories; category Number, subcategory letter.
    : [\p{L}\p{Nl}]
    // Only escapes for categories L & Nl allowed. See note below.
    | Unicode_Escape_Sequence
    ;

fragment Combining_Character
    // Category Mark, subcategories non-spacing and spacing combining.
    : [\p{Mn}\p{Mc}]
    // Only escapes for categories Mn & Mc allowed. See note below.
    | Unicode_Escape_Sequence
    ;

fragment Decimal_Digit_Character
    // Category Number, subcategory decimal digit.
    : [\p{Nd}]
    // Only escapes for category Nd allowed. See note below.
    | Unicode_Escape_Sequence
    ;

fragment Connecting_Character
    // Category Punctuation, subcategory connector.
    : [\p{Pc}]
    // Only escapes for category Pc allowed. See note below.
    | Unicode_Escape_Sequence
    ;

fragment Formatting_Character
    // Category Other, subcategory format.
    : [\p{Cf}]
    // Only escapes for category Cf allowed, see note below.
    | Unicode_Escape_Sequence
    ;

Uwaga:

  • Aby uzyskać informacje na temat klas znaków Unicode wymienionych powyżej, zobacz Standard Unicode.
  • Fragment Available_Identifier wymaga wykluczenia słów kluczowych i słów kluczowych kontekstowych. Jeśli gramatyka w tej specyfikacji jest przetwarzana z ANTLR, to wykluczenie jest obsługiwane automatycznie przez semantyka ANTLR:
    • Słowa kluczowe i kontekstowe słowa kluczowe występują w gramatyce jako ciągi literału.
    • Narzędzie ANTLR tworzy niejawne reguły tokenów leksykalnych tworzone na podstawie tych ciągów literału.
    • ANTLR uwzględnia te niejawne reguły przed jawnymi regułami leksykalnymi w gramatyce.
    • W związku z tym fragment Available_Identifier nie będzie pasować do słów kluczowych ani słów kluczowych kontekstowych jako reguł leksykalnych dla powyższych.
  • Fragment Escaped_Identifier zawiera słowa kluczowe ucieczki i kontekstowe słowa kluczowe, ponieważ są one częścią dłuższego tokenu rozpoczynającego się od @ przetwarzania leksyktycznego i zawsze tworzy najdłuższy możliwy element leksykalny (§6.3.1).
  • W jaki sposób implementacja wymusza ograniczenia dotyczące dozwolonych wartości Unicode_Escape_Sequence jest problemem z implementacją.

notatka końcowa

Przykład: Przykłady prawidłowych identyfikatorów to identifier1, _identifier2i @if. przykład końcowy

Identyfikator w programie zgodnym ma format kanoniczny zdefiniowany przez formularz normalizacji Unicode C, zgodnie z definicją w standardzie Unicode Załącznik 15. Zachowanie podczas napotkania identyfikatora, który nie znajduje się w formularzu normalizacji C, jest definiowane przez implementację; jednak diagnostyka nie jest wymagana.

Prefiks "@" umożliwia używanie słów kluczowych jako identyfikatorów, co jest przydatne podczas łączenia się z innymi językami programowania. Znak @ nie jest faktycznie częścią identyfikatora, więc identyfikator może być widoczny w innych językach jako normalny identyfikator, bez prefiksu. Identyfikator z prefiksem @ jest nazywany identyfikatorem dosłownym.

Uwaga: używanie prefiksu @ dla identyfikatorów, które nie są słowami kluczowymi, jest dozwolone, ale zdecydowanie zniechęcane jako kwestia stylu. notatka końcowa

Przykład: przykład:

class @class
{
    public static void @static(bool @bool)
    {
        if (@bool)
        {
            System.Console.WriteLine("true");
        }
        else
        {
            System.Console.WriteLine("false");
        }
    }
}

class Class1
{
    static void M()
    {
        cl\u0061ss.st\u0061tic(true);
    }
}

definiuje klasę o nazwie "class" ze statyczną metodą o nazwie "static", która przyjmuje parametr o nazwie "bool". Należy pamiętać, że ponieważ znaki ucieczki Unicode nie są dozwolone w słowach kluczowych, token "cl\u0061ss" jest identyfikatorem i jest tym samym identyfikatorem co "@class".

przykład końcowy

Dwa identyfikatory są traktowane tak samo, jeśli są identyczne po zastosowaniu następujących przekształceń:

  • Prefiks "@", jeśli jest używany, jest usuwany.
  • Każda Unicode_Escape_Sequence jest przekształcana w odpowiadający mu znak Unicode.
  • Wszystkie Formatting_Charactersą usuwane.

Semantyka identyfikatora o nazwie _ zależy od kontekstu, w którym się pojawia:

  • Może on oznaczać nazwany element programu, taki jak zmienna, klasa lub metoda lub
  • Może on oznaczać odrzucenie (§9.2.9.1).

Identyfikatory zawierające dwa kolejne znaki podkreślenia (U+005F) są zarezerwowane do użytku przez implementację; jednak nie jest wymagana żadna diagnostyka, jeśli taki identyfikator jest zdefiniowany.

Uwaga: na przykład implementacja może zawierać rozszerzone słowa kluczowe rozpoczynające się od dwóch podkreśleń. notatka końcowa

Słowa kluczowe 6.4.4

Słowo kluczowe jest sekwencją podobną do identyfikatora znaków, która jest zarezerwowana i nie może być używana jako identyfikator, z wyjątkiem sytuacji, gdy znak jest poprzedzony znakiem @ .

keyword
    : 'abstract' | 'as'       | 'base'       | 'bool'      | 'break'
    | 'byte'     | 'case'     | 'catch'      | 'char'      | 'checked'
    | 'class'    | 'const'    | 'continue'   | 'decimal'   | DEFAULT
    | 'delegate' | 'do'       | 'double'     | 'else'      | 'enum'
    | 'event'    | 'explicit' | 'extern'     | FALSE       | 'finally'
    | 'fixed'    | 'float'    | 'for'        | 'foreach'   | 'goto'
    | 'if'       | 'implicit' | 'in'         | 'int'       | 'interface'
    | 'internal' | 'is'       | 'lock'       | 'long'      | 'namespace'
    | 'new'      | NULL       | 'object'     | 'operator'  | 'out'
    | 'override' | 'params'   | 'private'    | 'protected' | 'public'
    | 'readonly' | 'ref'      | 'return'     | 'sbyte'     | 'sealed'
    | 'short'    | 'sizeof'   | 'stackalloc' | 'static'    | 'string'
    | 'struct'   | 'switch'   | 'this'       | 'throw'     | TRUE
    | 'try'      | 'typeof'   | 'uint'       | 'ulong'     | 'unchecked'
    | 'unsafe'   | 'ushort'   | 'using'      | 'virtual'   | 'void'
    | 'volatile' | 'while'
    ;

Kontekstowe słowo kluczowe to sekwencja znaków przypominająca identyfikator, która ma specjalne znaczenie w niektórych kontekstach, ale nie jest zarezerwowana i może być używana jako identyfikator poza tymi kontekstami, a także w przypadku poprzedzania znakiem@.

contextual_keyword
    : 'add'    | 'alias'      | 'ascending' | 'async'     | 'await'
    | 'by'     | 'descending' | 'dynamic'   | 'equals'    | 'from'
    | 'get'    | 'global'     | 'group'     | 'into'      | 'join'
    | 'let'    | 'nameof'     | 'on'        | 'orderby'   | 'partial'
    | 'remove' | 'select'     | 'set'       | 'unmanaged' | 'value'
    | 'var'    | 'when'       | 'where'     | 'yield'
    ;

Uwaga: słowo kluczowe reguły i contextual_keyword są regułami analizatora, ponieważ nie wprowadzają nowych rodzajów tokenów. Wszystkie słowa kluczowe i słowa kluczowe kontekstowe są definiowane przez niejawne reguły leksykalne, ponieważ występują one jako ciągi literału w gramatyce (§6.2.3). notatka końcowa

W większości przypadków składniowa lokalizacja słów kluczowych kontekstowych jest taka, że nigdy nie można ich mylić ze zwykłym użyciem identyfikatora. Na przykład w deklaracji get właściwości identyfikatory i set mają specjalne znaczenie (§15.7.3). Identyfikator inny niż get lub set nigdy nie jest dozwolony w tych lokalizacjach, więc to użycie nie powoduje konfliktu z użyciem tych słów jako identyfikatorów.

W niektórych przypadkach gramatyka nie wystarczy, aby odróżnić kontekstowe użycie słowa kluczowego od identyfikatorów. We wszystkich takich przypadkach zostanie określony sposób uściślania między nimi. Na przykład słowo kluczowe var kontekstowe w niejawnie typizowanej deklaracji zmiennych lokalnych (§13.6.2) może powodować konflikt z zadeklarowanym typem o nazwie var, w tym przypadku zadeklarowana nazwa ma pierwszeństwo przed użyciem identyfikatora jako kontekstowego słowa kluczowego.

Innym przykładem takiego uściślania jest kontekstowe słowo kluczowe await (§12.9.8.1), które jest uważane za słowo kluczowe tylko wtedy, gdy wewnątrz zadeklarowanej asyncmetody , ale może być używane jako identyfikator gdzie indziej.

Podobnie jak w przypadku słów kluczowych słowa kluczowe kontekstowe mogą być używane jako zwykłe identyfikatory, prefiksując je znakiem @ .

Uwaga: jeśli są używane jako słowa kluczowe kontekstowe, te identyfikatory nie mogą zawierać Unicode_Escape_Sequences. notatka końcowa

Literały 6.4.5

6.4.5.1 Ogólne

Literał (§12.8.2) to reprezentacja wartości w kodzie źródłowym.

literal
    : boolean_literal
    | Integer_Literal
    | Real_Literal
    | Character_Literal
    | String_Literal
    | null_literal
    ;

Uwaga: literał jest regułą analizatora, ponieważ grupuje inne rodzaje tokenów i nie wprowadza nowego rodzaju tokenu. notatka końcowa

6.4.5.2 Literały logiczne

Istnieją dwie wartości literału logicznego: true i false.

boolean_literal
    : TRUE
    | FALSE
    ;

Uwaga: boolean_literal jest regułą analizatora, ponieważ grupuje inne rodzaje tokenów i nie wprowadza nowego rodzaju tokenu. notatka końcowa

Typ boolean_literal to bool.

Literały liczb całkowitych 6.4.5.3

Literały liczb całkowitych służą do zapisywania wartości typów int, , uintlongi ulong. Literały liczb całkowitych mają trzy możliwe formy: dziesiętne, szesnastkowe i binarne.

Integer_Literal
    : Decimal_Integer_Literal
    | Hexadecimal_Integer_Literal
    | Binary_Integer_Literal
    ;

fragment Decimal_Integer_Literal
    : Decimal_Digit Decorated_Decimal_Digit* Integer_Type_Suffix?
    ;

fragment Decorated_Decimal_Digit
    : '_'* Decimal_Digit
    ;
       
fragment Decimal_Digit
    : '0'..'9'
    ;
    
fragment Integer_Type_Suffix
    : 'U' | 'u' | 'L' | 'l' |
      'UL' | 'Ul' | 'uL' | 'ul' | 'LU' | 'Lu' | 'lU' | 'lu'
    ;
    
fragment Hexadecimal_Integer_Literal
    : ('0x' | '0X') Decorated_Hex_Digit+ Integer_Type_Suffix?
    ;

fragment Decorated_Hex_Digit
    : '_'* Hex_Digit
    ;
       
fragment Hex_Digit
    : '0'..'9' | 'A'..'F' | 'a'..'f'
    ;
   
fragment Binary_Integer_Literal
    : ('0b' | '0B') Decorated_Binary_Digit+ Integer_Type_Suffix?
    ;

fragment Decorated_Binary_Digit
    : '_'* Binary_Digit
    ;
       
fragment Binary_Digit
    : '0' | '1'
    ;

Typ literału liczby całkowitej jest określany w następujący sposób:

  • Jeśli literał nie ma sufiksu, ma pierwszy z tych typów, w których można przedstawić jego wartość: int, , uint, long. ulong
  • Jeśli literał ma sufiks U lub u, ma pierwszy z tych typów, w których można przedstawić jego wartość: uint, ulong.
  • Jeśli literał ma sufiks L lub l, ma pierwszy z tych typów, w których można przedstawić jego wartość: long, ulong.
  • Jeśli literał ma sufiks UL, , ulLuUlLUlUuLlub lu, jest typu .ulong

Jeśli wartość reprezentowana przez literał liczby całkowitej znajduje się poza zakresem ulong typu, wystąpi błąd czasu kompilacji.

Uwaga: W przypadku stylu sugerowane jest użycie znaku "L" zamiast "l" podczas pisania literałów typu long, ponieważ łatwo jest pomylić literę "" z cyfrą "l1". notatka końcowa

Aby zezwolić na zapisanie najmniejszych możliwych int wartości i long jako literałów liczb całkowitych, istnieją następujące dwie reguły:

  • Gdy Integer_Literal reprezentująca wartość 2147483648 (2³³) i nie Integer_Type_Suffix pojawia się jako token bezpośrednio po jednoargumentowym tokenie operatora minus (§12.9.3), wynik (obu tokenów) jest stałą typu int z wartością −2147483648 (−2³²). We wszystkich innych sytuacjach taki Integer_Literal jest typu uint.
  • Gdy Integer_Literal reprezentująca wartość 9223372036854775808 (2⁶³) i nie Integer_Type_Suffix lub Integer_Type_Suffix L lub l pojawia się jako token bezpośrednio po jednoargumentowym tokenie minus (§12.9.3), wynik (obu tokenów) jest stałą typu long o wartości −9223372036854775808 (−2⁶³). We wszystkich innych sytuacjach taki Integer_Literal jest typu ulong.

Przykład:

123                  // decimal, int
10_543_765Lu         // decimal, ulong
1_2__3___4____5      // decimal, int
_123                 // not a numeric literal; identifier due to leading _
123_                 // invalid; no trailing _allowed

0xFf                 // hex, int
0X1b_a0_44_fEL       // hex, long
0x1ade_3FE1_29AaUL   // hex, ulong
0x_abc               // hex, int
_0x123               // not a numeric literal; identifier due to leading _
0xabc_               // invalid; no trailing _ allowed

0b101                // binary, int
0B1001_1010u         // binary, uint
0b1111_1111_0000UL   // binary, ulong
0B__111              // binary, int
__0B111              // not a numeric literal; identifier due to leading _
0B111__              // invalid; no trailing _ allowed

przykład końcowy

6.4.5.4 Literały rzeczywiste

Literały rzeczywiste są używane do zapisywania wartości typów float, doublei decimal.

Real_Literal
    : Decimal_Digit Decorated_Decimal_Digit* '.'
      Decimal_Digit Decorated_Decimal_Digit* Exponent_Part? Real_Type_Suffix?
    | '.' Decimal_Digit Decorated_Decimal_Digit* Exponent_Part? Real_Type_Suffix?
    | Decimal_Digit Decorated_Decimal_Digit* Exponent_Part Real_Type_Suffix?
    | Decimal_Digit Decorated_Decimal_Digit* Real_Type_Suffix
    ;

fragment Exponent_Part
    : ('e' | 'E') Sign? Decimal_Digit Decorated_Decimal_Digit*
    ;

fragment Sign
    : '+' | '-'
    ;

fragment Real_Type_Suffix
    : 'F' | 'f' | 'D' | 'd' | 'M' | 'm'
    ;

Jeśli nie określono Real_Type_Suffix , typ Real_Literal to double. W przeciwnym razie Real_Type_Suffix określa typ literału rzeczywistego w następujący sposób:

  • Prawdziwy literał sufiksowany przez F lub f ma typ float.

    Przykład: literały 1f, 1.5f, 1e10fi 123.456F są typu float. przykład końcowy

  • Prawdziwy literał sufiksowany przez D lub d ma typ double.

    Przykład: literały 1d, 1.5d, 1e10di 123.456D są typu double. przykład końcowy

  • Prawdziwy literał sufiksowany przez M lub m ma typ decimal.

    Przykład: literały 1m, 1.5m, 1e10mi 123.456M są typu decimal. przykład końcowy
    Ten literał jest konwertowany na wartość, przyjmując dokładną decimal wartość, a w razie potrzeby zaokrąglając do najbliższej wartości reprezentującej przy użyciu zaokrąglania bankiera (§8.3.8). Każda skala widoczna w literału jest zachowywana, chyba że wartość jest zaokrąglona. Uwaga: W związku z tym literał 2.900m zostanie przeanalizowany w celu utworzenia decimal znaku 0, współczynnika 2900i skali 3. notatka końcowa

Jeśli wielkość określonego literału jest zbyt duża, aby być reprezentowana w wskazanym typie, wystąpi błąd czasu kompilacji.

Uwaga: W szczególności Real_Literal nigdy nie będzie produkować nieskończoności zmiennoprzecinkowych. Niezerowa Real_Literal może być jednak zaokrąglona do zera. notatka końcowa

Wartość rzeczywistego literału typu float lub double jest określana przy użyciu trybu IEC 60559 "zaokrąglić do najbliższego" z powiązaniami zerwanymi z "parzystymi" (wartością z wartością zero najmniej znaczącą), a wszystkie cyfry uważane za znaczące.

Uwaga: W rzeczywistym literału cyfry dziesiętne są zawsze wymagane po przecinku dziesiętnym. Na przykład jest prawdziwym literałem, 1.3F ale 1.F nie. notatka końcowa

Przykład:

1.234_567      // double
.3e5f          // float
2_345E-2_0     // double
15D            // double
19.73M         // decimal
1.F            // parsed as a member access of F due to non-digit after .
1_.2F          // invalid; no trailing _ allowed in integer part
1._234         // parsed as a member access of _234 due to non-digit after .
1.234_         // invalid; no trailing _ allowed in fraction
.3e_5F         // invalid; no leading _ allowed in exponent
.3e5_F         // invalid; no trailing _ allowed in exponent

przykład końcowy

6.4.5.5 Literały znaków

Literał znaku reprezentuje pojedynczy znak i składa się z znaku w cudzysłowie, jak w 'a'.

Character_Literal
    : '\'' Character '\''
    ;
    
fragment Character
    : Single_Character
    | Simple_Escape_Sequence
    | Hexadecimal_Escape_Sequence
    | Unicode_Escape_Sequence
    ;
    
fragment Single_Character
    // anything but ', \, and New_Line_Character
    : ~['\\\u000D\u000A\u0085\u2028\u2029]
    ;
    
fragment Simple_Escape_Sequence
    : '\\\'' | '\\"' | '\\\\' | '\\0' | '\\a' | '\\b' |
      '\\f' | '\\n' | '\\r' | '\\t' | '\\v'
    ;
    
fragment Hexadecimal_Escape_Sequence
    : '\\x' Hex_Digit Hex_Digit? Hex_Digit? Hex_Digit?
    ;

Uwaga: Znak, który jest zgodny z znakiem ukośnika odwrotnego (\) w znaku musi być jednym z następujących znaków: ', avxUutrf0n"\b. W przeciwnym razie wystąpi błąd czasu kompilacji. notatka końcowa

Uwaga: użycie \x Hexadecimal_Escape_Sequence produkcji może być podatne na błędy i trudne do odczytania ze względu na zmienną liczbę cyfr szesnastkowe po \x. Na przykład w kodzie:

string good = "\x9Good text";
string bad = "\x9Bad text";

Początkowo może się wydawać, że znak wiodący jest taki sam (U+0009znak tabulacji) w obu ciągach. W rzeczywistości drugi ciąg zaczyna się od U+9BAD wszystkich trzech liter w słowie "Bad" są prawidłowymi cyframi szesnastkowymi. W ramach stylu zaleca \x się unikać konkretnej sekwencji ucieczki (\t w tym przykładzie) lub sekwencji ucieczki o stałej długości \u .

notatka końcowa

Sekwencja ucieczki szesnastkowej reprezentuje pojedynczą jednostkę kodu Unicode UTF-16 z wartością utworzoną przez liczbę szesnastkową po "\x".

Jeśli wartość reprezentowana przez literał znaku jest większa niż U+FFFF, wystąpi błąd czasu kompilacji.

Sekwencja ucieczki Unicode (§6.4.2) w literału znaku musi znajdować się w zakresie U+0000 do U+FFFF.

Prosta sekwencja ucieczki reprezentuje znak Unicode, zgodnie z opisem w poniższej tabeli.

Sekwencja ucieczki Nazwa znaku Punkt kodu Unicode
\' Pojedynczy cudzysłów U+0027
\" Podwójny cudzysłów U+0022
\\ Ukośnik odwrotny U+005C
\0 Null (zero) U+0000
\a Alerty U+0007
\b Backspace U+0008
\f Źródło danych formularzy U+000C
\n Nowy wiersz U+000A
\r Powrót karetki U+000D
\t Karta Pozioma U+0009
\v Karta pionowa U+000B

Typ Character_Literal to char.

Literały ciągu 6.4.5.6

Język C# obsługuje dwie formy literałów ciągów: zwykłe literały ciągu i literały ciągu dosłownego. Zwykły literał ciągu składa się z zera lub większej liczby znaków ujętych w podwójny cudzysłów, jak i "hello"może zawierać zarówno proste sekwencje ucieczki (takie jak \t znak tabulacji) oraz sekwencje ucieczki Unicode i szesnastkowe i Unicode.

Literał ciągu dosłownego @ składa się z znaku, po którym następuje znak podwójnego cudzysłowu, zero lub więcej znaków oraz znak podwójnego cudzysłowu zamykającego.

Przykład: prostym przykładem jest @"hello". przykład końcowy

W literału ciągu dosłownego znaki między ogranicznikami są interpretowane dosłownie, a jedynym wyjątkiem jest Quote_Escape_Sequence, który reprezentuje jeden znak podwójnego cudzysłowu. W szczególności proste sekwencje ucieczki, a sekwencje ucieczki szesnastkowe i Unicode nie są przetwarzane w literałach ciągu dosłownego. Literał ciągu dosłownego może obejmować wiele wierszy.

String_Literal
    : Regular_String_Literal
    | Verbatim_String_Literal
    ;
    
fragment Regular_String_Literal
    : '"' Regular_String_Literal_Character* '"'
    ;
    
fragment Regular_String_Literal_Character
    : Single_Regular_String_Literal_Character
    | Simple_Escape_Sequence
    | Hexadecimal_Escape_Sequence
    | Unicode_Escape_Sequence
    ;

fragment Single_Regular_String_Literal_Character
    // anything but ", \, and New_Line_Character
    : ~["\\\u000D\u000A\u0085\u2028\u2029]
    ;

fragment Verbatim_String_Literal
    : '@"' Verbatim_String_Literal_Character* '"'
    ;
    
fragment Verbatim_String_Literal_Character
    : Single_Verbatim_String_Literal_Character
    | Quote_Escape_Sequence
    ;
    
fragment Single_Verbatim_String_Literal_Character
    : ~["]     // anything but quotation mark (U+0022)
    ;
    
fragment Quote_Escape_Sequence
    : '""'
    ;

Przykład: przykład

string a = "Happy birthday, Joel"; // Happy birthday, Joel
string b = @"Happy birthday, Joel"; // Happy birthday, Joel
string c = "hello \t world"; // hello world
string d = @"hello \t world"; // hello \t world
string e = "Joe said \"Hello\" to me"; // Joe said "Hello" to me
string f = @"Joe said ""Hello"" to me"; // Joe said "Hello" to me
string g = "\\\\server\\share\\file.txt"; // \\server\share\file.txt
string h = @"\\server\share\file.txt"; // \\server\share\file.txt
string i = "one\r\ntwo\r\nthree";
string j = @"one
two
three";

wyświetla różne literały ciągów. Ostatni literał ciągu, j, to literał ciągu dosłownego, który obejmuje wiele wierszy. Znaki między znakami cudzysłowu, w tym odstępami, takimi jak nowe znaki wiersza, są zachowywane dosłownie, a każda para znaków podwójnego cudzysłowu jest zastępowana jednym takim znakiem.

przykład końcowy

Uwaga: wszystkie podziały wierszy w literałach ciągu dosłownego są częścią wynikowego ciągu. Jeśli dokładne znaki używane do tworzenia podziałów wierszy są semantycznie istotne dla aplikacji, wszystkie narzędzia, które tłumaczą podziały wierszy w kodzie źródłowym na różne formaty (między "\n" i "\r\n", na przykład), zmieni zachowanie aplikacji. Deweloperzy powinni zachować ostrożność w takich sytuacjach. notatka końcowa

Uwaga: Ponieważ sekwencja ucieczki szesnastkowej może mieć zmienną liczbę cyfr szesnastkowych, literał "\x123" ciągu zawiera pojedynczy znak z wartością 123szesnastkową . Aby utworzyć ciąg zawierający znak z wartością 12 szesnastkową, po którym następuje znak 3, można napisać "\x00123" lub "\x12" + "3" zamiast tego. notatka końcowa

Typ String_Literal to string.

Każdy literał ciągu nie musi powodować wystąpienia nowego ciągu. Gdy co najmniej dwa literały ciągu, które są równoważne operatorowi równości ciągów (§12.12.8), pojawiają się w tym samym zestawie, te literały ciągu odwołują się do tego samego wystąpienia ciągu.

Przykład: na przykład dane wyjściowe generowane przez program

class Test
{
    static void Main()
    {
        object a = "hello";
        object b = "hello";
        System.Console.WriteLine(a == b);
    }
}

Jest to True spowodowane tym, że dwa literały odwołują się do tego samego wystąpienia ciągu.

przykład końcowy

6.4.5.7 Literał null

null_literal
    : NULL
    ;

Uwaga: null_literal jest regułą analizatora, ponieważ nie wprowadza nowego rodzaju tokenu. notatka końcowa

Null_literal reprezentuje null wartość. Nie ma typu, ale można go przekonwertować na dowolny typ odwołania lub typ wartości dopuszczanej wartości null za pomocą konwersji literału null (§10.2.7).

6.4.6 Operatory i znaki interpunkcyjne

Istnieje kilka rodzajów operatorów i znaków interpunkcyjnych. Operatory są używane w wyrażeniach do opisywania operacji obejmujących co najmniej jeden operand.

Przykład: wyrażenie a + b używa + operatora , aby dodać dwa operandy a i b. przykład końcowy

Znaki interpunkcyjne służą do grupowania i oddzielania.

operator_or_punctuator
    : '{'  | '}'  | '['  | ']'  | '('   | ')'  | '.'  | ','  | ':'  | ';'
    | '+'  | '-'  | ASTERISK    | SLASH | '%'  | '&'  | '|'  | '^'  | '!' | '~'
    | '='  | '<'  | '>'  | '?'  | '??'  | '::' | '++' | '--' | '&&' | '||'
    | '->' | '==' | '!=' | '<=' | '>='  | '+=' | '-=' | '*=' | '/=' | '%='
    | '&=' | '|=' | '^=' | '<<' | '<<=' | '=>'
    ;

right_shift
    : '>'  '>'
    ;

right_shift_assignment
    : '>' '>='
    ;

Uwaga: right_shift i right_shift_assignment są regułami analizatora, ponieważ nie wprowadzają nowego rodzaju tokenu, ale reprezentują sekwencję dwóch tokenów. Reguła operator_or_punctuator istnieje tylko do celów opisowych i nie jest używana w innych miejscach gramatyki. notatka końcowa

right_shift składa się z dwóch tokenów > i >. Podobnie right_shift_assignment składa się z dwóch tokenów > i >=. W przeciwieństwie do innych produkcji w gramatyce składniowej żadne znaki żadnego rodzaju (nawet białe znaki) nie są dozwolone między dwoma tokenami w każdej z tych produkcji. Te produkcje są traktowane specjalnie w celu umożliwienia prawidłowej obsługi type_parameter_lists (§15.2.3).

Uwaga: przed dodaniu typów ogólnych do języka C#i >> >>= były zarówno pojedynczymi tokenami. Jednak składnia dla typów ogólnych używa < znaków i > do rozdzielania parametrów typu i argumentów typu. Często pożądane jest użycie zagnieżdżonych typów skonstruowanych, takich jak List<Dictionary<string, int>>. Zamiast wymagać od programisty oddzielenia > wartości i > przez spację, definicja dwóch operator_or_punctuatorzostała zmieniona. notatka końcowa

6.5 Dyrektywy przetwarzania wstępnego

6.5.1 Ogólne

Dyrektywy przetwarzania wstępnego zapewniają możliwość warunkowego pomijania sekcji jednostek kompilacji, zgłaszania błędów i warunków ostrzegawczych, oznaczania odrębnych regionów kodu źródłowego i ustawiania kontekstu dopuszczalnego wartości null.

Uwaga: Termin "dyrektywy przetwarzania wstępnego" jest używany tylko do spójności z językami programowania C i C++. W języku C#nie ma oddzielnego kroku przetwarzania wstępnego; Dyrektywy przetwarzania wstępnego są przetwarzane w ramach fazy analizy leksykalnej. notatka końcowa

PP_Directive
    : PP_Start PP_Kind PP_New_Line
    ;

fragment PP_Kind
    : PP_Declaration
    | PP_Conditional
    | PP_Line
    | PP_Diagnostic
    | PP_Region
    | PP_Pragma
    | PP_Nullable
    ;

// Only recognised at the beginning of a line
fragment PP_Start
    // See note below.
    : { getCharPositionInLine() == 0 }? PP_Whitespace? '#' PP_Whitespace?
    ;

fragment PP_Whitespace
    : ( [\p{Zs}]  // any character with Unicode class Zs
      | '\u0009'  // horizontal tab
      | '\u000B'  // vertical tab
      | '\u000C'  // form feed
      )+
    ;

fragment PP_New_Line
    : PP_Whitespace? Single_Line_Comment? New_Line
    ;

Uwaga:

  • Gramatyka przetwarzania wstępnego definiuje pojedynczy token PP_Directive leksykalny używany dla wszystkich dyrektyw przetwarzania wstępnego. Semantyka każdej dyrektywy przetwarzania wstępnego jest definiowana w tej specyfikacji języka, ale nie sposób ich implementowania.
  • Fragment PP_Start musi być rozpoznawany tylko na początku linii, getCharPositionInLine() == 0 powyższy predykat leksykalny ANTLR sugeruje jeden ze sposobów, w jaki można to osiągnąć i jest tylko informacyjny, implementacja może używać innej strategii.

notatka końcowa

Dostępne są następujące dyrektywy przetwarzania wstępnego:

  • #define i #undef, które służą do definiowania i niezdefiniowania, odpowiednio, symboli kompilacji warunkowej (§6.5.4).
  • #if, , #else#elifi #endif, które są używane do pomijania warunkowo sekcji kodu źródłowego (§6.5.5).
  • #line, który służy do kontrolowania numerów wierszy emitowanych pod kątem błędów i ostrzeżeń (§6.5.8).
  • #error, który służy do wystawiania błędów (§6.5.6).
  • #region i #endregion, które są używane do jawnego oznaczania sekcji kodu źródłowego (§6.5.7).
  • #nullable, który służy do określania kontekstu dopuszczalnego wartości null (§6.5.9).
  • #pragma, który służy do określania opcjonalnych informacji kontekstowych kompilatora (§6.5.10).

Dyrektywa przetwarzania wstępnego zawsze zajmuje oddzielny wiersz kodu źródłowego i zawsze zaczyna się od # znaku i nazwy dyrektywy przetwarzania wstępnego. Białe znaki mogą występować przed znakiem # i między znakiem # a nazwą dyrektywy.

Wiersz źródłowy zawierający dyrektywę #define, #undef, #endif#if#else#line#elif#endregionlub #nullable może kończyć się komentarzem jednowierszowym. Komentarze rozdzielane ( /* */ styl komentarzy) nie są dozwolone w wierszach źródłowych zawierających dyrektywy przetwarzania wstępnego.

Dyrektywy przetwarzania wstępnego nie są częścią gramatyki składni języka C#. Jednak dyrektywy przetwarzania wstępnego mogą służyć do dołączania lub wykluczania sekwencji tokenów i mogą w ten sposób wpływać na znaczenie programu w języku C#.

Przykład: po skompilowaniu program

#define A
#undef B
class C
{
#if A
    void F() {}
#else
    void G() {}
#endif
#if B
    void H() {}
#else    
    void I() {}
#endif
}

wyniki w dokładnie tej samej sekwencji tokenów co program

class C
{
    void F() {}
    void I() {}
}

W związku z tym, podczas gdy leksykacyjnie, dwa programy są zupełnie różne, składniowo, są identyczne.

przykład końcowy

6.5.2 Symbole kompilacji warunkowej

Funkcje kompilacji warunkowej udostępniane przez #ifdyrektywy , , #else#elifi #endif są kontrolowane za pomocą wyrażeń wstępnego przetwarzania (§6.5.3) i symboli kompilacji warunkowej.

fragment PP_Conditional_Symbol
    // Must not be equal to tokens TRUE or FALSE. See note below.
    : Basic_Identifier
    ;

Uwaga W jaki sposób implementacja wymusza ograniczenie dozwolonych wartości Basic_Identifier jest problemem z implementacją. notatka końcowa

Dwa symbole kompilacji warunkowej są traktowane tak samo, jeśli są identyczne po zastosowaniu następujących przekształceń w następującej kolejności:

  • Każda Unicode_Escape_Sequence jest przekształcana w odpowiadający mu znak Unicode.
  • Wszystkie Formatting_Characters są usuwane.

Symbol kompilacji warunkowej ma dwa możliwe stany: zdefiniowane lub niezdefiniowane. Na początku leksykalnego przetwarzania jednostki kompilacji symbol kompilacji warunkowej jest niezdefiniowany, chyba że został jawnie zdefiniowany przez mechanizm zewnętrzny (taki jak opcja kompilatora wiersza polecenia). Po przetworzeniu #define dyrektywy symbol kompilacji warunkowej o nazwie w tej dyrektywie staje się zdefiniowany w tej lekcji kompilacji. Symbol pozostaje zdefiniowany do momentu #undef przetworzenia dyrektywy dla tego samego symbolu lub do momentu osiągnięcia końca jednostki kompilacji. Implikacją tego jest to, że #define dyrektywy i #undef w jednej lekcji kompilacji nie mają wpływu na inne jednostki kompilacji w tym samym programie.

W przypadku odwołania w wyrażeniu przetwarzania wstępnego (§6.5.3) zdefiniowany symbol kompilacji warunkowej ma wartość truelogiczną , a niezdefiniowany symbol kompilacji warunkowej ma wartość falselogiczną . Nie ma wymogu jawnego zadeklarowania symboli kompilacji warunkowej przed ich odwołaniem do wyrażeń przetwarzania wstępnego. Zamiast tego symbole niezadeklarowane są po prostu niezdefiniowane i w związku z tym mają wartość false.

Przestrzeń nazw dla symboli kompilacji warunkowej jest odrębna i oddzielona od wszystkich innych nazwanych jednostek w programie języka C#. Symbole kompilacji warunkowej mogą być przywoływane tylko w #define dyrektywach i #undef oraz w wyrażeniach wstępnego przetwarzania.

6.5.3 Wyrażenia przetwarzania wstępnego

Wyrażenia wstępnego przetwarzania mogą występować w #if dyrektywach i .#elif Operatory ! (tylko negacja logiczna prefiksu), ==, !=, &&i || są dozwolone w wyrażeniach przetwarzania wstępnego i nawiasy mogą być używane do grupowania.

fragment PP_Expression
    : PP_Whitespace? PP_Or_Expression PP_Whitespace?
    ;
    
fragment PP_Or_Expression
    : PP_And_Expression (PP_Whitespace? '||' PP_Whitespace? PP_And_Expression)*
    ;
    
fragment PP_And_Expression
    : PP_Equality_Expression (PP_Whitespace? '&&' PP_Whitespace?
      PP_Equality_Expression)*
    ;

fragment PP_Equality_Expression
    : PP_Unary_Expression (PP_Whitespace? ('==' | '!=') PP_Whitespace?
      PP_Unary_Expression)*
    ;
    
fragment PP_Unary_Expression
    : PP_Primary_Expression
    | '!' PP_Whitespace? PP_Unary_Expression
    ;
    
fragment PP_Primary_Expression
    : TRUE
    | FALSE
    | PP_Conditional_Symbol
    | '(' PP_Whitespace? PP_Expression PP_Whitespace? ')'
    ;

W przypadku odwołania w wyrażeniu przetwarzania wstępnego zdefiniowany symbol kompilacji warunkowej ma wartość truelogiczną , a niezdefiniowany symbol kompilacji warunkowej ma wartość falselogiczną .

Obliczanie wyrażenia wstępnego przetwarzania zawsze daje wartość logiczną. Reguły obliczania wyrażenia wstępnego przetwarzania są takie same jak w przypadku wyrażenia stałego (§12.23), z wyjątkiem tego, że jedynymi jednostkami zdefiniowanymi przez użytkownika, do których można się odwoływać, są symbole kompilacji warunkowej.

6.5.4 Dyrektywy definicji

Dyrektywy definicji służą do definiowania lub niezdefiniowania symboli kompilacji warunkowej.

fragment PP_Declaration
    : 'define' PP_Whitespace PP_Conditional_Symbol
    | 'undef' PP_Whitespace PP_Conditional_Symbol
    ;

Przetwarzanie dyrektywy powoduje zdefiniowanie danego symbolu #define kompilacji warunkowej, począwszy od wiersza źródłowego zgodnego z dyrektywą. Podobnie przetwarzanie #undef dyrektywy powoduje, że dany symbol kompilacji warunkowej staje się niezdefiniowany, począwszy od wiersza źródłowego zgodnego z dyrektywą.

Wszelkie #define dyrektywy i #undef w jednostce kompilacji mają miejsce przed pierwszym tokenem (§6.4) w jednostce kompilacji; w przeciwnym razie wystąpi błąd czasu kompilacji. W intuicyjnych kategoriach #define i #undef dyrektywy poprzedzają każdy "prawdziwy kod" w jednostce kompilacji.

Przykład: przykład:

#define Enterprise
#if Professional || Enterprise
#define Advanced
#endif
namespace Megacorp.Data
{
#if Advanced
    class PivotTable {...}
#endif
}

jest prawidłowy, ponieważ #define dyrektywy poprzedzają pierwszy token ( namespace słowo kluczowe) w jednostce kompilacji.

przykład końcowy

Przykład: Poniższy przykład powoduje błąd czasu kompilacji, ponieważ #define jest zgodny z rzeczywistym kodem:

#define A
namespace N
{
#define B
#if B
    class Class1 {}
#endif
}

przykład końcowy

Element #define może zdefiniować symbol kompilacji warunkowej, który jest już zdefiniowany, bez jakichkolwiek pośredniców #undef dla tego symbolu.

Przykład: Poniższy przykład definiuje symbol kompilacji warunkowej A, a następnie definiuje go ponownie.

#define A
#define A

W przypadku kompilatorów, które umożliwiają definiowanie symboli kompilacji warunkowej jako opcji kompilacji, alternatywnym sposobem wystąpienia takiej ponownej definicji jest zdefiniowanie symbolu jako opcji kompilatora, a także w źródle.

przykład końcowy

Element #undef może "niezdefiniowane" symbol kompilacji warunkowej, który nie jest zdefiniowany.

Przykład: W poniższym przykładzie zdefiniowano symbol A kompilacji warunkowej, a następnie dwukrotnie go nie zdefiniowano; chociaż drugi #undef nie ma żadnego efektu, nadal jest prawidłowy.

#define A
#undef A
#undef A

przykład końcowy

6.5.5 Dyrektywy kompilacji warunkowej

Dyrektywy kompilacji warunkowej są używane do warunkowego dołączania lub wykluczania części jednostki kompilacji.

fragment PP_Conditional
    : PP_If_Section
    | PP_Elif_Section
    | PP_Else_Section
    | PP_Endif
    ;

fragment PP_If_Section
    : 'if' PP_Whitespace PP_Expression
    ;
    
fragment PP_Elif_Section
    : 'elif' PP_Whitespace PP_Expression
    ;
    
fragment PP_Else_Section
    : 'else'
    ;
    
fragment PP_Endif
    : 'endif'
    ;

Dyrektywy kompilacji warunkowej są napisane w grupach składających się, w kolejności, #if dyrektywy, zerowej lub większej liczby #elif dyrektyw, zero lub jednej #else dyrektywy i #endif dyrektywy. Między dyrektywami znajdują się sekcje warunkowe kodu źródłowego. Każda sekcja jest kontrolowana przez bezpośrednio poprzednią dyrektywę. Sekcja warunkowa może zawierać zagnieżdżone dyrektywy kompilacji warunkowej, pod warunkiem że te dyrektywy tworzą pełne grupy.

Co najwyżej jedna z zawartych sekcji warunkowych jest wybierana do normalnego przetwarzania leksyktycznego:

  • PP_Expression dyrektywy #if i #elif są oceniane w kolejności do momentu, gdy jeden zwraca truewartość . Jeśli wyrażenie zwraca wartość true, zostanie wybrana sekcja warunkowa po odpowiedniej dyrektywie.
  • Jeśli wszystkie PP_Expression zwraca wartość false, a jeśli #else dyrektywa jest obecna, zostanie wybrana sekcja warunkowa postępująca zgodnie z dyrektywą#else.
  • W przeciwnym razie nie wybrano sekcji warunkowej.

Wybrana sekcja warunkowa, jeśli istnieje, jest przetwarzana jako normalny input_section: kod źródłowy zawarty w sekcji jest zgodny z gramatyką leksykatyczną; tokeny są generowane z kodu źródłowego w sekcji; a dyrektywy przetwarzania wstępnego w sekcji mają określone skutki.

Wszystkie pozostałe sekcje warunkowe są pomijane i żadne tokeny, z wyjątkiem tych dla dyrektyw przetwarzania wstępnego, są generowane na podstawie kodu źródłowego. W związku z tym pominięto kod źródłowy, z wyjątkiem dyrektyw przetwarzania wstępnego, może być niepoprawny leksykalnie. Pominięte dyrektywy przetwarzania wstępnego są poprawne leksykalnie, ale nie są przetwarzane w inny sposób. W sekcji warunkowej pomijanej są również wszystkie zagnieżdżone sekcje warunkowe (zawarte w konstrukcjach zagnieżdżonych #if...#endif ).

Uwaga: Powyższa gramatyka nie przechwytuje dodatku, że sekcje warunkowe między dyrektywami przetwarzania wstępnego mogą być źle sformułowane leksykalnie. W związku z tym gramatyka nie jest gotowa na ANTLR, ponieważ obsługuje tylko poprawne dane wejściowe. notatka końcowa

Przykład: Poniższy przykład ilustruje sposób zagnieżdżania dyrektyw kompilacji warunkowej:

#define Debug // Debugging on
#undef Trace // Tracing off
class PurchaseTransaction
{
    void Commit()
    {
#if Debug
        CheckConsistency();
    #if Trace
        WriteToLog(this.ToString());
    #endif
#endif
        CommitHelper();
    }
    ...
}

Z wyjątkiem dyrektyw wstępnego przetwarzania pominięty kod źródłowy nie podlega analizie leksykalnej. Na przykład następujące informacje są prawidłowe pomimo nieokreślonego komentarza #else w sekcji:

#define Debug // Debugging on
class PurchaseTransaction
{
    void Commit()
    {
#if Debug
        CheckConsistency();
#else
        /* Do something else
#endif
    }
    ...
}

Należy jednak pamiętać, że dyrektywy przetwarzania wstępnego muszą być poprawne leksykalnie nawet w pominiętych sekcjach kodu źródłowego.

Dyrektywy przetwarzania wstępnego nie są przetwarzane, gdy pojawiają się wewnątrz wielowierszowych elementów wejściowych. Na przykład program:

class Hello
{
    static void Main()
    {
        System.Console.WriteLine(@"hello,
#if Debug
        world
#else
        Nebraska
#endif
        ");
    }
}

wyniki w danych wyjściowych:

hello,
#if Debug
        world
#else
        Nebraska
#endif

W szczególnych przypadkach zestaw przetworzonych dyrektyw wstępnego przetwarzania może zależeć od oceny pp_expression. Przykład:

#if X
    /*
#else
    /* */ class Q { }
#endif

zawsze tworzy ten sam strumień tokenu (class }Q { ), niezależnie od tego, czy X jest zdefiniowany. Jeśli X jest zdefiniowana, jedynymi przetworzonymi dyrektywami są #if i #endif, ze względu na komentarz wielowierszowy. Jeśli X jest niezdefiniowany, trzy dyrektywy (#if, #else, #endif) są częścią zestawu dyrektywy.

przykład końcowy

6.5.6 Dyrektywy diagnostyczne

Dyrektywy diagnostyczne są używane do generowania jawnie komunikatów o błędach i ostrzeżeniach, które są zgłaszane w taki sam sposób, jak inne błędy i ostrzeżenia w czasie kompilacji.

fragment PP_Diagnostic
    : 'error' PP_Message?
    | 'warning' PP_Message?
    ;

fragment PP_Message
    : PP_Whitespace Input_Character*
    ;

Przykład: przykład

#if Debug && Retail
    #error A build can't be both debug and retail
#endif
class Test {...}

Tworzy błąd czasu kompilacji ("Kompilacja nie może być debugowana i detaliczna"), jeśli są zdefiniowane symbole Debug kompilacji warunkowej.Retail Należy pamiętać, że PP_Message może zawierać dowolny tekst; w szczególności nie musi zawierać dobrze sformułowanych tokenów, jak pokazano w pojedynczym cudzysłowie w słowie can't.

przykład końcowy

Dyrektywy regionów 6.5.7

Dyrektywy regionu są używane do oznaczania jawnie regionów kodu źródłowego.

fragment PP_Region
    : PP_Start_Region
    | PP_End_Region
    ;

fragment PP_Start_Region
    : 'region' PP_Message?
    ;

fragment PP_End_Region
    : 'endregion' PP_Message?
    ;

Żadne znaczenie semantyczne nie jest dołączone do regionu; regiony są przeznaczone do użycia przez programistę lub przez zautomatyzowane narzędzia do oznaczania sekcji kodu źródłowego. Istnieje jedna #endregion dyrektywa zgodna z każdą #region dyrektywą. Komunikat określony w #region dyrektywie or #endregion również nie ma semantycznego znaczenia; służy jedynie do identyfikowania regionu. Dopasowywanie #region i #endregion dyrektywy mogą mieć różne PP_Messages.

Przetwarzanie leksykalne regionu:

#region
...
#endregion

odpowiada dokładnie przetwarzaniu leksykalnemu dyrektywy kompilacji warunkowej formularza:

#if true
...
#endif

Uwaga: oznacza to, że region może zawierać co najmniej jeden #if/.../#endiflub być zawarty z sekcją warunkową w / #if.../#endif; ale region nie może nakładać się tylko na część #if/.../#endiflub uruchomić i zakończyć w różnych sekcjach warunkowych. notatka końcowa

6.5.8 Dyrektywy linii

Dyrektywy wiersza mogą służyć do zmiany numerów wierszy i nazw jednostek kompilacji, które są zgłaszane przez kompilator w danych wyjściowych, takich jak ostrzeżenia i błędy. Te wartości są również używane przez atrybuty wywołujące informacje (§22.5.6).

Uwaga: dyrektywy wiersza są najczęściej używane w narzędziach metaprogramowania, które generują kod źródłowy języka C# na podstawie innych danych wejściowych tekstowych. notatka końcowa

fragment PP_Line
    : 'line' PP_Whitespace PP_Line_Indicator
    ;

fragment PP_Line_Indicator
    : Decimal_Digit+ PP_Whitespace PP_Compilation_Unit_Name
    | Decimal_Digit+
    | DEFAULT
    | 'hidden'
    ;
    
fragment PP_Compilation_Unit_Name
    : '"' PP_Compilation_Unit_Name_Character+ '"'
    ;
    
fragment PP_Compilation_Unit_Name_Character
    // Any Input_Character except "
    : ~('\u000D' | '\u000A'   | '\u0085' | '\u2028' | '\u2029' | '#')
    ;

Jeśli nie #line ma żadnych dyrektyw, kompilator zgłasza prawdziwe numery wierszy i nazwy jednostek kompilacji w danych wyjściowych. Podczas przetwarzania #line dyrektywy zawierającej PP_Line_Indicator , która nie defaultjest , kompilator traktuje wiersz po dyrektywie jako o podanym numerze wiersza (i nazwie jednostki kompilacji, jeśli określono).

Dozwolona wartość maksymalna Decimal_Digit+ jest zdefiniowana przez implementację.

#line default Dyrektywa cofa efekt wszystkich poprzednich #line dyrektyw. Kompilator zgłasza prawdziwe informacje o wierszach dla kolejnych wierszy, dokładnie tak, jakby żadne dyrektywy nie #line zostały przetworzone.

#line hidden Dyrektywa nie ma wpływu na jednostkę kompilacji i numery wierszy zgłaszane w komunikatach o błędach lub utworzone przy użyciu CallerLineNumberAttribute (§22.5.6.2). Ma to na celu wpływ na narzędzia debugowania na poziomie źródła, tak aby podczas debugowania wszystkie wiersze między dyrektywą a kolejną #line dyrektywą #line hidden (nie ) #line hiddennie miały żadnych informacji o numerze wiersza i zostały pominięte całkowicie podczas przechodzenia przez kod.

Uwaga: chociaż PP_Compilation_Unit_Name może zawierać tekst, który wygląda jak sekwencja ucieczki, taki tekst nie jest sekwencją ucieczki. W tym kontekście znak "\" po prostu wyznacza zwykły znak ukośnika odwrotnego. notatka końcowa

6.5.9 Dyrektywa dopuszczana do wartości null

Dyrektywa dopuszczana do wartości null kontroluje kontekst dopuszczany do wartości null, zgodnie z poniższym opisem.

fragment PP_Nullable
    : 'nullable' PP_Whitespace PP_Nullable_Action
      (PP_Whitespace PP_Nullable_Target)?
    ;
fragment PP_Nullable_Action
    : 'disable'
    | 'enable'
    | 'restore'
    ;
fragment PP_Nullable_Target
    : 'warnings'
    | 'annotations'
    ;

Dyrektywa dopuszczana do wartości null ustawia dostępne flagi dla kolejnych wierszy kodu do momentu zastąpienia jej przez inną dyrektywę dopuszczaną do wartości null lub do momentu osiągnięcia końca _unit kompilacji. Kontekst dopuszczający wartość null zawiera dwie flagi: adnotacje i ostrzeżenia. Efektem każdej formy dyrektywy dopuszczanej do wartości null jest następująca:

  • #nullable disable: wyłącza zarówno adnotacje dopuszczające wartość null, jak i flagi ostrzeżeń dopuszczające wartość null.
  • #nullable enable: włącza zarówno adnotacje dopuszczające wartość null, jak i flagi ostrzeżeń dopuszczające wartość null.
  • #nullable restore: przywraca zarówno adnotacje, jak i flagi ostrzeżeń do stanu określonego przez mechanizm zewnętrzny, jeśli istnieje.
  • #nullable disable annotations: wyłącza flagę adnotacji dopuszczanych do wartości null. Flaga ostrzeżeń dopuszczanych do wartości null nie ma wpływu.
  • #nullable enable annotations: włącza flagę adnotacji dopuszczanych do wartości null. Flaga ostrzeżeń dopuszczanych do wartości null nie ma wpływu.
  • #nullable restore annotations: przywraca flagę adnotacji dopuszczanych do wartości null do stanu określonego przez mechanizm zewnętrzny, jeśli istnieje. Flaga ostrzeżeń dopuszczanych do wartości null nie ma wpływu.
  • #nullable disable warnings: wyłącza flagę ostrzeżeń dopuszczanych do wartości null. Nie ma to wpływu na flagę adnotacji dopuszczanych do wartości null.
  • #nullable enable warnings: włącza flagę ostrzeżeń dopuszczanych do wartości null. Nie ma to wpływu na flagę adnotacji dopuszczanych do wartości null.
  • #nullable restore warnings: przywraca flagę ostrzeżeń dopuszczanych do wartości null do stanu określonego przez mechanizm zewnętrzny, jeśli istnieje. Nie ma to wpływu na flagę adnotacji dopuszczanych do wartości null.

Stan wyrażeń dopuszczanych do wartości null jest śledzony przez cały czas. Stan flagi adnotacji oraz obecność lub brak adnotacji ?dopuszczanej do wartości null określa początkowy stan null deklaracji zmiennej. Ostrzeżenia są wystawiane tylko wtedy, gdy flaga ostrzeżeń jest włączona.

Przykład: przykład

#nullable disable
string x = null;
string y = "";
#nullable enable
Console.WriteLine(x.Length); // Warning
Console.WriteLine(y.Length);

tworzy ostrzeżenie w czasie kompilacji ("jak x to null"). Stan x dopuszczalny wartości null jest śledzony wszędzie. Po włączeniu flagi ostrzeżeń zostanie wyświetlone ostrzeżenie.

przykład końcowy

6.5.10 Dyrektywy Pragma

Dyrektywa #pragma przetwarzania wstępnego służy do określania informacji kontekstowych kompilatora.

Uwaga: na przykład kompilator może dostarczać #pragma dyrektywy, które

  • Włącz lub wyłącz określone komunikaty ostrzegawcze podczas kompilowania kolejnego kodu.
  • Określ optymalizacje, które mają być stosowane do kolejnego kodu.
  • Określ informacje, które mają być używane przez debuger.

notatka końcowa

fragment PP_Pragma
    : 'pragma' PP_Pragma_Text?
    ;

fragment PP_Pragma_Text
    : PP_Whitespace Input_Character*
    ;

Input_Character w PP_Pragma_Text są interpretowane przez kompilator w sposób zdefiniowany przez implementację. Informacje podane w #pragma dyrektywie nie zmieniają semantyki programu. #pragma Dyrektywa zmienia zachowanie kompilatora, które wykracza poza zakres tej specyfikacji języka. Jeśli kompilator nie może zinterpretować Input_Characters, kompilator może wygenerować ostrzeżenie, jednak nie generuje błędu czasu kompilacji.

Uwaga: PP_Pragma_Text może zawierać dowolny tekst; w szczególności nie musi zawierać dobrze sformułowanych tokenów. notatka końcowa