Classes are structures that contain data and functionality in the form of fields and methods. Fields are similar to variables, and methods are similar to functions both in syntax and functionality.
Classes are declared using the class keyword:
class MyClass {
// Members
}
An object is an instance of a class, and can be created using the new keyword:
new MyClass();
Members of an instance can be accessed externally using a member accession:
myInstance.member;
Members can also be accessed internally (i.e. inside of a method) using the this keyword:
this.member;
If no local symbol names conflict, the this keyword can be omitted:
member;
Fields are similar to variables or constants. They are declared as such:
class MyClass {
int myField1;
string myField2 = "Starting Value";
// etc.
}
Fields have the same flexibility as traditional variables and constants, meaning they can be any type including another class:
class A { }
class B {
A myField = new A();
}
Methods are similar to functions. They are declared as such:
class MyClass {
public void MyMethod(int param1) {
// Body
}
}
When accessing a method, it must be called:
var myInstance = new MyClass();
myInstance.MyMethod();
Operators are similar to methods. They are declared as such:
class MyClass {
public static MyClass operator+(MyClass left, MyClass right) {
// Body
}
}
Operator overloading is used to allow custom classes to use syntactical operators. The overloadable operators are:
| Operators | Notes |
|---|---|
+x, -x, !x, ~x, ++, --, x[] |
|
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 |
Must be overloaded in the following pairs: == and !=, < and >, <= and >= |
Note that operators must be marked public and static.
Public indicates that the member can be seen everywhere, including outside the class. Protected indicates that the member can only be seen within the class and child classes. Private indicates that the member can only be seen within the class, not even in child classes.
class MyClass {
private int a;
protected int b;
public int c;
}
A member can only have one accessibility modifier, but they do not require the modifier. By default, all struct members are public and all class members are private.
All types of members can be marked with all three accessibility modifiers except operators, which must always be public.
By default, members cannot be overridden. To allow a member to be overridden, it can be marked virtual. Virtual
members still require a definition. To override a virtual member, the override can be marked override. To instead
hide a member without overriding, a member can be marked new. A member cannot be marked as both override and new
or virtual.
Similar to virtual, a member can be marked abstract. Abstract members must be overridden in all non-abstract child
implementations, and as such abstract members do not have a definition when declared.
Currently, these modifiers only apply to methods.
Class members are instance members by default, meaning they require an instance to access. With the static and
constexpr keywords methods and fields respectively can be accessed without an instance.
class MyClass {
public constexpr int a = 3;
public static void B() { }
}
var myA = MyClass.a;
MyClass.B();
Classes themselves can also be marked as static, meaning that all contained members must also be static or constant
expressions.
Classes can be marked as sealed to indicate that they cannot be derived.
sealed class A { }
Classes can be marked as abstract to indicate that they must be derived.
abstract class A { }
When creating an object, values can be passed to modify the creation process. By default, no values are passed:
class MyClass { }
new MyClass();
But with a constructor data can be allowed to be passed when creating the object. Constructors are declared similar to
methods, but do not have a return type and they use the constructor keyword in place of an identifier.
class MyClass {
int myField;
public constructor(int a) {
this.myField = a;
}
}
new MyClass(4);
Classes can be declared as templates and take in template arguments that change how the class operates at runtime. Template arguments can either be compile-time constants or types (similar to C++ templates and C# generics).
To demonstrate this concept, a simplified definition for a List type is shown:
class List<type t> {
t[] array;
int length;
public constructor(t[] array) {
this.array = array;
length = Length(array);
}
public static ref t operator[](List<t> list, int index) {
return ref list.array[index];
}
}
This List template can be used to create List objects of different types:
var myList = new List<int>(
{ 1, 2, 3 }
);
var myList = new List<string>(
{ "Hello", "world!" }
);
var myList = new List<bool[]>(
{
{ true, false },
{ false },
{ true, true, false }
}
);
These types are not limited to primitives:
var myList = new List<List<int>>({
new List<int>({ 1, 2, 3 }),
new List<int>({ 3 }),
new List<int>({ 3, 3, 2, 45 })
});
Templates can be constrained at compile-time to ensure intended functionality. These constraints are defined within a
single where block in the class header.
These expressions are enforced at compile-time, and as such they must be computable at compile time. To be computable at compile time, the set of allowed expressions is limited:
| Expression | Additional Restrictions |
|---|---|
| Unary | |
| Binary | |
| Ternary | |
| Cast | Only compiler-computable casts; only casts between primitives |
| Index | Only constant indexes on constant initializer lists |
| Member access | Only when accessing members that are compile-time constants, meaning the accessed expression does not need to be a compile-time constant |
| Extend | Only on type template parameters |
| Initializer list | Only when every item is a compile-time constant |
| Literal | |
| Variable | Only template parameters |
Given the class definition:
class Int<int min, int max> where { min <= max; } {
...
}
Then we can see the following examples:
Int<0, 10>
Int<5, 5>
Int<10, 0> // Compile error