Uwaga
Dostęp do tej strony wymaga autoryzacji. Może spróbować zalogować się lub zmienić katalogi.
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować zmienić katalogi.
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:
- Przekształcenie, które konwertuje plik z określonego repertuaru znaków i schematu kodowania na sekwencję znaków Unicode.
- Analiza leksykalna, która tłumaczy strumień znaków wejściowych Unicode na strumień tokenów.
- 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łach znaków lub ciągów, można użyć jednej lub obu sekwencji ucieczki
\0
lub\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 ciągów znaków dosłownych (§6.4.5.6). Celem tego zalecenia jest umożliwienie dosłownego literału tekstowego utworzenia tej samej sekwencji znaków, gdy jednostka kompilacji kodu jest przenoszona między systemami obsługującymi różne zestawy znaków inne niż Unicode, szczególnie te, które używają różnych sekwencji znaków do oddzielania 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ą przedstawione w formie rozszerzonej Backusa-Naura 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. Mając to na uwadze, ta specyfikacja stara się zminimalizować lukę między określoną w specyfikacji gramatyką a wymaganą do utworzenia leksykatora i analizatora w ANTLR.
ANTLR rozróżnia leksykę od składni, nazywając te drugie analizatorami, w swojej notacji przez rozpoczynanie reguł leksykalnych wielką literą, a reguł analizatora 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 w tokeny (§6.4), białe znaki (§6.3.4), komentarze (§6.3.3) i dyrektywy przetwarzania wstępnego (§6.5).
Wiele symboli terminalnych gramatyki składni nie jest jawnie zdefiniowane jako tokeny w obrębie gramatyki leksykalnej. Raczej wykorzystuje się zachowanie ANTLR, zgodnie z którym literały w gramatyce są traktowane jako ukryte tokeny leksykalne. To pozwala, aby słowa kluczowe, operatory itp. były reprezentowane w gramatyce przez swoje literały, a nie nazwy tokenów.
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, podklauzulach i załącznikach, które następują po tej podklauzuli. Symbole terminalne gramatyki syntaktycznej to tokeny zdefiniowane jawnie przez gramatykę leksykalną i niejawnie przez literały 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# musi być zgodna z produkcją compilation_unit (§14.2) gramatyki składniowej.
6.2.5 Niejednoznaczności gramatyczne
Produkcje dla:
- simple_name (§12.8.4),
- member_access (§12.8.7),
- null_conditional_member_access (§12.8.8),
- dependent_access (§12.8.8),
- base_access (§12.8.15) i
- pointer_member_access (§23.6.3);
("niejednoznaczne produkcje") może spowodować powstanie niejednoznaczności w gramatyce wyrażeń.
Te produkcje występują w kontekstach, w których wartość może wystąpić w wyrażeniu i mają co najmniej jedną alternatywę, która kończy się gramatyką "identifier type_argument_list?
". Jest to opcjonalna lista_argumentów_typu , która skutkuje możliwą niejednoznacznością.
Przykład: oświadczenie:
F(G<A, B>(7));
można to interpretować jako wywołanie
F
z dwoma argumentami,G < A
iB > (7)
. Alternatywnie można go interpretować jako wywołanieF
z jednym argumentem, który jest wywołaniem metodyG
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 jedna z jednoznacznych produkcji, w tym opcjonalny type_argument_list (§8.4.2), token zamykający >
należy zbadać i jeśli jest on:
- jeden z
( ) ] } : ; , . ? == != | ^ && || & [
; lub - jeden z operatorów relacyjnych
< <= >= is as
; lub - kontekstowe słowo kluczowe zapytania pojawiające się w wyrażeniu zapytania.
następnie type_argument_list zostaną przechowane jako część jednoznacznej produkcji, a wszelkie inne możliwe analizy sekwencji tokenów zostaną odrzucone. W przeciwnym razie tokeny analizowane jako type_argument_list nie są uważane za część niejednoznacznej produkcji, nawet jeśli nie ma innej możliwej analizy tych tokenów.
Uwaga: te zasady uściślania nie są stosowane podczas analizowania innych produkcji, nawet jeśli podobnie kończą się w ciągu "
identifier type_argument_list?
"; takie produkcje są analizowane w normalny sposób. Przykłady to: namespace_or_type_name (§7.8); named_entity (§12.8.23); null_conditional_projection_initializer (§12.8.8); i qualified_alias_member (§14.8.1). notatka końcowa
Przykład: stwierdzenie:
F(G<A, B>(7));
zgodnie z tą regułą zostanie zinterpretowane jako wywołanie
F
z jednym argumentem, który jest wywołaniem metody ogólnejG
z dwoma argumentami typu i jednym zwykłym argumentem. OświadczeniaF(G<A, B>7); F(G<A, B>>7);
każda z nich będzie interpretowana jako wywołanie metody
F
z dwoma argumentami. Oświadczeniex = F<A> + y;
będzie interpretowany jako operator mniejszości, operator większości oraz operator 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 oświadczeniux = y is C<T> && z;
tokeny
C<T>
są interpretowane jako namespace_or_type_name z type_argument_list ze względu na obecność rozstrzygającego tokenu&&
po type_argument_list.Wyrażenie
(A < B, C > D)
jest krotką z dwoma elementami, z których każdy jest porównaniem.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:
case używa wzorca deklaracji.przykład końcowy
W przypadku rozpoznania relational_expression (§12.12.1), jeśli zarówno opcja „relational_expressionis
typu” jak i opcja „relational_expressionis
wzorca” są możliwe do zastosowania, a typ jest uznawany za dostępny, wtedy należy wybrać alternatywę „relational_expressionis
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ę na strukturę leksykalną jednostki kompilacji C#: terminatory wierszy (§6.3.2), białe znaki (§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, oraz umożliwić traktowanie jednostki kompilacji jako sekwencję prawidłowo zakończonych wierszy, następujące przekształcenia są stosowane w określonej kolejności do 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 pozwala programowi zakończyć się w PP_Directive (§6.5), który nie ma zakończenia New_Line. 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 ograniczony 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 znakowych i tekstowych.
Uwaga: te reguły muszą być dokładnie interpretowane. Na przykład, w poniższym przykładzie, rozdzielany komentarz, który rozpoczyna się przed
A
, kończy się pomiędzyB
iC()
. Przyczyną jest to, że// B */ C();
nie jest w rzeczywistości komentarzem jednowierszowym, ponieważ
//
nie ma specjalnego znaczenia w ograniczonym komentarzu, dlatego*/
ma swoje zwykłe specjalne znaczenie w tym wierszu.Podobnie, wydzielony komentarz rozpoczynający się przed
D
kończy się przedE
. 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 ograniczonego komentarza.Przydatną konsekwencją faktu, że
/*
i*/
nie mają specjalnego znaczenia w komentarzu jednowierszowym, jest możliwość zakomentowania bloku wierszy kodu źródłowego poprzez umieszczenie//
na początku każdego wiersza. Ogólnie rzecz biorąc, umieszczenie/*
przed tymi wierszami i*/
po nich nie działa poprawnie, ponieważ nie obejmuje właściwie komentarzy w bloku i ogólnie może całkowicie zmienić strukturę takich odgraniczonych komentarzy.Przykładowy kod:
static void Main() { /* A // B */ C(); Console.WriteLine(/* "D */ "E"); }
notatka końcowa
Jednolinijkowe_komentarze i Komentarze_ograniczone o określonych formatach mogą być używane jako komentarze dokumentacyjne, zgodnie z opisem w §D.
6.3.4 Biała spacja
Odstęp jest definiowany jako każdy znak należący do klasy Unicode Zs (w tym znak spacji), a także znak tabulatora poziomego, znak tabulatora pionowego i znak przejścia na nową stronę.
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łach znaków (§6.4.5.5), zwykłych literałach ciągu (§6.4.5.6) i interpolowanych wyrażeniach ciągów regularnych (§12.8.3). Sekwencja ucieczki Unicode nie jest przetwarzana w żadnej innej lokalizacji (na przykład w celu utworzenia operatora, znaku 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. Punkty kodu Unicode powyżej U+FFFF
nie są dozwolone w literałach znaków. Powyższe U+10FFFF
punkty kodu Unicode są nieprawidłowe i nie są obsługiwane.
Nie przeprowadza się wielu tłumaczeń. Na przykład literał ciągu "\u005Cu005C"
jest odpowiednikiem "\u005C"
, a 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ą wyjścia dla litery „f
”. Program jest odpowiednikiemclass 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 przez ANTLR, to wykluczenie jest obsługiwane automatycznie przez semantykę ANTLR.
- Słowa kluczowe i kontekstowe słowa kluczowe występują w gramatyce jako dosłowne łańcuchy znaków.
- Narzędzie ANTLR tworzy niejawne reguły tokenów leksykalnych z tych ciągów znaków.
- 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, ponieważ reguły leksykalne dla tych słów je poprzedzają.
- Fragment Escaped_Identifier zawiera wyrażenia ucieczki i kontekstowe słowa kluczowe, ponieważ są one częścią dłuższego tokenu, rozpoczynając się od
@
, a przetwarzanie leksykalne 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
,_identifier2
i@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 na odpowiadający znak Unicode.
- Wszystkie Formatting_Character są 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 to oznaczać odrzucenie (§9.2.9.2).
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' | 'notnull' | 'on' | 'orderby'
| 'partial' | 'remove' | 'select' | 'set' | 'unmanaged'
| 'value' | 'var' | 'when' | 'where' | 'yield'
;
Uwaga: reguły keyword oraz contextual_keyword są regułami syntaktycznymi, 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 właściwości identyfikatory get
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 rozróżniania między dwiema opcjami. 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 async
metody , 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, gdyż 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 literału logicznego 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
, , uint
long
i 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
lubu
, ma pierwszy z tych typów, w których można przedstawić jego wartość:uint
,ulong
. - Jeśli literał ma sufiks
L
lubl
, ma pierwszy z tych typów, w których można przedstawić jego wartość:long
,ulong
. - Jeśli literał ma sufiks
UL
,Ul
,uL
,ul
,LU
,Lu
,lU
lublu
, jest typuulong
.
Jeśli wartość reprezentowana przez literał liczby całkowitej znajduje się poza zakresem typu ulong
, wystąpi błąd w czasie kompilacji.
Uwaga: W przypadku stylu sugerowane jest użycie znaku "
L
" zamiast "l
" podczas pisania literałów typulong
, ponieważ łatwo jest pomylić literę "" z cyfrą "l
1
". 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 typuuint
. - Gdy Integer_Literal reprezentuje wartość
9223372036854775808
(2⁶³) i żaden Integer_Type_Suffix ani Integer_Type_SuffixL
nie pojawiają się jako tokeny bezpośrednio po unarnym operatorem minus (l
), wynik (obu tokenów) jest stałą typu o wartościlong
(−2⁶³). We wszystkich innych sytuacjach taki Integer_Literal jest typuulong
.
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
, double
i 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 Real_Type_Suffix nie jest określony, typ Real_Literal to double
.
W przeciwnym razie Real_Type_Suffix określa typ literału rzeczywistego w następujący sposób:
- Literał liczby rzeczywistej z sufiksem
F
lubf
ma typfloat
.Przykład: literały
1f
,1.5f
,1e10f
i123.456F
są typufloat
. przykład końcowy - Literał rzeczywisty z sufiksem
D
lubd
ma typdouble
.Przykład: literały
1d
,1.5d
,1e10d
i123.456D
są typudouble
. przykład końcowy - Literał rzeczywisty z sufiksem
M
lubm
jest typudecimal
.Przykład: literały
1m
,1.5m
,1e10m
i123.456M
są typudecimal
. koniec przykładu Ten literał jest konwertowany na wartośćdecimal
przez pobranie dokładnej wartości, a w razie potrzeby zaokrąglenie do najbliższej wartości reprezentowanej, stosując zaokrąglanie zgodne z zasadami bankierskimi (§8.3.8). Każda skala, która jest widoczna w literałach, jest zachowywana, chyba że wartość jest zaokrąglona. Uwaga: W związku z tym literał2.900m
zostanie przeanalizowany w celu utworzeniadecimal
znaku0
, współczynnika2900
i skali3
. 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ąglanie do najbliższego” z ustaleniem remisu poprzez zaokrąglenie do wartości parzystej (wartość z zerowym najmniej znaczącym bitem), przy czym wszystkie cyfry są uważane za znaczące.
Uwaga: W literałach liczby rzeczywistej cyfry dziesiętne są zawsze wymagane po kropce dziesiętnej. Na przykład
1.3F
jest prawdziwym literałem, ale1.F
nie jest. notatka końcowaPrzykł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 ujętego w cudzysłów znaku, 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 występuje po znaku ukośnika odwrotnego (
\
) w znaku musi być jednym z następujących znaków:'
,"
,\
,0
,a
,b
,f
,n
,r
,t
,u
,U
,x
,v
. 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+0009
, znak tabulacji) w obu ciągach. W rzeczywistości drugi ciąg zaczyna się odU+9BAD
, a wszystkie trzy litery w słowie "Bad" są prawidłowymi cyframi szesnastkowymi. Zaleca się unikać stosowania\x
na rzecz konkretnych sekwencji ucieczki (\t
w tym przykładzie) albo 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ł znaku musi znajdować się w przedziale od U+0000
do U+FFFF
.
Prosta sekwencja ucieczki reprezentuje znak Unicode, zgodnie z opisem w poniższej tabeli.
Sekwencja ucieczki | Nazwa znaku | Punkt kodowy Unicode |
---|---|---|
\' |
Pojedynczy cudzysłów | U+0027 |
\" |
Podwójny cudzysłów | U+0022 |
\\ |
Ukośnik odwrotny | U+005C |
\0 |
null | U+0000 |
\a |
Alert | U+0007 |
\b |
klawisz Backspace | U+0008 |
\f |
Źródło danych formularzy | U+000C |
\n |
Nowy wiersz | U+000A |
\r |
Powrót karetki | U+000D |
\t |
Zakładka pozioma | U+0009 |
\v |
Tabulacja pionowa | U+000B |
Typ Character_Literal jest char
.
6.4.5.6 Literały ciągu
Język C# obsługuje dwie formy literałów ciągów znakowych: zwykłe literały ciągów znakowych i literały dosłowne ciągów znakowych. Zwykły literał ciągu składa się z zera lub większej liczby znaków ujętych w podwójny cudzysłów, jak w "hello"
, i może zawierać zarówno proste sekwencje ucieczki (takie jak \t
dla znaku tabulacji), jak i szesnastkowe oraz Unicode sekwencje ucieczki.
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 zawarte pomiędzy ogranicznikami są interpretowane dosłownie, a jedynym wyjątkiem jest Quote_Escape_Sequence, który reprezentuje jeden znak podwójnego cudzysłowia. W szczególności proste ciągi ucieczki, a ciągi ucieczki szesnastkowe i Unicode nie są przetwarzane w dosłownych literałach tekstowych. 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 znakó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ż szesnastkowa sekwencja ucieczki może mieć zmienną liczbę cyfr szesnastkowych, literał ciągu znaków
"\x123"
zawiera pojedynczy znak o wartości szesnastkowej123
. Aby utworzyć ciąg zawierający znak o wartości szesnastkowej12
, po którym następuje znak3
, 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 wartość null
. Nie ma typu, ale można go przekonwertować na dowolny typ referencyjny lub typ wartości dopuszczający 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 operandya
ib
. 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 dodaniem typów ogólnych do języka C#
>>
i>>=
były 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 skonstruowanych typów, takich jakList<Dictionary<string, int>>
. Zamiast wymagać od programisty oddzielenia>
i>
przez spację, definicja dwóch operator_or_punctuator został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
, ,#elif
#else
i#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 w przypadku błędów i ostrzeżeń (§6.5.8). -
#error
, który służy do zgłaszania 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 odnoszącego się do 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
, #if
, #elif
, #else
, #endif
, #line
, #endregion
lub #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
Funkcjonalność kompilacji warunkowej zapewniana przez dyrektywy #if
, #elif
, #else
i #endif
jest kontrolowana za pomocą wyrażenia 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 wymieniony w tej dyrektywie staje się zdefiniowany w tej jednostce kompilacji. Symbol pozostaje zdefiniowany do momentu, gdy dyrektywa #undef
dla tego samego symbolu zostanie przetworzona, lub do końca jednostki kompilacji. Implikacją tego jest to, że dyrektywy #define
i #undef
w jednej jednostce 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ść logiczną true
, a niezdefiniowany symbol kompilacji warunkowej ma wartość logiczną false
. 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 dyrektywach #define
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 dyrektywach #if
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 preprocesora zdefiniowany symbol kompilacji warunkowej ma wartość logiczną true
, a niezdefiniowany symbol kompilacji warunkowej ma wartość logiczną false
.
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 #define
powoduje zdefiniowanie danego symbolu kompilacji warunkowej, począwszy od wiersza źródłowego następującego po dyrektywie. 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
i #undef
dyrektywy w jednostce kompilacji powinny wystąpić przed pierwszym tokenem (§6.4) w jednostce kompilacji; w przeciwnym razie wystąpi błąd czasu kompilacji. Intuicyjnie rzecz ujmując, dyrektywy #define
i #undef
powinny poprzedzać 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 }
są prawidłowe, 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 usunąć definicję symbolu 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 powinny być zapisane w grupach składających się, w określonej kolejności, z dyrektywy #if
, zerowej lub większej liczby dyrektyw #elif
, zerowej lub jednej dyrektywy #else
oraz dyrektywy #endif
. 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 dyrektyw
#if
i#elif
są oceniane w kolejności, aż jeden z nich zwrócitrue
. Jeśli wyrażenie zwraca wartośćtrue
, wybierana jest sekcja warunkowa następująca po odpowiedniej dyrektywie. - Jeśli wszystkie PP_Expression przynoszą
false
, i jeśli obecna jest dyrektywa#else
, sekcja warunkowa po dyrektywie#else
jest wybierana. - 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ęty kod źródłowy, z wyjątkiem dyrektyw przetwarzania wstępnego, może być niepoprawny pod względem leksykalnym. Pominięte dyrektywy przetwarzania wstępnego są poprawne leksykalnie, ale nie są przetwarzane w inny sposób. W pomijanej sekcji warunkowej są również pomijane wszystkie zagnieżdżone sekcje warunkowe (zawarte w zagnieżdżonych konstrukcjach #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ący przykład jest prawidłowy pomimo niezakończonego komentarza w sekcji
#else
:#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 wyjściu:
hello, #if Debug world #else Nebraska #endif
W szczególnych przypadkach zestaw dyrektyw wstępnego przetwarzania, które są przetwarzane, może zależeć od ewaluacji pp_expression. Przykład:
#if X /* #else /* */ class Q { } #endif
zawsze tworzy ten sam strumień tokenu (
class
Q
{
}
), niezależnie od tego, czyX
jest zdefiniowany. JeżeliX
jest zdefiniowane, przetwarza się tylko dyrektywy#if
i#endif
, ze względu na komentarz wielowierszowy. JeśliX
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ć jednocześnie debug i release”), jeśli zdefiniowane są symbole kompilacji warunkowej
Debug
iRetail
. 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łowiecan'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 dyrektywie #region
lub #endregion
również nie ma znaczenia semantycznego; służy jedynie do identyfikacji regionu. Dopasowywanie dyrektyw #region
i #endregion
może mieć różne PP_Messages.
Przetwarzanie leksykalne regionu:
#region
...
#endregion
odpowiada dokładnie przetwarzaniu leksykalnemu dyrektywy kompilacji w formie warunkowej.
#if true
...
#endif
Uwaga: oznacza to, że region może zawierać jeden lub więcej
#if
/.../#endif
, lub być zawarty w sekcji warunkowej w ramach#if
/.../#endif
; ale region nie może nakładać się tylko na część#if
/.../#endif
, lub zaczynać się i kończyć w różnych sekcjach warunkowych. notatka końcowa
6.5.8 Dyrektywy linii
Dyrektywy linii mogą służyć do zmiany numerów linii i nazw jednostek kompilacji, które są zgłaszane przez kompilator w wynikach, takich jak ostrzeżenia i błędy. Te wartości są również używane przez atrybuty informacji o dzwoniącym (§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 ma żadnych dyrektyw #line
, kompilator zgłasza prawdziwe numery wierszy i nazwy jednostek kompilacji w danych wyjściowych. Podczas przetwarzania dyrektywy #line
zawierającej PP_Line_Indicator, która nie jest default
, kompilator traktuje wiersz po dyrektywy jako o podanej liczbie wierszy (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 dokładne informacje o liniach dla kolejnych linii, tak, jakby żadna z dyrektyw #line
nie została przetworzona.
#line hidden
Dyrektywa nie ma wpływu na jednostkę kompilacji i numery wierszy zgłaszane w komunikatach o błędach ani na te powstałe 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 #line hidden
dyrektywą a kolejną #line
dyrektywą, która nie jest #line hidden
, nie miały informacji o numerze wiersza i były całkowicie pomijane 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 Nullable
Dyrektywa nullable kontroluje kontekst nullable, 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 nullable ustawia dostępne flagi dla kolejnych wierszy kodu, aż zostanie zastąpiona przez inną dyrektywę nullable lub do momentu osiągnięcia końca kompilacji _unit. Kontekst dopuszczający wartość null zawiera dwie flagi: adnotacje i ostrzeżenia. Skutek działania każdej opcji dyrektywy nullable jest następujący:
-
#nullable disable
: Wyłącza zarówno adnotacje obsługujące wartości null, jak i flagi ostrzeżeń obsługujące wartości 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 obsługujących wartości null. Flaga ostrzeżeń dla typów dopuszczających wartość null pozostaje niezmieniona. -
#nullable enable annotations
: włącza flagę adnotacji dopuszczanych do wartości null. Flaga ostrzeżeń dotyczących wartości null pozostaje bez zmian. -
#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ń dotyczących wartości null pozostaje bez zmian. -
#nullable disable warnings
: wyłącza flagę ostrzeżeń związanych z wartościami null. Flaga adnotacji nullable pozostaje bez zmian. -
#nullable enable warnings
: Włącza flagę ostrzeżeń o wartości null. Nie ma to wpływu na flagę adnotacji dla wartości null. -
#nullable restore warnings
: Przywraca flagę ostrzeżeń o wartościach null do stanu określonego przez mechanizm zewnętrzny, o ile taki 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 dopuszczającej wartość 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 ("że
x
jestnull
"). Stan dopuszczalności nullx
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 dla 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 powinna zmieniać tylko to 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; nie generuje jednak 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
ECMA C# draft specification