This article describes the syntax of the C# programming language. The features described are compatible with .NET Framework and Mono.
Basics
Identifier An identifier is the name of a code element. 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: They cannot start with a digit; They cannot start with a symbol, except for the at sign (@), which can be used to prefix keywords to allow them as identifiers; They cannot contain more than 511 characters.
Identifier names may be prefixed by @, which allows the use of reserved keywords as identifiers (for example, @class), but the @ is not considered part of the identifier's name for most purposes; @name and name refer to the same identifier.
Microsoft offered naming conventions for identifiers in C#, recommending the use of PascalCase for the names of types and most type members, and camelCase for variables and for private or internal fields. These naming conventions are not enforced by the language.
Keywords
Keywords are words with syntactic meaning. The language has two types of keyword — contextual and reserved. Reserved keywords such as false or byte may only be used as keywords. Contextual keywords such as where or from are treated as keywords only in certain situations. If an identifier is needed that matches a reserved keyword, it is prefixed by an @ sign to distinguish it. For example, @out is interpreted as an identifier, whereas out is a keyword. This syntax facilitates reuse of .NET code written in other languages.
The following C# keywords are reserved words, of which there are 77.
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 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 meanings in multiple contexts. The following 42 C# keywords are contextual.
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
Preprocessor directives Although C# does not have a separate preprocessor, unlike C and C++ which use the C preprocessor, these 13 directives are processed as if there were one.
#nullable #if #elif #else #endif #define #undef #region #endregion #error #warning #line #pragma
Literals + Integers decimal hexadecimal binary + Floating-point values float double decimal + Characters char + Strings string + Character escapes in strings Unicode character followed by the hexadecimal unicode code point Extended_ASCII character followed by the hexadecimal extended ASCII code point Null character Null character Tab Tab Backspace Backspace Carriage return Carriage return (moves cursor to the start of the current line) Form feed Form feed (page break) Backslash Backslash Single quote Single quote Double quote Double quote Line feed Line feed (moves cursor to the next line)
Digit separators 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. int bin = 0b1101_0010_1011_0100; int hex = 0x2F_BB_4A_F1; int dec = 1_000_500_954; double real = 1_500.200_2e-1_000; Generally, separators may be put only between digit characters. It cannot be put at the beginning () or the end of the value ( or ), next to the decimal in floating point values (), next to the exponent character (), or next to the type specifier ().
Variables 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.
Declare int myInt; // Declaring an uninitialized variable called 'myInt', of type 'int'
Assigning int myInt; // Declaring an uninitialized variable myInt = 35; // Assigning the variable a value
Initialize int myInt = 35; // Declaring and initializing the variable
Multiple variables of the same type can be declared and initialized in one statement. int a, b; // Declaring multiple variables of the same type
int a = 2, b = 3; // Declaring and initializing multiple variables of the same type
Local variable type inference 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 myChars = new char[] {'A', 'Ö'}; // equivalent to: char[] myChars = new char[] {'A', 'Ö'};
var myNums = new List<int>(); // equivalent to: List<int> myNums = new List<int>();
Constants 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. Thereafter the value cannot change. They can either be declared in the context as a field or a local variable. Constants are implicitly static. const double PI = 3.14;
This shows both uses of the keyword. public class Foo { private const double X = 3;
public Foo()
const int y = 2;
readonly The readonly keyword does a similar thing to fields. Like fields marked as const their value 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. This works only on fields. readonly fields can either be members of an instance or static class members.
Code blocks 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: void DoSomething() { int a;
int b; a = 1;
a = 2; b = 3; // Will fail because the variable is declared in an inner scope. }
Program structure A C# application consists of classes and their members. Classes and other types exist in namespaces and can be nested inside other classes.
Main method Every program must have an entry point. 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. static void Main(string[] args) // string[] args can be omitted if the program doesn't have any command-line arguments.
The main method may return an integer value if specified. static int Main(string[] args) { return 0; }
Async Main 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. static async Task Main(string[] args) { await DoWorkAsync(42); }
All combinations of Task, or Task<int>, and with, or without, the string[] args parameter are supported.
Top-level statements This is a feature of C# 9.0.
Similar to in scripting languages, top-level statements remove the ceremony of having to declare the Program class with a Main method.
Instead, statements can be written directly in one file, which is the program entry point. Code in other files must be defined in classes.
This was introduced to make C# less verbose, and thus more accessible.using System;
Console.WriteLine("Hello World!");Types are declared after the statements, and are available from the statements above them.
Namespaces Namespaces are a part of a type name and are used to group and/or distinguish named entities from other ones. System.IO.DirectoryInfo // DirectoryInfo is in the System.IO-namespace
A namespace is defined with the namespace keyword: namespace FooNamespace { // Members }
A namespace can be nested inside a namespace in three ways: namespace FooNamespace { // Members namespace BarNamespace
// Members
and: namespace FooNamspace { // Members }
namespace FooNamspace.BarNamespace { // Members }
In C# 10 and later, namespaces can be defined using file-scoped declarations by doing the following: namespace FooNamespace; // the brackets are omitted here in favor of a semicolon.
using directive The 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, e.g. inside a class. using System; using System.Collections;
The directive can also be used to define another name for an existing namespace or type. This is useful when names are long. using Net = System.Net; using DirInfo = System.IO.DirectoryInfo;
directive The directive loads the static members of a specified type into the current scope, making them accessible directly by the name of the member. using static System.Console;
WriteLine("Hello, World!");
directive Since C# 10, it is legal to write directives to indicate a using directive once and apply it to all source files. global using System; global using Microsoft.Extensions.FileSystemGlobbing;
Operators Operator categoryOperatorsArithmetic, , , , Logical (boolean and bitwise), , , , , , , , String concatenationIncrement, decrement, Shift, Relational (conditional), , , , , Assignment, , , , , , , , , , Member access, , IndexingCastConditional (ternary)Delegate concatenation and removal, Object creation Type information, , , Overflow exception control, Indirection and Address, , , CoalesceLambda expression
Operator overloading Some operators can be overloaded by writing an overload method. public static Foo operator+(Foo foo, Bar bar) { return new Foo(foo.Value + bar.Value); }
These are the overloadable operators: Operators, , , , , , , Unary operators, , , , , , , , , Binary operators==, !=, , , <=, >=Comparison operators, must be overloaded in pairs
Assignment operators ( etc.) are combinations of a binary operator and the assignment operator () and is evaluated using the ordinary operators, which can be overloaded. Cast operators () cannot be overloaded, but it is possible to define conversion operators. Array indexing () operator is not overloadable, but it is possible to define new indexers.
Conversion operators The cast operator is not overloadable, but a conversion operator method can live 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 () while the explicit operator requires it to be used.
Implicit conversion operator class Foo { public int Value;
public static implicit operator Foo(int value)
return new Foo(value);
// Implicit conversion Foo foo = 2;
Explicit conversion operator class Foo { public int Value;
public static explicit operator Foo(int value)
return new Foo(value);
// Explicit conversion Foo foo = (Foo)2;
as operator The 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. Stream stream = File.Open(@"C:\Temp\data.dat"); FileStream fstream = stream as FileStream; // Will return an object.
String str = stream as String; // Will return null.
Null coalesce operator