After a file is read as Unicode text (see File structure), the compiler splits it into lines and then into input elements: white space, comments, tokens and preprocessor directives (C# specification clause 6.3). Tokens are the identifiers, keywords, literals, and operators and punctuators that the grammar is built from.
White space
White space separates tokens and is otherwise ignored (clause 6.3.4). It is any character of Unicode class Zs, which includes the space, and these three:
| Character | Code |
|---|---|
| Horizontal tab | U+0009 |
| Vertical tab | U+000B |
| Form feed | U+000C |
Comments
| Form | Name | Ends at |
|---|---|---|
// text | Single-line comment | The end of the line |
/* text */ | Delimited comment | The first */; may span lines |
/// text | XML documentation comment, single-line form | The end of the line |
/** text */ | XML documentation comment, delimited form | The first */ |
Comments do not nest. Comment markers have no special meaning inside a string or
character literal or inside another comment (clause 6.3.3). A comment counts as white
space, so a/**/b is two tokens.
XML documentation comments
A /// or /** comment placed directly before a type or
member is an XML documentation comment (annex D). With the
GenerateDocumentationFile project property or the -doc
option, the compiler collects these into a .xml file. It checks that the
text is well-formed XML and that cref and param references
resolve, and warns if they do not.
/// <summary>Returns the larger of two values.</summary>
/// <param name="a">The first value.</param>
/// <param name="b">The second value.</param>
/// <returns><paramref name="a"/> if it is not smaller, otherwise <paramref name="b"/>.</returns>
/// <seealso cref="Math.Max(int, int)"/>
public static int Larger(int a, int b) => a >= b ? a : b;
These are the tags recommended by the standard (annex D.3). The compiler checks only the ones marked:
| Tag | Meaning |
|---|---|
<summary> | A short description of the member |
<remarks> | Longer description |
<param name="…"> | A parameter; the compiler checks the name |
<paramref name="…"/> | A reference to a parameter in the text; the compiler checks the name |
<typeparam name="…"> | A type parameter; the compiler checks the name |
<typeparamref name="…"/> | A reference to a type parameter; the compiler checks the name |
<returns> | The return value |
<value> | The value of a property |
<exception cref="…"> | An exception the member can throw; cref is checked |
<permission cref="…"> | The security access needed; cref is checked |
<see cref="…"/> | An inline link; cref is checked |
<seealso cref="…"/> | A “see also” link; cref is checked |
<c> | Inline code |
<code> | A code block |
<para> | A paragraph |
<list> | A bullet, numbered or table list |
<example> | A usage example |
<include file="…" path="…"/> | Includes XML from another file, chosen by an XPath |
A cref is written to the output file as an ID string whose first
character gives the kind of element: N namespace, T type,
F field, P property or indexer, M method,
E event, and ! for an error.
Tokens
There are several kinds of token: identifiers, keywords, integer literals, real literals, character literals, string literals, and operators and punctuators (clause 6.4.1). White space and comments separate them, but are not tokens.
Unicode escapes
A Unicode escape is \uXXXX or \UXXXXXXXX with four or eight
hexadecimal digits (clause 6.4.2). Unlike in Java, escapes are not replaced before
tokenising. They are processed only in identifiers, character literals and regular
string literals. Elsewhere they have no meaning, so one cannot write a keyword or a
brace with an escape. Only one level is decoded: "\u005Cu005C" is
\u005C as text, not a backslash.
Identifiers
identifier:
available_identifier
@ identifier_or_keyword
available_identifier:
identifier_or_keyword that is not a keyword
identifier_or_keyword:
identifier_start_character {identifier_part_character}
- A start character is a letter of Unicode classes Lu, Ll, Lt, Lm, Lo or Nl, or the underscore. A part character also allows decimal digits (Nd), connecting characters (Pc), combining marks (Mn, Mc) and formatting characters (Cf).
- Identifiers are case-sensitive and have no fixed length limit.
- Two identifiers are the same if they are identical after removing the
@prefix, decoding Unicode escapes and removing formatting characters. - An identifier shall be in Unicode Normalization Form C. How other forms behave is left to the implementation.
- Identifiers with two consecutive underscores are reserved for the implementation, though the compiler does not reject them.
The @ prefix
The prefix @ lets a keyword be used as an identifier, which is useful
when calling code from other languages: int @class = 1;. The
@ is not part of the name, so @foo and foo
are the same identifier. The prefix is also allowed on a word that is not a keyword,
though that is poor style.
The underscore
_ is an ordinary identifier. In some positions it is instead a
discard: _ = Compute();, out _, and patterns. See
Variables.
Keywords
C# has 77 reserved keywords (clause 6.4.4). They are identifiers only with the
@ prefix.
| Group | Keywords |
|---|---|
| Built-in types | bool byte sbyte char short ushort int uint long ulong float double decimal object string void |
| Declarations | class struct interface enum delegate event namespace using operator implicit explicit |
| Modifiers | public protected internal private static abstract sealed virtual override readonly volatile const extern unsafe fixed params in out ref |
| Statements | if else switch case default while do for foreach in break continue goto return throw try catch finally lock checked unchecked |
| Expressions | new this base is as typeof sizeof stackalloc default checked unchecked delegate |
| Literals | true false null |
Some words appear in several groups. In alphabetical order the full list is:
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.
Contextual keywords
These words are keywords only in certain places. Everywhere else they are ordinary identifiers, so old code that uses them as names still compiles. New keywords are almost always added this way. Since C# 11 the compiler warns when a type name is all lower-case ASCII, because such names may become keywords.
| Word | Used in |
|---|---|
add, remove | Event accessors |
get, set, init, value, field | Property and indexer accessors; field since C# 14 |
partial | Partial types and members |
record | Record types |
required | Required members |
file | File-local types |
extension | Extension blocks, C# 14 |
union | Union types, C# 15 |
closed | Closed classes, C# 15 |
safe | Memory safety preview, C# 15 |
alias, global | Extern aliases, global:: and global usings |
where, notnull, unmanaged, allows | Generic constraints |
dynamic, nint, nuint, var | Types and implicitly typed variables |
async, await | Asynchronous functions |
yield | Iterators |
when, and, or, not | Patterns; when also in exception filters |
with | With expressions; with(…) in collection expressions (C# 15) |
nameof | The nameof operator |
scoped | Ref safety |
args | Top-level statements |
managed, unmanaged | Function pointer calling conventions |
from, select, group, into, orderby, join, let, on, equals, by, ascending, descending, where | Query expressions |
The specification also treats Cdecl, Stdcall,
Thiscall and Fastcall as contextual, in function pointer
declarations. Writing @await uses a contextual keyword as an identifier,
just as with reserved keywords. Unlike ordinary identifiers, a contextual keyword cannot
contain Unicode escapes when it is used as a keyword.
Literals
A literal is the source form of a value (clause 6.4.5).
| Kind | Examples | Type |
|---|---|---|
| Boolean | true false | bool |
| Integer | 42 0xFF 0b1010 42U 42L 42UL | int, uint, long or ulong |
| Real | 1.5 .5 1e10 2.5f 1.5d 1.5m | double, float or decimal |
| Character | 'a' '\n' '\u0041' | char |
| String | "text" @"C:\dir" """raw""" $"{x}" "text"u8 | string; ReadOnlySpan<byte> for u8 |
| Null | null | The null type, converted to any reference or nullable type |
| Default | default | Target-typed; see Expressions |
Integer literals
| Base | Prefix | Example | Value | Since |
|---|---|---|---|---|
| Decimal | none | 255 | 255 | 1.0 |
| Hexadecimal | 0x or 0X | 0xFF | 255 | 1.0 |
| Binary | 0b or 0B | 0b1111_1111 | 255 | 7.0 |
There are no octal literals: a leading zero means nothing, so 010 is
ten.
| Suffix | Type: the first of these that holds the value |
|---|---|
| none | int, uint, long, ulong |
U or u | uint, ulong |
L or l | long, ulong |
UL, LU, in any case | ulong |
- Digit separators (C# 7.0) are underscores between digits, any
number of them:
1_000_000,0xFFFF_FFFF. C# 7.2 also allows one after the prefix,0x_FF. They may not be at the start or end of the digits. - Use
Lrather thanl, which looks like1; the compiler warns aboutl. 2147483648(231) with no suffix, written directly after unary minus, givesint.MinValue; likewise9223372036854775808(263) with no suffix orLgiveslong.MinValue. A value that fits no type is an error.
Real literals
| Suffix | Type |
|---|---|
none, D or d | double |
F or f | float |
M or m | decimal |
- Digits are required after a decimal point:
1.3Fis a real literal,1.Fis not..5and1e10are real literals.1f,1dand1mare real literals without a point. - There is no hexadecimal real literal.
- Digit separators work here too:
3.141_593. floatanddoublevalues use IEC 60559 round to nearest, with ties to even. A literal too large for its type is a compile-time error, so a real literal never produces infinity; a non-zero literal may round to zero. Adecimalliteral is rounded to the nearest value with banker’s rounding, and keeps the scale written unless rounded, so2.900mis the value 2.9 with a scale of 3.- A real literal cannot produce infinity or NaN. Use
double.PositiveInfinityanddouble.NaN.
Character literals
A character literal is one character or one escape sequence in
single quotes. It holds one UTF-16 code unit, so a character beyond U+FFFF cannot be
written as a char literal; a string or a surrogate pair must be used
instead.
Escape sequences
| Escape | Character | Code |
|---|---|---|
\' | Single quote | U+0027 |
\" | Double quote | U+0022 |
\\ | Backslash | U+005C |
\0 | Null | U+0000 |
\a | Alert | U+0007 |
\b | Backspace | U+0008 |
\e | Escape, C# 13 | U+001B |
\f | Form feed | U+000C |
\n | New line | U+000A |
\r | Carriage return | U+000D |
\t | Horizontal tab | U+0009 |
\v | Vertical tab | U+000B |
\uXXXX | Unicode, four hexadecimal digits | That code unit |
\UXXXXXXXX | Unicode, eight hexadecimal digits | A code point up to U+10FFFF; two code units beyond U+FFFF |
\xH to \xHHHH | Hexadecimal, one to four digits | That code unit |
\x takes as many digits as it can, up to four, so
"\x1E2" is a single character (U+01E2), not U+001E then "2". Using
\u avoids this surprise. A backslash before any other character is an
error. There is no octal escape.
String literals
Each kind of string literal below is a string. Equal literals in the
same assembly refer to the same string instance (clause 6.4.5.6). Add u8
to make a UTF-8 literal instead (see below).
| Kind | Example | Since |
|---|---|---|
| Regular | "C:\\dir" | 1.0 |
| Verbatim | @"C:\dir" | 1.0 |
| Interpolated | $"x is {x}" | 6 |
| Interpolated verbatim | $@"C:\{x}", @$"…" | 6 (@$: 8.0) |
| Raw | """a "b" c""" | 11 |
| Interpolated raw | $"""x is {x}""" | 11 |
Regular strings
A regular string literal is zero or more characters and escapes in double quotes, on one line.
Verbatim strings
A verbatim string starts with @". Backslashes are ordinary characters,
the text may span lines, and "" means one double quote. Line breaks are
kept exactly as they are in the file.
string path = @"C:\Users\Example";
string text = @"First line
second line, ""quoted""";
Interpolated strings
A $ prefix makes a string interpolated (clause 12.8.3).
Expressions in braces are evaluated and formatted:
double pi = 3.14159;
$"pi is {pi:F2}"; // "pi is 3.14"
$"[{42,6}] [{42,-6}]"; // "[ 42] [42 ]"
$"{{ literal braces }} {pi}"; // "{ literal braces } 3.14159"
$"{(pi > 3 ? "big" : "small")}"; // conditional in parentheses
{ interpolation_expression [, alignment] [: format_string] }
{{and}}are literal braces.- An alignment is a constant giving the minimum field width. Positive values align right, negative values align left.
- A conditional must be parenthesised, because
:starts the format string. - Since C# 11 the expression in a hole may contain line breaks, including in a regular interpolated string.
- An interpolated string of constant strings is a constant (C# 10), so it can
initialise a
const. - An interpolated string converts to
string, or toIFormattableandFormattableString. It can also convert to an interpolated string handler (C# 10), such asDefaultInterpolatedStringHandler, for efficient formatting.
Raw strings
A raw string (C# 11) starts and ends with at least three double quotes. No character in it has a special meaning: there are no escapes.
var single = """This is "raw" and \ is just a backslash.""";
var xml = """
<element attr="content">
<body/>
</element>
""";
// "<element attr=\"content\">\n <body/>\n</element>"
var more = """"
The text contains """ three quotes.
"""";
The content of a raw string may not contain a run of double quotes as long as the delimiter or longer, so use a longer delimiter when needed. A single-line raw string may not start or end with a double quote; use the multi-line form then.
A multi-line raw string has these rules:
- The opening quotes must be the last non-white-space characters on their line, and the closing quotes the first non-white-space characters on theirs.
- White space to the left of the closing quotes is removed from every line, and each content line must start with exactly that white space: a space does not match a tab.
- White space after the opening quotes on their line is ignored.
- Lines that are only white space after the opening line are kept. The line break before the closing quotes is not part of the string.
A raw string can be interpolated. The number of $ signs sets how many
braces mark a hole, so other braces can be plain text:
int n = 1;
var json = $$"""
{
"value": {{n}}
}
""";
$$ makes {{ }} the hole, so single braces are literalUTF-8 strings
Any string literal except an interpolated one may be followed by u8 or
U8 (C# 11). The result is a ReadOnlySpan<byte> holding
the UTF-8 bytes of the text, followed by a null byte not counted in the length. Because
it is a ref
struct, it cannot be boxed. Two u8 literals may be joined with
+.
Operators and punctuators
Operators and punctuators are the remaining tokens (clause 6.4.6):
| Kind | Tokens |
|---|---|
| Grouping and separators | { } [ ] ( ) . , : ; ? |
| Arithmetic | + - * / % ++ -- |
| Bitwise | & | ^ ~ << |
| Comparison | == != < > <= >= |
| Logical | ! && || ?? |
| Assignment | = += -= *= /= %= &= |= ^= <<= ??= |
| Other | -> => :: .. |
The right shift >>, the unsigned right shift >>>, and
>>= are not tokens of their own in the specification. They are built from
separate > tokens in the grammar, with no space between them. This is why
List<List<int>> needs no space. Their meanings and precedence are on
the Expressions page.