Skip to content

Expressions and operators

SyntaxDescriptionNote
+ - * / %Arithmetic and remainder
-b(byte)Promotion to int in unary operators
int + long → longType unification in binary operators
-a / !bSign negation and logical negation
++ --Increment and decrement
+= -= *= /= %=Compound assignment
a = b = 3Chained assignment
< > <= >= == !=Numeric comparison
(a, b) == (c, d)Tuple equality
&& ||Short-circuit
& | ^ ~Bitwise logical operators
(a << 3) >> 1Bit shifting
a << (int)nVariable shift amount
"a" + "b" + 1String concatenation
a == "x"String content comparison
$"n={n}"Value interpolation
$"{f:F2}"Interpolation format specifier
$"{n,5}"Interpolation width specifier
s ?? "x"Default value when null
s ??= "x"Assignment only when null
target?.Rotate(...)Null-conditional call
target?.nameNull-conditional member accessOnly forms returning a reference type can be written.
(1 + 2) * 3Parentheses
int.MaxValueStatic member access
this.gameObjectReference to self
(x: 1, y: 2)Named tuple
new Point { X = 1 }Object initializer
new[] { 1, 2 }Array type inference from elements
new int[2, 3]Creating a 2D arrayWith nowhere to remember the element type, a conversion is inserted on every read and write. Three or more dimensions, and listing values at the point of creation, are not supported.
default(int)Default value of a type
s!Null-forgiving operator
unchecked(1 + 2)unchecked as an expressionNothing was being checked to begin with, so the value doesn’t change.
unchecked { ... }unchecked as a block
(int a, int b) = Pair();Tuple deconstruction
nameof(Start)Stringifying a name
typeof(int)Getting the type itself
public delegate int Op(int a);Declaring a delegate
SyntaxDescriptionErrorReasonAlternative
(a, b) == (c, d) (tuples of different element types)Comparing tuples of different element typesTUKI0099not yetConvert each element before comparing
new { X = 1 }Creating an unnamed typeTUKI0001by designUse a named tuple or a struct
new GridCell[2, 3] (a self-declared type)Arranging a self-declared type in 2 dimensionsTUKI0001not yetUse a 1D array or an array of arrays
new List<int> { 1, 2 }Populate a List while creating itTUKI0102, TUKI0101runtimeUse an array
new Dictionary<string,int> { ["k"] = 1 }Populate by index while creating itTUKI0102, TUKI0101runtimeSame as above
new System.Action(Helper)Creating a delegateTUKI0108runtimeCall by specifying a method name
v => v * 2Creating a function inlineTUKI0108, TUKI0001runtimeWrite it as a method and call it by name
Cb Handler; Handler = Do;Holding a function as a valueTUKI0108, TUKI0001runtimeCall it directly, or pass the name of the method to call
private Op F;Declares a field of a delegate typeTUKI0108runtimePass the name of the method to call, and send by name
System.Func<int,int> f = x => x + 1;Assigns a lambda to a variableTUKI0108, TUKI0001runtimeCall the method by name
() => nA lambda that captures an outer variableTUKI0108, TUKI0001runtimePass the value you wanted to capture as an argument, and call the method by name
a?[0]Skip indexing when nullTUKI0001runtimeBranch with if
target?.childCountGetting a value type with ?.TUKI0001runtimeCheck for null before taking it
sizeof(int)Getting the sizeTUKI0099runtimeWrite the constant yourself
checked(1 + 2)Halting when a value overflowsTUKI0001by designCheck with an if that the result fits in range before computing. unchecked does compile
checked { ... }Halting when a value overflows inside a blockTUKI0001by designSame as above
Declaring and using are separateBeing supported for declaration doesn’t mean it can be used. Delegate types are supported as declarations, and the error appears only the moment you write the line that creates a value of that type
Forms ?. supportsCalls with no return value, and member accesses returning a reference type
Unsupported formsForms returning a value type. The result would be a type meaning “a value or null”, and the runtime has no such type
using UnityEngine;
using Tsukimi;
public class ExpressionsArithmetic : TsukimiBehaviour
{
void Start()
{
int a = 7;
int b = 2;
Debug.Log(a + b - a * b / a % b); // => 9
}
}
using UnityEngine;
using Tsukimi;
public class OpUnaryPromo : TsukimiBehaviour
{
void Start()
{
byte b = 1;
int a = -b;
Debug.Log(a); // => -1
}
}
using UnityEngine;
using Tsukimi;
public class OpBinPromo : TsukimiBehaviour
{
void Start()
{
int a = 1;
long b = 2;
long c = a + b;
Debug.Log(c); // => 3
}
}
using UnityEngine;
using Tsukimi;
public class ExpressionsUnary : TsukimiBehaviour
{
void Start()
{
int a = 1;
bool b = false;
Debug.Log(-a + (!b ? 1 : 0)); // => 0
}
}
using UnityEngine;
using Tsukimi;
public class ExpressionsIncrement : TsukimiBehaviour
{
void Start()
{
int a = 1;
a++;
++a;
a--;
--a;
Debug.Log(a); // => 1
}
}
using UnityEngine;
using Tsukimi;
public class ExpressionsCompoundAssign : TsukimiBehaviour
{
void Start()
{
int a = 1;
a += 2;
a -= 1;
a *= 3;
a /= 2;
a %= 4;
Debug.Log(a); // => 3
}
}
using UnityEngine;
using Tsukimi;
public class ExpressionsChainedAssign : TsukimiBehaviour
{
void Start()
{
int a = 0;
int b = 0;
a = b = 3;
Debug.Log(a + b); // => 6
}
}
using UnityEngine;
using Tsukimi;
public class ExpressionsComparison : TsukimiBehaviour
{
void Start()
{
int a = 1;
Debug.Log(a < 2 && a > 0 && a <= 1 && a >= 1 && a == 1 && a != 2); // => true
}
}
using UnityEngine;
using Tsukimi;
public class ExpressionsTupleEquality : TsukimiBehaviour
{
void Start()
{
(int, int) a = (1, 2);
(int, int) b = (1, 2);
Debug.Log(a == b); // => true
}
}
using UnityEngine;
using Tsukimi;
public class ExpressionsShortCircuit : TsukimiBehaviour
{
void Start()
{
int n = 1;
bool b = n > 0 && n < 5;
bool c = n < 0 || n > 0;
Debug.Log(b && c); // => true
}
}
using UnityEngine;
using Tsukimi;
public class ExpressionsBitwise : TsukimiBehaviour
{
void Start()
{
int a = 6;
int b = 3;
Debug.Log((a & b) | (a ^ b) | ~a); // => -1
}
}
using UnityEngine;
using Tsukimi;
public class ExpressionsShift : TsukimiBehaviour
{
void Start()
{
int a = 1;
Debug.Log((a << 3) >> 1); // => 4
}
}
using UnityEngine;
using Tsukimi;
public class OpShiftRelaxed : TsukimiBehaviour
{
void Start()
{
int a = 1;
long n = 2;
int b = a << (int)n;
Debug.Log(b); // => 4
}
}
using UnityEngine;
using Tsukimi;
public class ExpressionsStringConcat : TsukimiBehaviour
{
void Start()
{
string s = "a" + "b" + 1;
Debug.Log(s); // => "ab1"
}
}
using UnityEngine;
using Tsukimi;
public class OpStrEq : TsukimiBehaviour
{
void Start()
{
string a = "x";
Debug.Log(a == "x"); // => true
}
}
using UnityEngine;
using Tsukimi;
public class ExpressionsStringInterpolation : TsukimiBehaviour
{
void Start()
{
int n = 1;
string s = $"n={n}";
Debug.Log(s); // => "n=1"
}
}
using UnityEngine;
using Tsukimi;
public class ExpressionsStringFormatSpec : TsukimiBehaviour
{
void Start()
{
float f = 1.5f;
string s = $"{f:F2}";
Debug.Log(s); // => "1.50"
}
}
using UnityEngine;
using Tsukimi;
public class PeInterpAlign : TsukimiBehaviour
{
void Start()
{
int n = 1;
Debug.Log($"{n,5}"); // => " 1"
}
}
using UnityEngine;
using Tsukimi;
public class ExpressionsNullCoalesce : TsukimiBehaviour
{
void Start()
{
string s = null;
string r = s ?? "x";
Debug.Log(r); // => "x"
}
}
using UnityEngine;
using Tsukimi;
public class ExpressionsNullCoalesceAssign : TsukimiBehaviour
{
void Start()
{
string s = null;
s ??= "x";
Debug.Log(s); // => "x"
}
}
using UnityEngine;
using Tsukimi;
public class ExpressionsNullConditionalVoid : TsukimiBehaviour
{
public Transform target;
void Start()
{
target?.Rotate(Vector3.up);
}
}

When the left side is null, the whole expression becomes null.

using UnityEngine;
using Tsukimi;
public class ExpressionsNullConditionalReference : TsukimiBehaviour
{
public Transform target;
void Start()
{
string s = target?.name;
Debug.Log(s); // => null
}
}
using UnityEngine;
using Tsukimi;
public class PeParen : TsukimiBehaviour
{
void Start()
{
int a = (1 + 2) * 3;
Debug.Log(a); // => 9
}
}
using UnityEngine;
using Tsukimi;
public class PeStaticMember : TsukimiBehaviour
{
void Start()
{
Debug.Log(int.MaxValue); // => 2147483647
}
}
using UnityEngine;
using Tsukimi;
public class PeThis : TsukimiBehaviour
{
void Start()
{
Debug.Log(this.gameObject.name);
}
}
using UnityEngine;
using Tsukimi;
public class PeTupleNamed : TsukimiBehaviour
{
void Start()
{
(int x, int y) p = (x: 1, y: 2);
Debug.Log(p.x); // => 1
}
}
using UnityEngine;
using Tsukimi;
public struct Point { public int X; }
public class PeObjInit : TsukimiBehaviour
{
void Start()
{
Point p = new Point { X = 1 };
Debug.Log(p.X); // => 1
}
}
using UnityEngine;
using Tsukimi;
public class PeArrInferred : TsukimiBehaviour
{
void Start()
{
int[] a = new[] { 1, 2 };
Debug.Log(a.Length); // => 2
}
}

Two indices are written to read and write.

using UnityEngine;
using Tsukimi;
public class ExpressionsArrayCreationRank2 : TsukimiBehaviour
{
void Start()
{
int[,] a = new int[2, 3];
a[1, 2] = 7;
Debug.Log(a[1, 2]); // => 7
Debug.Log(a.GetLength(1)); // => 3
}
}
using UnityEngine;
using Tsukimi;
public class PeDefaultExpr : TsukimiBehaviour
{
void Start()
{
int a = default(int);
Debug.Log(a); // => 0
}
}

! only suppresses the null-check warning. It does nothing at runtime.

#nullable enable
using UnityEngine;
using Tsukimi;
public class PeNullForgiving : TsukimiBehaviour
{
void Start()
{
string? s = Get();
string t = s!;
Debug.Log(t); // => "a"
}
private string? Get() { return "a"; }
}

unchecked states explicitly that overflow isn’t checked.

using UnityEngine;
using Tsukimi;
public class PeUncheckedExpr : TsukimiBehaviour
{
void Start()
{
int a = unchecked(1 + 2);
Debug.Log(a); // => 3
}
}

unchecked on a block doesn’t change the value either.

using UnityEngine;
using Tsukimi;
public class ExpressionsUncheckedBlock : TsukimiBehaviour
{
void Start()
{
int n = 1;
unchecked { n = n + 1; }
Debug.Log(n); // => 2
}
}
using UnityEngine;
using Tsukimi;
public class ExpressionsTupleDeconstruct : TsukimiBehaviour
{
private (int, int) Pair() { return (1, 2); }
void Start()
{
(int a, int b) = Pair();
Debug.Log(a + b); // => 3
}
}
using UnityEngine;
using Tsukimi;
public class ExpressionsNameof : TsukimiBehaviour
{
void Start()
{
Debug.Log(nameof(Start)); // => "Start"
}
}
using UnityEngine;
using Tsukimi;
public class ExpressionsTypeof : TsukimiBehaviour
{
void Start()
{
Debug.Log(typeof(int)); // => typeof:SystemInt32
}
}

You can declare a delegate type, but you cannot make a value of it.

using UnityEngine;
using Tsukimi;
public delegate int Op(int a);
public class DlDeclared : TsukimiBehaviour
{
void Start()
{
Debug.Log(1); // => 1
}
}