Skip to content

Member declarations

SyntaxDescriptionNote
private int count;Field
private int count = 5;Declaration with initializer
private const int Max = 10;Constant
private static int Count;Static fieldThe value is separate for each instance of the Behaviour and is not shared between instances.
private static readonly int Max = 10;Static readonly field
private void Do()Method declaration
private int Get()Method with a return value
private int Add(int a, int b)Method with parameters
int Add(int a, int b = 2)Default argument
Add(b: 2, a: 1)Named argument
Add(int, int) / Add(float, float)Overload
private int Twice(int n) => n * 2;Expression-bodied method
private static int Zero()Static method
public int Value { get; set; }Auto-property
public int Doubled { get { return n * 2; } }Computed propertyset can’t be written inside a struct (struct and record).
{ get; private set; }Different accessibility for get and set
{ get; set; } = 1;Property initializer
{ get; }Getter-only auto-property
{ get; init; }init accessor
[field: SerializeField]Attribute on the backing field
public virtual int V { get { … } }Virtual propertyTo override it in a derived type, make the base type abstract.
public int this[int i]IndexerThe value in the square brackets is passed as the accessor’s argument.
this[0] = 3;Writing to an indexervalue holds the value being written, and the index arrives as an argument.
~C() { }Finalizer
private volatile int V;volatile
public readonly int Read()readonly member of a value typeA value type is copied on every pass, so adding it changes nothing at runtime.
Add(1) / Add(1, 2)Selection by argument count
Show(1) / Show("a")Selection by argument type
Show(int) beats Show(long)Preference for the candidate with fewer conversions
Sum(1, 2)Expansion into a variable-length argument
Sum(int,int) beats paramsPreference for the normal candidate
Add(1)Omitting a default argument
Pick(1, 2)Inference from arguments
First(new int[] { 1 })Inference from array elements
Left(1, "a")Inference of two type parameters
Depth(new int[1][])Inference from a nested array
true ? 1 : 2LCommon type from both sides of ?:
public struct Inner { ... }Nested type
public partial class CType written in parts
abstract int Get(); / overrideImplementing an abstract method
virtual int Get() / overrideOverriding a default implementationThe base type may be either abstract or concrete.
base.Ring()Calling the base implementationOnly implementations in types you wrote can be called; the base of an event defined by the runtime cannot.
SyntaxDescriptionErrorReasonAlternative
public class Base : TsukimiBehaviour ... public class Leaf : BaseDeclaring two concrete types in the same fileTUKI0109by designSplit them into separate files (inheriting from a concrete type itself does work)
public event System.Action Fired;Declare an eventTUKI0108runtimeCall the method directly, or send it by specifying a name
public event System.Action Fired { add { } remove { } }write the add/remove accessors yourselfTUKI0108runtimeSame as above
Fired?.Invoke()Notify with eventTUKI0108runtimeCall the method directly, or send it by specifying a name
Fired += HandlerSubscribe to eventTUKI0108, TUKI0099runtimeSame as above
public static extern int Native()Declare an extern implementationTUKI0001runtimeCall a method on the runtime
Read(int v) and Read(in int v) side by sideDistinguish candidates by how the argument is passedTUKI0001undecidedRename it. in alone can be used (Variables and argument passing)
public int Twice { get { … } set { … } } (inside a struct)A mutable computed property (value type)TUKI0001not yetUse get only, or make it a method (struct and record)
public int Extra { get; set; } (inside a record)Property (record)TUKI0001by designUse a positional parameter, or make it a method
c[0] = 1; (an indexer inside a struct)Writing to an indexer (value type)TUKI0001not yetMake it a method that returns a new value, or use an indexer on the Behaviour
panel[0] (an indexer on another Behaviour)Read an indexer on another BehaviourTUKI0001undecidedPut a method on the other side and call it
eventIt can’t even be declared. Declaring, subscribing, and raising are all TUKI0108
~C() and volatileThey compile and run with neither an error nor a warning, but writing them changes no behaviour
Members that initialize the type itselfStatic constructors and module initializers are both TUKI0001 (Field initialization and defaults)

Whether a property is supported depends on where it lives and on which of the 3 accessor shapes it has.

Where it’s placedAuto-propertyComputed (get only)Computed (with set)
BehaviourCompilesCompilesCompiles
structCompilesCompilesDoesn’t compile
recordinit worksCompilesDoesn’t compile
InterfaceCompiles (the implementing side must also use an auto-property)Doesn’t compile—
using UnityEngine;
using Tsukimi;
public class MemField : TsukimiBehaviour
{
private int count;
void Start()
{
Debug.Log(count); // => 0
}
}
using UnityEngine;
using Tsukimi;
public class MemFieldInitializer : TsukimiBehaviour
{
private int count = 5;
void Start()
{
Debug.Log(count); // => 5
}
}
using UnityEngine;
using Tsukimi;
public class MemConst : TsukimiBehaviour
{
private const int Max = 10;
void Start()
{
Debug.Log(Max); // => 10
}
}

Read and written without creating an instance.

using UnityEngine;
using Tsukimi;
public class MemStaticField : TsukimiBehaviour
{
private static int Count;
void Start()
{
Count = 1;
Debug.Log(Count); // => 1
}
}

Read without creating an instance, and can’t be changed afterwards.

using UnityEngine;
using Tsukimi;
public class MemStaticReadonly : TsukimiBehaviour
{
private static readonly int Max = 10;
void Start()
{
Debug.Log(Max); // => 10
}
}
using UnityEngine;
using Tsukimi;
public class MemMethod : TsukimiBehaviour
{
private void Do() { Debug.Log(1); } // => 1
void Start()
{
Do();
}
}
using UnityEngine;
using Tsukimi;
public class MemMethodReturn : TsukimiBehaviour
{
private int Get() { return 1; }
void Start()
{
Debug.Log(Get()); // => 1
}
}
using UnityEngine;
using Tsukimi;
public class MemMethodParameters : TsukimiBehaviour
{
private int Add(int a, int b) { return a + b; }
void Start()
{
Debug.Log(Add(1, 2)); // => 3
}
}
using UnityEngine;
using Tsukimi;
public class MemDefaultArgument : TsukimiBehaviour
{
private int Add(int a, int b = 2) { return a + b; }
void Start()
{
Debug.Log(Add(1)); // => 3
}
}
using UnityEngine;
using Tsukimi;
public class MemNamedArgument : TsukimiBehaviour
{
private int Add(int a, int b) { return a + b; }
void Start()
{
Debug.Log(Add(b: 2, a: 1)); // => 3
}
}
using UnityEngine;
using Tsukimi;
public class MemOverload : TsukimiBehaviour
{
private int Add(int a, int b) { return a + b; }
private float Add(float a, float b) { return a + b; }
void Start()
{
Debug.Log(Add(1, 2) + Add(1f, 2f)); // => 6
}
}
using UnityEngine;
using Tsukimi;
public class MemExpressionBodied : TsukimiBehaviour
{
private int Twice(int n) => n * 2;
void Start()
{
Debug.Log(Twice(2)); // => 4
}
}
using UnityEngine;
using Tsukimi;
public class MemStaticMethod : TsukimiBehaviour
{
private static int Zero() { return 0; }
void Start()
{
Debug.Log(Zero()); // => 0
}
}
using UnityEngine;
using Tsukimi;
public class MemAutoProperty : TsukimiBehaviour
{
public int Value { get; set; }
void Start()
{
Value = 1;
Debug.Log(Value); // => 1
}
}

A property whose get body you write yourself.

using UnityEngine;
using Tsukimi;
public class MemComputedProperty : TsukimiBehaviour
{
private int n;
public int Doubled { get { return n * 2; } }
void Start()
{
n = 3;
Debug.Log(Doubled); // => 6
}
}
using UnityEngine;
using Tsukimi;
public class MbAccessorAcc : TsukimiBehaviour
{
void Start()
{
V = 1;
Debug.Log(V); // => 1
}
public int V { get; private set; }
}
using UnityEngine;
using Tsukimi;
public class MbAutoPropInit : TsukimiBehaviour
{
void Start()
{
Debug.Log(V); // => 1
}
public int V { get; set; } = 1;
}
using UnityEngine;
using Tsukimi;
public class MbGetterOnly : TsukimiBehaviour
{
void Start()
{
Debug.Log(V); // => 0
}
public int V { get; }
}
using UnityEngine;
using Tsukimi;
public class MbInitAccessor : TsukimiBehaviour
{
void Start()
{
Debug.Log(V); // => 0
}
public int V { get; init; }
}
using UnityEngine;
using Tsukimi;
public class MbFieldAttr : TsukimiBehaviour
{
void Start()
{
Debug.Log(V); // => 0
}
[field: SerializeField]
public int V { get; set; }
}

A property marked virtual.

using UnityEngine;
using Tsukimi;
public class MbVirtualProp : TsukimiBehaviour
{
void Start()
{
Debug.Log(V); // => 1
}
public virtual int V { get { return 1; } }
}

Declare this[int i] and values of that type can be read as v[i].

using UnityEngine;
using Tsukimi;
public class MbIndexer : TsukimiBehaviour
{
void Start()
{
Debug.Log(this[2]); // => 20
}
public int this[int i] { get { return i * 10; } }
}

Write a set too and they can be written as v[i] = x.

using UnityEngine;
using Tsukimi;
public class MbIndexerSet : TsukimiBehaviour
{
private int a;
private int b;
void Start()
{
this[0] = 3;
this[1] = 4;
Debug.Log(this[0] * 10 + this[1]); // => 34
}
public int this[int i]
{
get { return i == 0 ? a : b; }
set { if (i == 0) { a = value; } else { b = value; } }
}
}

The runtime has no mechanism for calling a finalizer, so the body never runs.

using UnityEngine;
using Tsukimi;
public class MbFinalizer : TsukimiBehaviour
{
void Start()
{
Debug.Log(1); // => 1
}
~MbFinalizer() { }
}

There are no concurrent flows of execution, so volatile doesn’t change what a read or write means.

using UnityEngine;
using Tsukimi;
public class MbVolatile : TsukimiBehaviour
{
void Start()
{
Debug.Log(V); // => 0
}
private volatile int V;
}

It can go on a member that doesn’t modify the value.

using UnityEngine;
using Tsukimi;
public struct Point { public int X;
public readonly int Read() { return X; } }
public class MbReadonlyMember : TsukimiBehaviour
{
void Start()
{
Point p = new Point();
Debug.Log(p.Read()); // => 0
}
}
using UnityEngine;
using Tsukimi;
public class OvByArity : TsukimiBehaviour
{
void Start()
{
Debug.Log(Add(1)); // => 1
Debug.Log(Add(1, 2)); // => 3
}
private int Add(int a) { return a; }
private int Add(int a, int b) { return a + b; }
}
using UnityEngine;
using Tsukimi;
public class OvByType : TsukimiBehaviour
{
void Start()
{
Debug.Log(Show(1)); // => 1
Debug.Log(Show("a")); // => 2
}
private int Show(int a) { return 1; }
private int Show(string a) { return 2; }
}

Preference for the candidate with fewer conversions

Section titled “Preference for the candidate with fewer conversions”
using UnityEngine;
using Tsukimi;
public class OvBetterConv : TsukimiBehaviour
{
void Start()
{
Debug.Log(Show(1)); // => 2
}
private int Show(long a) { return 1; }
private int Show(int a) { return 2; }
}
using UnityEngine;
using Tsukimi;
public class OvExpanded : TsukimiBehaviour
{
void Start()
{
Debug.Log(Sum(1, 2)); // => 2
Debug.Log(Sum(new int[] { 1, 2 })); // => 2
}
private int Sum(params int[] xs) { return xs.Length; }
}
using UnityEngine;
using Tsukimi;
public class OvNormalBeatsParams : TsukimiBehaviour
{
void Start()
{
Debug.Log(Sum(1, 2)); // => 1
}
private int Sum(int a, int b) { return 1; }
private int Sum(params int[] xs) { return 2; }
}
using UnityEngine;
using Tsukimi;
public class OvDefaultArg : TsukimiBehaviour
{
void Start()
{
Debug.Log(Add(1)); // => 3
Debug.Log(Add(1, 5)); // => 6
}
private int Add(int a, int b = 2) { return a + b; }
}
using UnityEngine;
using Tsukimi;
public class OvInferArgs : TsukimiBehaviour
{
void Start()
{
Debug.Log(Pick(1, 2)); // => 1
}
private T Pick<T>(T a, T b) { return a; }
}
using UnityEngine;
using Tsukimi;
public class OvInferLower : TsukimiBehaviour
{
void Start()
{
Debug.Log(First(new int[] { 1 })); // => 1
}
private T First<T>(T[] xs) { return xs[0]; }
}
using UnityEngine;
using Tsukimi;
public class OvInferTwo : TsukimiBehaviour
{
void Start()
{
Debug.Log(Left(1, "a")); // => 1
}
private T1 Left<T1, T2>(T1 a, T2 b) { return a; }
}

The element type is also determined from a nested array like T[][].

using UnityEngine;
using Tsukimi;
public class OvInferNested : TsukimiBehaviour
{
void Start()
{
Debug.Log(Depth(new int[1][])); // => 1
}
private int Depth<T>(T[][] xs) { return xs.Length; }
}
using UnityEngine;
using Tsukimi;
public class OvBestCommon : TsukimiBehaviour
{
void Start()
{
long v = true ? 1 : 2L;
Debug.Log(v); // => 1
}
}
using UnityEngine;
using Tsukimi;
public class MemNestedType : TsukimiBehaviour
{
public struct Inner { public int V; }
void Start()
{
Inner i = new Inner();
Debug.Log(i.V); // => 0
}
}
using UnityEngine;
using Tsukimi;
public partial class R_part : TsukimiBehaviour
{
void Start()
{
Debug.Log(Helper());
}
}
public partial class R_part
{
private int extra;
int Helper()
{
extra = 41;
return extra + 1;
}
}
using UnityEngine;
using Tsukimi;
public abstract class MemAbstractBase : TsukimiBehaviour
{
public abstract int Get();
}
public class MemAbstractLeaf : MemAbstractBase
{
public override int Get() { return 1; }
void Start()
{
Debug.Log(Get()); // => 1
}
}

A virtual method can be overridden with override.

using UnityEngine;
using Tsukimi;
public abstract class MemVirtualBase : TsukimiBehaviour
{
public virtual int Get() { return 0; }
}
public class MemVirtualLeaf : MemVirtualBase
{
public override int Get() { return 1; }
void Start()
{
Debug.Log(Get()); // => 1
}
}

From the override, the implementation written in the base can be called with base.

using UnityEngine;
using Tsukimi;
public abstract class MemBaseCallBase : TsukimiBehaviour
{
public int hits;
public virtual void Ring() { hits = hits + 1; }
}
public class MemBaseCallLeaf : MemBaseCallBase
{
public int extra;
public override void Ring()
{
base.Ring();
extra = extra + 1;
}
void Start()
{
Ring();
Debug.Log(hits); // => 1
Debug.Log(extra); // => 1
}
}