belte

4 Classes and Objects

4.1 Classes

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();

4.2 Members

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;

4.2.1 Fields

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();
}

4.2.2 Methods

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();

4.2.3 Operators

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.

4.3 Modifiers

4.3.1 Accessibility Modifiers

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.

4.3.4 Overriding Modifiers

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.

4.3.2 Static & ConstExpr

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.

4.3.3 Sealed & Abstract

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 { }

4.4 Constructors

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);

4.5 Templates

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 })
});

4.5.1 Constraint Clauses

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

4.6 Enums

Enums are value types that contain a list of integral constants. Enum field values implicitly start at 0 and count up, but explicit values can be specified. The underlying integral type defaults to int but can be specified:

enum MyEnum extends uint8 {
  Field1,
  Field2,
  ...
}

Where Field1 equals 0 and Field2 equals 1. Explicitly declaring field values can be done as such:

enum MyEnum {
  Field1 = 300,
  Field2 = 400,
  ...
}

Creating an instance of an enum type is done by initializing to a field of the enum:

MyEnum myLocal = MyEnum.Field1;

enum MyEnum {
  Field1,
  ...
}

Instances of enum types can interact with their underlying integral type implicitly:

int myLocal = MyEnum.Field1 + 10;
// myLocal = 20

enum MyEnum {
  Field1 = 10
}

4.6.1 Flags

The flags keyword can be used to signal to other developers that the enum is meant to be used with multiple fields at the same time. For example:

var myLocal = MyEnum.Field1 | MyEnum.Field2;

enum flags MyEnum {
  None,
  Field1,
  Field2,
}

Beyond documentation, the flags keyword also changes the default value behavior of enum fields. Instead of incrementally counting up from 0, enum fields will count up in powers of 2 (0, 1, 2, 4, 8, etc.) so that when the fields are combined their bits do not conflict. You can still give fields explicit values like normal.

Additionally, flags enums string cast will display each field component of the value. For example:

var myLocal = MyEnum.Field1 | MyEnum.Field2;
var myString = (string)myLocal;
// myString = "Field1, Field2"

enum flags MyEnum {
  None,
  Field1,
  Field2,
}

4.6.2 Implicit Enum Fields

In target typed expressions, an implicit enum field expression can be used which omits the enum type name. The following are equivalent:

var myLocal = MyEnum.Field1;
MyEnum myLocal = .Field1;

4.6.3 Experimental Underlying Types

When using the Evaluator, enums can additional represent the string and char primitives:

enum MyEnum extends string {
  Field1 = "some string",
}

This feature is experimental and may be removed.