This article describes the syntax of the C# programming language. The features described are compatible with .NET Framework and Mono.
An identifier is the name of an element in the code. It can contain letters, digits and underscores (_
), and is case sensitive (FOO
is different from foo
). The language imposes the following restrictions on identifier names:
Identifier names may be prefixed by an at sign (@
), but this is insignificant; @name
is the same identifier as name
.
Microsoft has published naming conventions for identifiers in C#, which recommends the use of PascalCase for the names of types and most type members, and camelCase for variables and for private or internal fields.[1] However, these naming conventions are not enforced in the language.
Keywords are predefined reserved words with special syntactic meaning.[2] The language has two types of keyword - contextual and reserved. The reserved keywords such as false
or byte
may only be used as keywords. The contextual keywords such as where
or from
are only treated as keywords in certain situations. If an identifier is needed which would be the same as a reserved keyword, it may be prefixed by an at sign to distinguish it. For example, @out
is interpreted as an identifier, whereas out
as a keyword. This syntax facilitates reuse of .NET code written in other languages.
The following C# keywords are reserved words:
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
A contextual keyword is used to provide a specific meaning in the code, but it is not a reserved word in C#. Some contextual keywords, such as partial
and where
, have special meanings in multiple contexts. The following C# keywords are contextual:[3]
add
allows
alias
and
ascending
args
async
await
by
descending
dynamic
equals
from
get
global
group
init
into
join
let
managed
nameof
nint
not
notnull
nuint
on
or
orderby
partial
record
remove
required
select
set
unmanaged
value
var
when
where
with
yield
char |
---|
string |
---|
Unicode character | followed by the hexadecimal unicode code point | |
---|---|---|
Extended_ASCII character | followed by the hexadecimal extended ASCII code point | |
Null character | ||
Tab | ||
Backspace | ||
Carriage return | ||
Form feed | ||
Backslash | ||
Single quote | ||
Double quote | ||
Line feed |
Starting in C# 7.0, the underscore symbol can be used to separate digits in number values for readability purposes. The compiler ignores these underscores.
Variables are identifiers associated with values. They are declared by writing the variable's type and name, and are optionally initialized in the same statement.[8] [9]
Declare
Assigning
Initialize
Multiple variables of the same type can be declared and initialized in one statement.
int a = 2, b = 3; // Declaring and initializing multiple variables of the same type
This is a feature of C# 3.0.
C# 3.0 introduced type inference, allowing the type specifier of a variable declaration to be replaced by the keyword var
, if its actual type can be statically determined from the initializer. This reduces repetition, especially for types with multiple generic type-parameters, and adheres more closely to the DRY principle.
var myNums = new List
Constants are immutable values.
const
When declaring a local variable or a field with the const
keyword as a prefix the value must be given when it is declared. After that it is locked and cannot change. They can either be declared in the context as a field or a local variable. Constants are implicitly static.
This shows both uses of the keyword.
readonly
The readonly
keyword does a similar thing to fields. Like fields marked as const
they cannot change once initialized. The difference is that one can choose to initialize them in a constructor, or to a value that is not known until run-time.[10] This only works on fields. readonly
fields can either be members of an instance or static class members.
Curly braces are used to signify a code block and a new scope. Class members and the body of a method are examples of what can live inside these braces in various contexts.
Inside of method bodies, braces can be used to create new scopes:
A C# application consists of classes and their members. Classes and other types exist in namespaces but can also be nested inside other classes.
Whether it is a console or a graphical interface application, the program must have an entry point of some sort. The entry point of a C# application is the Main
method. There can only be one declaration of this method, and it is a static method in a class. It usually returns void
and is passed command-line arguments as an array of strings.
The main method is also allowed to return an integer value if specified.
This is a feature of C# 7.1.
Asynchronous Tasks can be awaited in the Main
method by declaring the method's return type as Task
.
All the combinations of Task
, or Task<int>,
and with, or without, the string[] args
parameter are supported.
This is a feature of C# 9.0.
Similar to in scripting languages, top-level statements removes the ceremony of having to declare the Program
class with a Main
method.
Instead, statements can be written directly in one specific file, and that file will be the entry point of the program. Code in other files will still have to be defined in classes.
This was introduced to make C# less verbose, and thus more accessible for beginners to get started.
Console.WriteLine("Hello World!");Types are declared after the statements, and will be automatically available from the statements above them.
Namespaces are a part of a type name and they are used to group and/or distinguish named entities from other ones.
A namespace is defined like this:
A namespace can be put inside a namespace like this:
It can also be done like this:
namespace FooNamspace.BarNamespace
In C# 10 and later, namespaces can also be defined using file-scoped declarations by doing the following:[11]
using
directiveThe using
directive loads a specific namespace from a referenced assembly. It is usually placed in the top (or header) of a code file but it can be placed elsewhere if wanted, e.g. inside classes.
The directive can also be used to define another name for an existing namespace or type. This is sometimes useful when names are too long and less readable.
The directive loads the static members of a specified type into the current scope, making them accessible directly by the name of the member.
WriteLine("Hello, World!");
Operator category | Operators | |
---|---|---|
Arithmetic | ,,,, | |
Logical (boolean and bitwise) | ,,,,,,,, | |
String concatenation | ||
Increment, decrement | , | |
Shift | , | |
Relational (conditional) | ,,,,, | |
Assignment | ,,,,,,,,,, | |
Member access | ,, | |
Indexing | ||
Cast | ||
Conditional (ternary) | ||
Delegate concatenation and removal | , | |
Object creation | ||
Type information | ,,, | |
Overflow exception control | , | |
Indirection and Address | ,,, | |
Coalesce | ||
Lambda expression |
Some of the existing operators can be overloaded by writing an overload method.
These are the overloadable operators:
Operators | ||
---|---|---|
,,,,,,, | Unary operators | |
,,,,,,,,, | Binary operators | |
== , != ,,, <= , >= | Comparison operators, must be overloaded in pairs |
The cast operator is not overloadable, but one can write a conversion operator method which lives in the target class. Conversion methods can define two varieties of operators, implicit and explicit conversion operators. The implicit operator will cast without specifying with the cast operator and the explicit operator requires it to be used.
Implicit conversion operator
Explicit conversion operator
as
operatorThe as
operator will attempt to do a silent cast to a given type. It will return the object as the new type if possible, and otherwise will return null.
String str = stream as String; // Will return null.
This is a feature of C# 2.0.The following:
C# 8.0 introduces null-coalescing assignment, such that
C# inherits most of the control structures of C/C++ and also adds new ones like the foreach
statement.
These structures control the flow of the program through given conditions.
if
statementThe if
statement is entered when the given condition is true. Single-line case statements do not require block braces although it is mostly preferred by convention.
Simple one-line statement:
Multi-line with else-block (without any braces):
Recommended coding conventions for an if-statement.
2)else
switch
statementThe switch
construct serves as a filter for different values. Each value leads to a "case". It is not allowed to fall through case sections and therefore the keyword break
is typically used to end a case. An unconditional return
in a case section can also be used to end a case. See also how goto
statement can be used to fall through from one case to the next. Many cases may lead to the same code though. The default case handles all the other cases not handled by the construct.
Iteration statements are statements that are repeatedly executed when a given condition is evaluated as true.
while
loopdo ... while
loopfor
loopThe for
loop consists of three parts: declaration, condition and counter expression. Any of them can be left out as they are optional.
Is equivalent to this code represented with a while
statement, except here the variable is not local to the loop.
foreach
loopThe foreach
statement is derived from the for
statement and makes use of a certain pattern described in C#'s language specification in order to obtain and use an enumerator of elements to iterate over.
Each item in the given collection will be returned and reachable in the context of the code block. When the block has been executed the next item will be returned until there are no items remaining.
Jump statements are inherited from C/C++ and ultimately assembly languages through it. They simply represent the jump-instructions of an assembly language that controls the flow of a program.
goto
statementLabels are given points in code that can be jumped to by using the goto
statement.goto
statement; it may be before it in the source file.
The goto
statement can be used in switch
statements to jump from one case to another or to fall through from one case to the next.
break
statementThe break
statement breaks out of the closest loop or switch
statement. Execution continues in the statement after the terminated statement, if any.
continue
statementThe continue
statement discontinues the current iteration of the current control statement and begins the next iteration.
The while
loop in the code above reads characters by calling, skipping the statements in the body of the loop if the characters are spaces.
Runtime exception handling method in C# is inherited from Java and C++.
The base class library has a class called from which all other exception classes are derived. An -object contains all the information about a specific exception and also the inner exceptions that were caused.Programmers may define their own exceptions by deriving from the class.
An exception can be thrown this way:
Exceptions are managed within blocks.
The statements within the try
block are executed, and if any of them throws an exception, execution of the block is discontinued and the exception is handled by the catch
block. There may be multiple catch
blocks, in which case the first block with an exception variable whose type matches the type of the thrown exception is executed.
If no catch
block matches the type of the thrown exception, the execution of the outer block (or method) containing the try ... catch
statement is discontinued, and the exception is passed up and outside the containing block or method. The exception is propagated upwards through the call stack until a matching catch
block is found within one of the currently active methods. If the exception propagates all the way up to the top-most method without a matching catch
block being found, the entire program is terminated and a textual description of the exception is written to the standard output stream.
The statements within the finally
block are always executed after the try
and catch
blocks, whether or not an exception was thrown. Such blocks are useful for providing clean-up code.
Either a catch
block, a finally
block, or both, must follow the try
block.
C# is a statically typed language like C and C++. That means that every variable and constant gets a fixed type when it is being declared. There are two kinds of types: value types and reference types.
Instances of value types reside on the stack, i.e. they are bound to their variables. If one declares a variable for a value type the memory gets allocated directly. If the variable gets out of scope the object is destroyed with it.
Structures are more commonly known as structs. Structs are user-defined value types that are declared using the struct
keyword. They are very similar to classes but are more suitable for lightweight types. Some important syntactical differences between a class and a struct are presented later in this article.
The primitive data types are all structs.
These are the primitive datatypes.
Primitive types | ||||||
---|---|---|---|---|---|---|
Type name | BCL equivalent | Value | Range | Size | Default value | |
integer | −128 through +127 | 8-bit (1-byte) | ||||
integer | −32,768 through +32,767 | 16-bit (2-byte) | ||||
integer | −2,147,483,648 through +2,147,483,647 | 32-bit (4-byte) | ||||
integer | −9,223,372,036,854,775,808 through +9,223,372,036,854,775,807 | 64-bit (8-byte) | ||||
unsigned integer | 0 through 255 | 8-bit (1-byte) | ||||
unsigned integer | 0 through 65,535 | 16-bit (2-byte) | ||||
unsigned integer | 0 through 4,294,967,295 | 32-bit (4-byte) | ||||
unsigned integer | 0 through 18,446,744,073,709,551,615 | 64-bit (8-byte) | ||||
signed decimal number | −79,228,162,514,264,337,593,543,950,335 through +79,228,162,514,264,337,593,543,950,335 | 128-bit (16-byte) | ||||
floating point number | ±1.401298E−45 through ±3.402823E+38 | 32-bit (4-byte) | ||||
floating point number | ±4.94065645841246E−324 through ±1.79769313486232E+308 | 64-bit (8-byte) | ||||
Boolean | or | 8-bit (1-byte) | ||||
single Unicode character | through | 16-bit (2-byte) |
Note: is not a struct and is not a primitive type.
Enumerated types (declared with enum
) are named values representing integer values.
Enum variables are initialized by default to zero. They can be assigned or initialized to the named values defined by the enumeration type.
Enum type variables are integer values. Addition and subtraction between variables of the same type is allowed without any specific cast but multiplication and division is somewhat more risky and requires an explicit cast. Casts are also required for converting enum variables to and from integer types. However, the cast will not throw an exception if the value is not specified by the type definition.
season++; // Season.Spring (1) becomes Season.Summer (2).season--; // Season.Summer (2) becomes Season.Spring (1).
Values can be combined using the bitwise-OR operator