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:
- 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ł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 iG < A
B > (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 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 as
relacyjnych ; 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
is
lubcase
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
metodyG
ogólnej z dwoma argumentami typu i jednym zwykłym argumentem. InstrukcjeF(G<A, B>7); F(G<A, B>>7);
każda z nich będzie interpretowana jako wywołanie metody
F
z dwoma argumentami. Instrukcjax = 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 instrukcjix = 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 "is
relational_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ędzyB
iC()
. 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 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 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
,_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 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 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, 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
, , 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
Lu
Ul
LU
lU
uL
lublu
, 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 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 reprezentująca wartość
9223372036854775808
(2⁶³) i nie Integer_Type_Suffix lub Integer_Type_SuffixL
lubl
pojawia się jako token bezpośrednio po jednoargumentowym tokenie minus (§12.9.3), wynik (obu tokenów) jest stałą typulong
o wartości−9223372036854775808
(−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 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
lubf
ma typfloat
.Przykład: literały
1f
,1.5f
,1e10f
i123.456F
są typufloat
. przykład końcowy - Prawdziwy literał sufiksowany przez
D
lubd
ma typdouble
.Przykład: literały
1d
,1.5d
,1e10d
i123.456D
są typudouble
. przykład końcowy - Prawdziwy literał sufiksowany przez
M
lubm
ma typdecimal
.Przykład: literały
1m
,1.5m
,1e10m
i123.456M
są typudecimal
. 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 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ą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
ale1.F
nie. 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 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:'
,a
v
x
U
u
t
r
f
0
n
"
\
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+0009
znak tabulacji) w obu ciągach. W rzeczywistości drugi ciąg zaczyna się odU+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ą123
szesnastkową . Aby utworzyć ciąg zawierający znak z wartością12
szesnastkową, 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 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 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 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 jakList<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
#elif
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 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
#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
Funkcje kompilacji warunkowej udostępniane przez #if
dyrektywy , , #else
#elif
i #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ść true
logiczną , a niezdefiniowany symbol kompilacji warunkowej ma wartość false
logiczną . 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ść true
logiczną , a niezdefiniowany symbol kompilacji warunkowej ma wartość false
logiczną .
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 zwracatrue
wartość . 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, czyX
jest zdefiniowany. JeśliX
jest zdefiniowana, jedynymi przetworzonymi dyrektywami są#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ć 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ł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 #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
/.../#endif
lub być zawarty z sekcją warunkową w /#if
.../#endif
; ale region nie może nakładać się tylko na część#if
/.../#endif
lub 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 default
jest , 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 hidden
nie 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
tonull
"). Stanx
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
ECMA C# draft specification