コンテンツにスキップ

ジェネリクス

書き方説明
T First<T>(T a, T b)型引数を持つメソッド
First(3, 4)型引数の推論
T1 Pick<T1, T2>(T1 a, T2 b)2 つの型引数
int Count<T>(T[] xs)型引数の配列
default(T)型引数の既定値
struct Boxed<T> { public T V; }型引数を持つ struct
record Boxed<T>(T V);型引数を持つ record
Boxed<Boxed<int>>型引数の入れ子
public Boxed<int> B;別の型のフィールド
class Box<T> { … }型引数を持つ class の宣言
interface IProducer<T>型引数を持つインタフェースの宣言
delegate T Maker<T>();型引数を持つデリゲートの宣言
where T : struct値型への限定
where T : class参照型への限定
where T : notnullnull にならない型への限定
where T : unmanaged参照を含まない値型への限定
where T : System.Enum列挙型への限定
where T : System.Delegateデリゲートへの限定
where T : IShapeインタフェースを実装した型への限定
v.Get()インタフェースの制約を通した呼び出し
where T : Root基底クラスの派生への限定
where T : U別の型引数へ変換できる型への限定
where T : new()引数なしで作れる型への限定
T x = new T();型引数の値の生成
where T : struct, IThing制約の列挙
object o = v; o is int型引数の値に型を問う
abstract class Base<T> : TsukimiBehaviour型引数を持つ基底の Behaviour
interface IProducer<out T>取り出す向きにだけ使う型引数
interface IConsumer<in T>渡す向きにだけ使う型引数
書き方説明エラー理由代わりに
Box<int> b = new Box<int>();型引数を持つ class を使うTUKI0001環境struct か record にする(そちらは使える)
IProducer<int> p = new IntProducer();型引数を持つインタフェースを使うTUKI0001設計型引数を使わないインタフェースにする
v.V(where T : Root を通して)基底クラスの制約を通してメンバに触るTUKI0001環境インタフェースの制約に変える(そちらはメソッドを呼べる)
Make<Holder>()(where T : new() に class を渡す)参照型を型引数にするTUKI0001環境struct か record を渡す
typeof(T)型引数の型そのものを取るTUKI0001未定型引数を使わない形にする
o is Bead(型引数の値を入れた object に対して)上の 14 個に無い型を問うTUKI0001未実装union かインタフェースで表す
int Id<T>(T v) { … }(メソッドの中)その場で型引数つきの関数を書くTUKI0001未定メソッドとして書く
Outer<int>.Inner型引数を持つ型の中の型TUKI0001環境入れ子にしない
where T : class?null になりうる参照型への限定TUKI0201設計where T : class にする(この形だけは null 安全の検査が反応する)
class Leaf<U> : Base<U>型引数を渡したまま基底を継承するTUKI0001未実装派生側で型を決める(class Leaf : Base<int>
public class C<T> : TsukimiBehaviour型引数を持つ BehaviourTUKI0001未実装型引数を使わない形にする(1 つのプログラムは 1 つの具体的な型になる)
宣言できることと、使えることは別です。型引数を持つ class・インタフェース・デリゲートは、宣言までは通って、使った行で断られます。型引数を持つ struct と record なら使えます。

制約は「宣言」と「制約を通して何ができるか」で割れます。

制約宣言通してできること
where T : IShape通るメソッドを呼べる
where T : Root(基底クラス)通るメンバに触ると断られる
where T : new()通るnew T() で値を作れる

型引数は、Behaviour そのものには付けられません。1 つのプログラムは 1 つの具体的な型で、展開先を決められないためです。基底には付けられます。継承した時点で型が決まるので、展開先が 1 通りに定まるためです。

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

宣言はできますが、使うと断られます(書けない形)。

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

型引数を持つインタフェースの宣言

Section titled “型引数を持つインタフェースの宣言”

インタフェースにも型引数を付けられます。こちらも使うと断られます(書けない形)。

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

型引数を持つデリゲートの宣言

Section titled “型引数を持つデリゲートの宣言”
using UnityEngine;
using Tsukimi;
public delegate T Maker<T>();
public class GcGenericDelegate : TsukimiBehaviour
{
void Start()
{
Debug.Log(1); // => 1
}
}

int や自分で作った struct だけを受け付けます。

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

制約は宣言できます。この環境ではデリゲートの値を作れません。

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

インタフェースを実装した型への限定

Section titled “インタフェースを実装した型への限定”
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
}
}

インタフェースの制約を通した呼び出し

Section titled “インタフェースの制約を通した呼び出し”

インタフェースの制約を通すと、そのメソッドを呼べます。基底クラスの制約では呼べません(書けない形)。

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

宣言はできますが、そのメンバに触ると断られます(書けない形)。

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

別の型引数へ変換できる型への限定

Section titled “別の型引数へ変換できる型への限定”
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; }
}
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; }
}

この制約を通すと、型引数の値を作れます。この環境は値型の引数なしコンストラクタを公開していないので、できあがる値は 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(); }
}

型引数の値を object に入れてから、決まった型かどうかを問えます。問える型は bool byte sbyte short ushort char int uint long ulong float double decimal string の 14 個で、それ以外は書けません(書けない形)。

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

基底に型引数を付けられます。継承した時点で型が決まるので、派生側は型引数を持ちません。

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

取り出す向きにだけ使う型引数

Section titled “取り出す向きにだけ使う型引数”
using UnityEngine;
using Tsukimi;
public interface IProducer<out T> { T Make(); }
public class GcCovarianceDecl : TsukimiBehaviour
{
void Start()
{
Debug.Log(1); // => 1
}
}
using UnityEngine;
using Tsukimi;
public interface IConsumer<in T> { void Take(T v); }
public class GcContravarianceDecl : TsukimiBehaviour
{
void Start()
{
Debug.Log(1); // => 1
}
}