Skip to content

Expressions

SyntaxDescription
+ - * / %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
`&& \\
`& \^ ~`
(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 access
(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 array
default(int)Default value of a type
s!Null-forgiving operator
checked(1 + 2)checked as an expression
checked { ... }checked as a block
unchecked(1 + 2)unchecked as an expression
unchecked { ... }unchecked as a block
(int a, int b) = Pair();Tuple deconstruction
nameof(Start)Stringifying a name
typeof(int)Getting the type itself
SyntaxDescriptionErrorReasonInstead
(a, b) == (c, d)(要素の型が違うタプル)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](自分で宣言した型)Arranging a self-declared type in 2 dimensionsTUKI0001not yetUse a 1D array or a jagged array (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
a?[0]Skip indexing when nullTUKI0001runtimeBranch with if
target?.childCountGetting a value type with ?.TUKI0001runtimeCheck for null before accessing
sizeof(int)Getting the sizeTUKI0099runtimeWrite the constant yourself
Whether ?. compiles depends on the return type. A call with no return value, and member access that returns a reference type, compile. Forms that return a value type don’t compile. The result would be a type meaning “a value or null,” and that type doesn’t exist in the runtime.
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 receiver is null, the whole expression becomes null. Only forms that return a reference type compile.

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

Read and write with two indices. There’s no place to remember the element type, so a conversion happens on every read and write. Forms with 3 or more dimensions, and forms that lay out values at creation time, don’t compile.

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"; }
}
using UnityEngine;
using Tsukimi;
public class PeCheckedExpr : TsukimiBehaviour
{
void Start()
{
int a = checked(1 + 2);
Debug.Log(a); // => 3
}
}

checked on a block doesn’t check for overflow either, because the runtime has no exception mechanism.

using UnityEngine;
using Tsukimi;
public class ExpressionsCheckedBlock : TsukimiBehaviour
{
void Start()
{
int n = 1;
checked { n = n + 1; }
Debug.Log(n); // => 2
}
}

unchecked explicitly states that overflow isn’t checked. Since it’s not checked anyway, the value doesn’t change.

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