Skip to content

Generics

SyntaxDescriptionNote
T First<T>(T a, T b)A method with a type argument
First(3, 4)Type argument inference
T1 Pick<T1, T2>(T1 a, T2 b)2 type arguments
int Count<T>(T[] xs)An array of a type argument
default(T)Default value of a type argument
struct Boxed<T> { public T V; }A struct with a type argument
record Boxed<T>(T V);A record with a type argument
Boxed<Boxed<int>>Nested type arguments
public Boxed<int> B;A field of another type
class Box<T> { … }Declaring a class with a type argument
interface IProducer<T>Declaring an interface with a type argumentUsing this one errors too (Forms that don’t compile (11)).
delegate T Maker<T>();Declaring a delegate with a type argument
where T : structConstrained to value types
where T : classConstrained to reference types
where T : notnullConstraining to types that cannot be null
where T : unmanagedConstrained to value types without references
where T : System.EnumConstrained to enum types
where T : System.DelegateConstrained to delegatesDelegate values can’t be created in this runtime.
where T : IShapeConstrained to types that implement an interface
v.Get()Calling through an interface constraintIt can’t be called through a base-class constraint (Forms that don’t compile (11)).
where T : RootConstrained to derived types of a base class
where T : UConstrained to types convertible to another type argument
where T : new()Constrained to types that can be created without arguments
T x = new T();Creating a value of a type argumentThis runtime doesn’t expose parameterless constructors for value types, so the resulting value is the same as default(T).
where T : struct, IThingMultiple constraints
object o = v; o is intAsking the type of a type argument’s valueThe 14 types you can test for are bool byte sbyte short ushort char int uint long ulong float double decimal string; nothing else can be written (Forms that don’t compile (11)).
abstract class Base<T> : TsukimiBehaviourA base behaviour with a type argumentThe type is fixed at the point of inheritance, so the derived side has no type parameters.
interface IProducer<out T>A type argument used only in output position
interface IConsumer<in T>A type argument used only in input position
SyntaxDescriptionErrorReasonAlternative
Box<int> b = new Box<int>();Using a class with a type argumentTUKI0001runtimeUse a struct or record instead (that works)
IProducer<int> p = new IntProducer();Using an interface with a type argumentTUKI0001by designUse an interface without a type argument
v.V (through where T : Root)Touching a member through a base class constraintTUKI0001runtimeChange to an interface constraint (that lets you call the method)
Make<Holder>() (passing a class to where T : new())Using a reference type as the type argumentTUKI0001runtimePass a struct or record
typeof(T)Getting the type argument’s type itselfTUKI0001undecidedUse a form without a type argument
o is Bead (against an object holding the type argument’s value)Asking about a type not in the 14 aboveTUKI0001not yetRepresent it with a union or interface
int Id<T>(T v) { … } (inside a method)Writing a function with a type argument inlineTUKI0001undecidedWrite it as a method
Outer<int>.InnerA type inside a type with a type argumentTUKI0001runtimeDon’t nest them
where T : class?Constrained to nullable reference typesTUKI0201by designUse where T : class (only this form triggers the null-safety check)
class Leaf<U> : Base<U>Inheriting a base class while passing the type argument throughTUKI0001not yetDecide the type on the derived side (class Leaf : Base<int>)
public class C<T> : TsukimiBehaviourA behaviour with a type argumentTUKI0001not yetUse a form without a type argument (one program becomes one concrete type)
Declaring and using are separateClasses, interfaces, and delegates with type parameters are supported as far as their declaration, and error on the line that uses them (structs and records with type parameters are usable)

There are 3 kinds of what you can do through a constraint, split into “declaration” and “what you can do through the constraint.”

ConstraintDeclarationWhat you can do through it
where T : IShapeForms that compileCan call the method
where T : Root (base class)Forms that compileTouching a member errors
where T : new()Forms that compileCan create a value with new T()
Type parameters on a BehaviourThey can’t go on the Behaviour itself (one program is one concrete type, and the expansion target can’t be decided). They can go on the base, where inheritance fixes the type and the expansion target resolves to exactly one
using UnityEngine;
using Tsukimi;
public class GnMethod : TsukimiBehaviour
{
void Start()
{
Debug.Log(First(3, 4)); // => 3
}
private T First<T>(T a, T b)
{
return a;
}
}
using UnityEngine;
using Tsukimi;
public class GnInference : TsukimiBehaviour
{
void Start()
{
int n = First(3, 4);
float f = First(1f, 2f);
Debug.Log(n + f); // => 4
}
private T First<T>(T a, T b)
{
return a;
}
}
using UnityEngine;
using Tsukimi;
public class GnTwoParameters : TsukimiBehaviour
{
void Start()
{
Debug.Log(Pick(1, "a")); // => 1
}
private T1 Pick<T1, T2>(T1 a, T2 b)
{
return a;
}
}
using UnityEngine;
using Tsukimi;
public class GnArrayParameter : TsukimiBehaviour
{
void Start()
{
int[] a = new int[] { 1, 2 };
Debug.Log(Count(a)); // => 2
}
private int Count<T>(T[] xs)
{
return xs.Length;
}
}
using UnityEngine;
using Tsukimi;
public class GnDefaultOfT : TsukimiBehaviour
{
void Start()
{
Debug.Log(Zero<int>()); // => 0
}
private T Zero<T>()
{
return default(T);
}
}
using UnityEngine;
using Tsukimi;
public struct Boxed<T>
{
public T V;
}
public class GnGenericStruct : TsukimiBehaviour
{
void Start()
{
Boxed<int> b = new Boxed<int>();
b.V = 1;
Debug.Log(b.V); // => 1
}
}
using UnityEngine;
using Tsukimi;
public record Boxed<T>(T V);
public class GnGenericRecord : TsukimiBehaviour
{
void Start()
{
Boxed<int> b = new Boxed<int>(1);
Debug.Log(b.V); // => 1
}
}
using UnityEngine;
using Tsukimi;
public struct Boxed<T>
{
public T V;
}
public class GnNestedArgument : TsukimiBehaviour
{
void Start()
{
Boxed<Boxed<int>> b = new Boxed<Boxed<int>>();
Debug.Log(b.V.V); // => 0
}
}
using UnityEngine;
using Tsukimi;
public struct Boxed<T>
{
public T V;
}
public struct Holder
{
public Boxed<int> B;
public int Get() { return B.V; }
}
public class GnGenericTypeInStruct : TsukimiBehaviour
{
void Start()
{
Holder h = new Holder();
Debug.Log(h.Get()); // => 0
}
}

It can be declared, but using it errors (Forms that don’t compile (11)).

using UnityEngine;
using Tsukimi;
public class Box<T> { public T V; }
public class G2ClassDecl : TsukimiBehaviour
{
void Start()
{
Debug.Log(1); // => 1
}
}

Declaring an interface with a type argument

Section titled “Declaring an interface with a type argument”

Interfaces can take type parameters too.

using UnityEngine;
using Tsukimi;
public interface IProducer<T> { T Make(); }
public class GcGenericInterface : TsukimiBehaviour
{
void Start()
{
Debug.Log(1); // => 1
}
}
using UnityEngine;
using Tsukimi;
public delegate T Maker<T>();
public class GcGenericDelegate : TsukimiBehaviour
{
void Start()
{
Debug.Log(1); // => 1
}
}

Accepts only int or a struct you wrote yourself.

using UnityEngine;
using Tsukimi;
public class GnConstraintStruct : TsukimiBehaviour
{
void Start()
{
Debug.Log(Copy(1)); // => 1
}
private T Copy<T>(T v) where T : struct
{
return v;
}
}
using UnityEngine;
using Tsukimi;
public class GcClass : TsukimiBehaviour
{
void Start()
{
Debug.Log(Take<string>("a")); // => 1
}
private int Take<T>(T v) where T : class { return 1; }
}
using UnityEngine;
using Tsukimi;
public class GcNotNull : TsukimiBehaviour
{
void Start()
{
Debug.Log(Take<int>(1)); // => 1
}
private int Take<T>(T v) where T : notnull { return 1; }
}

Constrained to value types without references

Section titled “Constrained to value types without references”
using UnityEngine;
using Tsukimi;
public class GcUnmanaged : TsukimiBehaviour
{
void Start()
{
Debug.Log(Take<int>(1)); // => 1
}
private int Take<T>(T v) where T : unmanaged { return 1; }
}
using UnityEngine;
using Tsukimi;
public enum Mode { Off, On }
public class GcEnum : TsukimiBehaviour
{
void Start()
{
Debug.Log(Take<Mode>(Mode.On)); // => 1
}
private int Take<T>(T v) where T : System.Enum { return 1; }
}

The constraint can be declared.

using UnityEngine;
using Tsukimi;
public class GcDelegate : TsukimiBehaviour
{
void Start()
{
Debug.Log(1); // => 1
}
private int Take<T>(T v) where T : System.Delegate { return 1; }
}

Constrained to types that implement an interface

Section titled “Constrained to types that implement an interface”
using UnityEngine;
using Tsukimi;
public interface IShape
{
float Area();
}
public struct Box : IShape
{
public float Area() { return 1f; }
}
public struct Wrap<T> where T : IShape
{
public T V;
}
public class GnConstraintInterface : TsukimiBehaviour
{
void Start()
{
Wrap<Box> w = new Wrap<Box>();
Debug.Log(w.V.Area()); // => 1
}
}

Going through an interface constraint lets you call its methods.

using UnityEngine;
using Tsukimi;
public interface IThing { int Get(); }
public class G2IfaceMember : TsukimiBehaviour
{
void Start()
{
Debug.Log(1); // => 1
}
private int Take<T>(T v) where T : IThing { return v.Get(); }
}

Constrained to derived types of a base class

Section titled “Constrained to derived types of a base class”

It can be declared, but touching its members errors (Forms that don’t compile (11)).

using UnityEngine;
using Tsukimi;
public class Root { public int V; }
public class Leaf : Root { }
public class G2BaseDecl : TsukimiBehaviour
{
void Start()
{
Debug.Log(1); // => 1
}
private int Take<T>(T v) where T : Root { return 1; }
}

Constrained to types convertible to another type argument

Section titled “Constrained to types convertible to another type argument”
using UnityEngine;
using Tsukimi;
public interface IThing { int Get(); }
public struct Impl : IThing { public int Get() { return 1; } }
public class GcTypeParam : TsukimiBehaviour
{
void Start()
{
Debug.Log(Take<Impl, Impl>(new Impl())); // => 1
}
private int Take<T, U>(T v) where T : U where U : IThing { return 1; }
}

Constrained to types that can be created without arguments

Section titled “Constrained to types that can be created without arguments”
using UnityEngine;
using Tsukimi;
public class Holder { public int V; }
public class G2NewDecl : TsukimiBehaviour
{
void Start()
{
Debug.Log(1); // => 1
}
private int Take<T>() where T : new() { return 1; }
}

Going through this constraint lets you create a value of the type argument.

using UnityEngine;
using Tsukimi;
public struct Bead
{
public int V;
}
public class R_cnew : TsukimiBehaviour
{
void Start()
{
Debug.Log(Make<Bead>().V); // => 0
}
private T Make<T>() where T : new()
{
T x = new T();
return x;
}
}
using UnityEngine;
using Tsukimi;
public interface IThing { int Get(); }
public struct Impl : IThing { public int Get() { return 1; } }
public class GcMultiple : TsukimiBehaviour
{
void Start()
{
Debug.Log(Take<Impl>(new Impl())); // => 1
}
private int Take<T>(T v) where T : struct, IThing { return v.Get(); }
}

Asking the type of a type argument’s value

Section titled “Asking the type of a type argument’s value”

Assign the type argument’s value to object and you can then test it against a fixed type.

using UnityEngine;
using Tsukimi;
public class GcIsOnT : TsukimiBehaviour
{
void Start()
{
Debug.Log(Check<int>(1)); // => true
}
private bool Check<T>(T v) { object o = v; return o is int; }
}

The base can take type parameters.

using UnityEngine;
using Tsukimi;
public abstract class R_gbase<T> : TsukimiBehaviour
{
public T V;
}
public class R_gleaf : R_gbase<int>
{
void Start()
{
V = 1;
Debug.Log(V); // => 1
}
}

A type argument used only in output position

Section titled “A type argument used only in output position”
using UnityEngine;
using Tsukimi;
public interface IProducer<out T> { T Make(); }
public class GcCovarianceDecl : TsukimiBehaviour
{
void Start()
{
Debug.Log(1); // => 1
}
}

A type argument used only in input position

Section titled “A type argument used only in input position”
using UnityEngine;
using Tsukimi;
public interface IConsumer<in T> { void Take(T v); }
public class GcContravarianceDecl : TsukimiBehaviour
{
void Start()
{
Debug.Log(1); // => 1
}
}