Skip to content

Generics

SyntaxDescription
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 argument
delegate T Maker<T>();Declaring a delegate with a type argument
where T : structRestricted to value types
where T : classRestricted to reference types
where T : notnullRestricting to types that cannot be null
where T : unmanagedRestricted to value types without references
where T : System.EnumRestricted to enum types
where T : System.DelegateRestricted to delegates
where T : IShapeRestricted to types that implement an interface
v.Get()Calling through an interface constraint
where T : RootRestricted to derived types of a base class
where T : URestricted to types convertible to another type argument
where T : new()Restricted to types that can be created without arguments
T x = new T();Creating a value of a type argument
where T : struct, IThingMultiple constraints
object o = v; o is intAsking the type of a type argument’s value
abstract class Base<T> : TsukimiBehaviourA base behaviour with a type argument
interface IProducer<out T>A type argument used only in output position
interface IConsumer<in T>A type argument used only in input position
SyntaxDescriptionErrorReasonInstead
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(where T : Root を通して)Touching a member through a base class constraintTUKI0001runtimeChange to an interface constraint (that lets you call the method)
Make<Holder>()(where T : new() に class を渡す)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(型引数の値を入れた object に対して)Asking about a type not in the 14 aboveTUKI0001not yetRepresent it with a union or interface
int Id<T>(T v) { … }(メソッドの中)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?Restricted 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 different things. A class, interface, or delegate with a type argument compiles up through the declaration, but the line that uses it is rejected. A struct or record with a type argument can be used.

Constraints split into “declaration” and “what you can do through the constraint.”

ConstraintDeclarationWhat you can do through it
where T : IShapeCompilesCan call the method
where T : Root (base class)CompilesRejected when you touch a member
where T : new()CompilesCan create a value with new T()

A type argument can’t be attached to the behaviour itself. One program is one concrete type, so there’s no expansion target to decide. It can be attached to the base class: the type is fixed at the point of inheritance, so the expansion target is fixed 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
}
}

The declaration compiles, but using it is rejected (Forms that don’t compile).

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”

An interface can also have a type argument. Using it is rejected too (Forms that don’t compile).

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

Restricted to value types without references

Section titled “Restricted 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 compiles. This runtime can’t create a delegate value.

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

Restricted to types that implement an interface

Section titled “Restricted 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
}
}

An interface constraint lets you call its method. A base class constraint doesn’t (Forms that don’t compile).

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

Restricted to derived types of a base class

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

The declaration compiles, but touching its member is rejected (Forms that don’t compile).

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

Restricted to types convertible to another type argument

Section titled “Restricted 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; }
}

Restricted to types that can be created without arguments

Section titled “Restricted 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; }
}

This constraint lets you create a value of the type argument. This runtime doesn’t expose a value type’s parameterless constructor, so the resulting value is the same as default(T).

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”

Put the type argument’s value into object, then you can ask whether it’s a fixed type. The types you can ask about are these 14: bool byte sbyte short ushort char int uint long ulong float double decimal string. Anything else doesn’t compile (Forms that don’t compile).

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

You can put a type argument on the base class. The type is fixed at the point of inheritance, so the derived side doesn’t have a type argument.

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