English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية

C# グェネリクス

泛型是指一般形式,而不是特定形式。在C#中,泛型意味着不特定于特定数据类型。

C#允许您使用type参数并且不使用特定数据类型来定义泛型类、接口、抽象类、字段、方法、静态方法、属性、事件、委托和运算符。类型参数是在创建泛型类型的实例时指定的特定类型的占位符。

通过在类型名称后的尖括号中指定类型参数来声明泛型,例如 TypeName<T>,这里 T 是类型参数。

泛型类

泛型类是在类名称后的尖括号中使用类型参数定义的。下面定义了一个泛型类。

クラス データストア<T>
{
    public T Data { get; set; }
}

上面,DataStore 是一个泛型类。T称为类型参数,可用作DataStore类中的字段、属性、方法参数、返回类型和DataStore类中的委托的类型。例如,Data是泛型属性,因为我们使用了类型参数T作为其类型,而不是特定的数据类型。

注意:通常,只有一个类型参数时可以使用T。如果有多个参数类型,不要将T用作类型参数,您可以为类型参数指定任何名称。建议根据要求使用更可读的类型参数名称,如TSession、TKey、TValue等。

您还可以定义多个类型参数,并用逗号分隔。

class KeyValuePair<TKey, TValue>
{
    public TKey Key { get; set; }
    public TValue Value { get; set; }
}

汎型クラスのインスタンス化

実際の型を尖括号で指定して汎型クラスのインスタンスを作成することができます。以下に汎型クラスDataStoreのインスタンスを作成します。

DataStore<string> store = new DataStore<string>();

上図では、インスタンスの作成時に尖括号で指定されたstringが使用され、Tはコンパイル時にクラス全体で使用されるstringのあらゆる型に置き換えられます。したがって、Data属性の型はstringです。

以下は汎型の動作を説明する図です。

Data属性に文字列値を割り当てることができます。文字列以外の値を割り当てるとコンパイル時エラーが発生します。

DataStore<string> store = new DataStore<string>();
store.Data = "Hello World!";//obj.Data = 123; //コンパイル時エラー

異なるオブジェクトに対して異なるデータ型を指定することができます。以下のようになります。

DataStore<string> strStore = new DataStore<string>();
strStore.Data = "Hello World!";
//strStore.Data = 123; // コンパイル時エラー
DataStore<int> intStore = new DataStore<int>();
intStore.Data = 100;
//intStore.Data = "Hello World!"; // コンパイル時エラー
KeyValuePair<int, string> kvp1 = new KeyValuePair<int, string>();
kvp1.Key = 100;
kvp1.Value = "Hundred";
KeyValuePair<string, string> kvp2 = new KeyValuePair<string, string>();
kvp2.Key = "IT";
kvp2.Value = "Information Technology";

汎型クラスの特徴

  • 汎型クラスは再利用性を高めます。型が多いほど再利用性が高くなりますが、過度な汎化はコードの理解や保守が難しくなります。

  • 汎型クラスは他の汎型または非汎型クラス、抽象クラスの基底クラスになれます。

  • 汎型クラスは他の汎型または非汎型インターフェース、クラス、抽象クラスから派生できます。

汎型フィールド

汎型クラスは汎型フィールドを含むことができますが、初期化はできません。

クラス データストア<T>
{
    public T data;
}

以下に汎型配列を宣言します。

クラス データストア<T>
{
    public T[] data = new T[10];
}

汎用メソッド

型引数を使用して返り値またはパラメータの型を宣言するメソッドを汎用メソッドと呼びます。

クラス データストア<T>
{
    プライベート T[] _データ = 新しい T[10];
    
    パブリック void AddOrUpdate(int インデックス、T アイテム)
    {
        if(インデックス >= 0 && インデックス < 10)
            _データ[インデックス] = アイテム;
    }
    パブリック T GetData(int インデックス)
    {
        if(インデックス >= 0 && インデックス < 10)
            return _データ[インデックス];
        else 
            デフォルト(T)を返します;
    }
}

上記の AddorUpdate()と GetData()メソッドは汎用メソッドです。itemパラメータの実際のデータ型は、DataStore<T>クラスをインスタンス化したときに指定されます。以下のようになります。

データストア<string> cities = 新しい データストア<string>();
cities.AddOrUpdate(0、 "ムンバイ");
cities.AddOrUpdate(1、 "シカゴ");
cities.AddOrUpdate(2、 "ロンドン");
データストア<int> empIds = 新しい データストア<int>();
empIds.AddOrUpdate(0、 50);
empIds.AddOrUpdate(1, 65);
empIds.AddOrUpdate(2, 89);

汎用パラメータの型は、非汎用パラメータとまたは非汎用パラメータと返り値を持つ複数のパラメータと一緒に使用できます。以下は有効な汎用メソッドオーバーロードです。

パブリック void AddOrUpdate(int インデックス、T データ){ }
パブリック void AddOrUpdate(T データ1、 T データ2) { }
パブリック void AddOrUpdate<U>(T データ1、 U データ2) { }
パブリック void AddOrUpdate(T データ){ }

尖括号内でメソッド名を使用して型引数を指定することで、非汎用クラスに汎用メソッドを含むことができます。以下のようになります。

クラス プリンタ
{
    パブリック void Print<T>(T データ)
    {
        コンソール.ライトライン(データ);
    }
}
プリンタ プリンタ = 新しい プリンタ();
printer.Print<int>(100);
printer.Print(200); // 指定された値に基づいて推測
printer.Print<string>("Hello");
printer.Print("World!"); // 指定された値に基づいて推測

ジェネリクスの利点

  1. ジェネリクスはコードの再利用性を高めます。異なるデータタイプを処理するためにコードを書く必要はありません。

  2. ジェネリクスは型安全です。定義で指定されたデータタイプとは異なるデータタイプを使用しようとすると、コンパイル時エラーが発生します。

  3. ジェネリクスはパッキングとアンパッキングの可能性を排除することでパフォーマンス上の利点を持っています。