A class is a reference type that combines data and behaviour (C# specification clause 15). This page covers declarations and members. Inheritance and overriding are on Interfaces and inheritance, and record classes on Structs and records.
Class declarations
class_declaration:
{attributes} {class_modifier} [partial] class identifier [type_parameter_list]
[primary_constructor_parameters] [class_base] {type_parameter_constraints_clause}
class_body [;]
public sealed class Account : Entity, IComparable<Account>
{
public const int MaxNameLength = 64; // constant
private static int created; // static field
private decimal balance; // instance field
public Account(string name) { Name = name; created++; } // constructor
public string Name { get; } // property
public decimal this[int year] => balance; // indexer
public event EventHandler? Changed; // event
public void Deposit(decimal amount) // method
{
balance += amount;
Changed?.Invoke(this, EventArgs.Empty);
}
public int CompareTo(Account? other) => balance.CompareTo(other?.balance);
public static Account operator +(Account a, decimal b) { a.Deposit(b); return a; } // operator
}
Class modifiers
| Modifier | Meaning |
|---|---|
public, internal, protected, private, protected internal, private protected | Accessibility |
abstract | Cannot be instantiated; may have abstract members |
sealed | Cannot be derived from |
static | Holds only static members; see Static classes |
unsafe | The whole class is an unsafe context |
new | On a nested class: hides an inherited member with the same name |
partial | Split over several declarations |
file | Local to the file (C# 11) |
closed | Direct subclasses only in the same assembly (C# 15) |
Accessibility
| Modifier | Accessible from | Since |
|---|---|---|
public | Anywhere | 1.0 |
protected internal | The same assembly, or derived classes anywhere | 1.0 |
internal | The same assembly | 1.0 |
protected | The containing class and derived classes | 1.0 |
private protected | The containing class and derived classes in the same assembly | 7.2 |
private | The containing class only | 1.0 |
A member with no modifier has a default: private in a class or struct,
public in an interface or enum. A type directly in a namespace defaults to
internal and may only be public or internal. A
member cannot be more accessible than the types in its signature (clause 7.4.5).
Members
| Member | Clause |
|---|---|
| Constants and fields | 15.4, 15.5 |
| Methods | 15.6 |
| Properties | 15.7 |
| Events | 15.8 |
| Indexers | 15.9 |
| Operators | 15.10 |
| Instance constructors | 15.11 |
| Static constructors | 15.12 |
| Finalizers | 15.13 |
| Nested types | 15.3.9 |
A static member belongs to the type; any other member belongs to each
object. A static member is reached through the type name, and it cannot use
this. A nested type is written inside another class, can reach the private
members of its enclosing types, and defaults to private.
Constants
const declares a compile-time constant (clause 15.4). Its value must be a
constant expression of a numeric type, bool, char,
string, an enum, or null for another reference type. A constant
is implicitly static, and its value is copied into every place that uses it, including
other assemblies.
Fields
public class Sensor
{
private readonly int id; // set here or in a constructor only
private static readonly object Gate = new();
private volatile bool stopping;
private int reading = 10; // field initialiser
}
- A
readonlyfield can be assigned only in its declaration or in a constructor of the same class. The field cannot change, but the object it refers to can. volatilemakes reads and writes follow acquire and release ordering (clause 15.5.4). It is allowed on reference types, pointers, the typessbyte,byte,short,ushort,int,uint,char,floatandbool, enums based on those, and native integers.- Fields have the default value of their type until assigned (clause 9.3).
Methods
method_declaration:
{attributes} {method_modifier} [ref [readonly]] return_type member_name
[type_parameter_list] ( [parameter_list] ) {type_parameter_constraints_clause} method_body
public int Add(int a, int b) { return a + b; }
public int Twice(int a) => a * 2; // expression body, C# 6
public static T First<T>(IList<T> items) => items[0];
public extern void Native(); // implemented outside C#
- Methods are overloaded on name, the number and types of parameters, and whether each parameter is by reference. The return type is not part of the signature (clause 7.5).
- Overload resolution (clause 12.6.4) chooses the best overload at compile time. Since
C# 13 a library can prefer an overload with
[OverloadResolutionPriority]. - A method can return by reference with
reforref readonly; see Variables. - Parameter modifiers,
paramsand optional parameters are on the Variables page.
Local functions
A method can declare other methods in its body (C# 7.0, clause 13.6.4). A local
function may capture variables of the method, unless it is static
(C# 8). It may be declared after it is used, and may have attributes (C# 9).
int Sum(int[] values)
{
return Loop(0, 0);
int Loop(int i, int total) => i == values.Length ? total : Loop(i + 1, total + values[i]);
}
Extension methods
An extension method (C# 3, clause 15.6.10) adds a method to an existing type. It is a
static method in a top-level, non-generic static class, whose first parameter has
the this modifier. It is called as if it were an instance method. Instance
methods take priority over extension methods with the same signature.
public static class StringExtensions
{
public static int WordCount(this string text) => text.Split(' ').Length;
}
"one two".WordCount(); // 2
Extension members
C# 14 adds extension blocks in a top-level, non-generic static class.
A block names the receiver once and can hold methods, properties and static
operators (feature specification Extensions). C# 15 adds indexers.
public static class SequenceExtensions
{
extension<T>(IEnumerable<T> source) // instance-like members
{
public bool IsEmpty => !source.Any();
public IEnumerable<T> Skip2() => source.Skip(2);
}
extension<T>(IEnumerable<T>) // static-like members
{
public static IEnumerable<T> Identity => Enumerable.Empty<T>();
public static IEnumerable<T> operator +(IEnumerable<T> a, IEnumerable<T> b) => a.Concat(b);
}
}
items.IsEmpty; // used like an instance property
IEnumerable<int>.Identity; // used like a static property
- The receiver parameter names the instance. It is required if the block has instance members, and may be left out if it has only static members.
- A type parameter belongs on the block when the receiver uses it, and on the member otherwise. It cannot be in both.
- An extension block does not create a scope for names: members in all blocks of the class must have unique signatures.
- Extension methods in both forms compile to the same intermediate language, so existing methods can be converted without breaking callers.
Instance constructors
A constructor has the class’s name and no return type (clause 15.11). If a class has no constructor, the compiler adds a public parameterless one (protected if the class is abstract).
public class Box
{
private int size;
public Box() : this(1) { } // calls another constructor
public Box(int size) : base() { this.size = size; } // calls the base class
}
When an object is created, the steps run in this order (clause 15.11.4):
- Instance field initialisers of the class, in the order they are written.
- The base class constructor, chosen by the
base(…)initialiser, orbase()if there is none. That applies the same steps to the base class. - The body of the constructor.
This order differs from Java, where field initialisers run after the base constructor.
A constructor with a this(…) initialiser runs the other constructor instead.
A virtual method called from a constructor reaches the most derived override, even
though that class’s constructor has not yet finished.
Primary constructors
A class or struct may list constructor parameters in its header (C# 12, clause 15.11.6). The parameters are in scope throughout the type.
public class Order(string id, int quantity) : Entity(id)
{
public string Id => id; // captures the parameter
public int Quantity { get; } = quantity; // initialises a property
public Order(string id) : this(id, 1) { } // other constructors must call the primary
}
- Unlike in a record, the parameters do not become properties. They are ordinary parameters, and the compiler keeps a hidden field if a member uses one.
- Any other constructor must eventually call the primary one with
this(…). - Base class arguments go on the
class_base:class D(int x) : B(x). this.iddoes not work, because the parameter is not a member.
Static constructors
static Name() { } (clause 15.12) initialises the type. It takes no
parameters and no access modifier. The runtime calls it once, before the first
instance is created or the first static member is used. Static field initialisers run,
in order, just before it.
Finalizers
~Name() { } (clause 15.13) runs when the garbage collector reclaims the
object, at an unknown time. It cannot have parameters or modifiers and cannot be called
directly. Most classes should use
IDisposable instead.
Properties
A property looks like a field but runs code through accessors (clause 15.7).
public class Person
{
private int age;
public int Age // full property
{
get => age;
set => age = value < 0 ? 0 : value; // value is the assigned value
}
public string Name { get; set; } = ""; // auto-property, with an initialiser
public DateTime Created { get; } // read-only
public string Id { get; init; } // init-only
public int Length => Name.Length; // expression-bodied, read-only
public string City { get; private set; } // restricted accessor
}
| Form | Since |
|---|---|
| Different access on the accessors | 2 |
| Auto-implemented properties | 3 |
| Auto-property initialisers and getter-only auto-properties | 6 |
| Expression-bodied properties | 6 |
init accessors | 9 |
required properties | 11 |
| Partial properties | 13 |
field keyword | 14 |
An init accessor can be set only during object initialisation: by a
constructor, an object initialiser, or a with expression.
The field keyword
In an accessor, field (C# 14) names a backing field that the compiler
creates. Either accessor can then have a body, and the other can be automatic.
public string Message
{
get;
set => field = value ?? throw new ArgumentNullException(nameof(value));
}
If a type also has a member called field, use @field or
this.field to reach it.
Required members
A required field or property (C# 11) must be set at object creation,
normally in an object initialiser. A constructor that sets them all can say so with
[SetsRequiredMembers].
public class Person
{
public required string Name { get; init; }
}
var p = new Person { Name = "Ann" }; // omitting Name is a compile-time error
Indexers
An indexer lets an object be used like an array (clause 15.9). It is declared with
this, and may have several parameters of any type. It is an instance member,
and may be overloaded by its parameter types. Indexers can be ref returns.
In compiled code an indexer is a property, named Item by default, or as set
by [IndexerName].
public class Grid
{
private readonly int[,] cells = new int[8, 8];
public int this[int x, int y]
{
get => cells[x, y];
set => cells[x, y] = value;
}
}
grid[1, 2] = 3;
Events
An event exposes a delegate so that other code can subscribe but not raise it (clause
15.8). Outside the class, only += and -= are allowed.
public class Button
{
public event EventHandler? Clicked; // field-like event
private EventHandler? handlers;
public event EventHandler? Pressed // explicit accessors
{
add { handlers += value; }
remove { handlers -= value; }
}
protected void OnClicked() => Clicked?.Invoke(this, EventArgs.Empty);
}
button.Clicked += (sender, e) => Console.WriteLine("clicked");
Inside the class that declares it, a field-like event is a delegate field. A virtual event can be overridden, and a static event belongs to the class. See Delegates.
Operators
A class or struct can define operators for its own types (clause 15.10). An operator
must be public, and, except for the forms in C# 14 below, also
static. At least one parameter must be the containing type T or
T?.
| Operators | Notes |
|---|---|
+x -x !x ~x ++ -- true false | true and false must be declared together |
x + y x - y x * y x / y x % y x & y x | y x ^ y x << y x >> y x >>> y | |
x == y x != y x < y x > y x <= y x >= y | Declared in pairs: == with !=, < with >, <= with >= |
+= -= *= /= %= &= |= ^= <<= >>= >>>= | C# 14; instance methods, return void, take one parameter |
These cannot be overloaded: &&, ||, indexing,
casts, assignment, member access, ?:, ??, ??=,
.., ^x, ->, =>, invocation,
as, is, await, checked,
unchecked, default, delegate, nameof,
new, sizeof, stackalloc, switch,
typeof and with. && and
|| work on a type through its & or | with
true and false (clause 12.17.3); indexing is done with
an indexer; casts with conversion operators.
public struct Fraction
{
public static Fraction operator +(Fraction a, Fraction b) => /* ... */ a;
public void operator +=(Fraction b) { /* ... */ } // compound assignment, C# 14
public void operator ++() { /* ... */ } // instance increment, C# 14
public static Fraction operator checked -(Fraction a) => /* ... */ a; // checked, C# 11
public static Fraction operator -(Fraction a) => /* ... */ a; // its unchecked partner
}
In x += y, if x is a variable, a declared
operator += is preferred over operator +. Declaring
operator + still gives += without a compound form. A postfix
x++ whose value is used does not use an instance operator ++.
A checked operator is the form used in a
checked context, and it needs a
non-checked partner.
Conversion operators
A class may define conversions to or from itself (clause 15.10.4).
implicit needs no cast and should never fail. explicit
needs a cast. explicit operator checked is also allowed (C# 11).
public readonly struct Celsius(double degrees)
{
public double Degrees { get; } = degrees;
public static implicit operator double(Celsius c) => c.Degrees;
public static explicit operator Celsius(double d) => new(d);
}
Static classes
A static class (C# 2, clause 15.2.2) holds only static members and
cannot be instantiated or derived from. It may not have instance constructors, a
base class other than object, or interfaces. Library helpers such as
System.Math are static classes.
Partial members
In a partial type, a member can be declared in one part and implemented in another. One part is the defining declaration and the other the implementing declaration.
| Member | Since | Rules |
|---|---|---|
| Method | 3 | With no access modifier, a void return and no out parameters, it may have no implementation, and calls to it are then removed. With an access modifier or another return type (C# 9), an implementation is required |
| Property, indexer | 13 | Exactly one defining and one implementing declaration. The defining one has accessors with no bodies; the implementing one is not an auto-property |
| Instance constructor | 14 | Exactly one of each. Only the implementing declaration may have this(…) or base(…). Only one part of the type may have a primary constructor |
| Event | 14 | Exactly one of each. The defining declaration is field-like; the implementing declaration has add and remove |