[C#] 32. ジェネリックタイプ(Generic Type)を使い方
こんにちは。明月です。
この投稿はC#のジェネリックタイプ(Generic Type)を使い方に関する説明です。
我々がリスト(List)やディクショナリ(Dictionary)を使う時、どのデータタイプをリスト中で使うのかを括弧で設定します。
using System;
using System.Collections.Generic;
namespace Example
{
class Program
{
// 実行関数
static void Main(string[] args)
{
// intタイプを扱うListのインスタンスを生成
var list = new List<int>();
// listにデータを入力
list.Add(1);
list.Add(2);
// listの値を順番とおりに出力
foreach (int node in list)
{
// コンソール出力
Console.WriteLine(node);
}
// 任意のキーを押してください
Console.WriteLine("Press any key...");
Console.ReadLine();
}
}
}
もし、設定したデータを使わなかったらエラーが発生します。
このようにどのデータタイプを使うのかを設定することがジェネリック(Generic)です。
つまり、クラスやメソッド中ではデータタイプを設定せず、インスタンスを生成する位置でデータタイプを設定して使う方法という意味です。
using System;
using System.Collections;
namespace Example
{
// 連結リストアルゴリズム
class List
{
// リストから使うNodeクラス
private class Node
{
// データ
public int Data { get; set; }
// リストの次のポインタ
public Node Next { get; set; }
}
// foreachを使うためにIEnumeratorインタフェースを継承して関数を再宣言
private class Pointer : IEnumerator
{
// Listインスタンスを受け取ってポインタ管理
private List list;
// 現ポインタ
private Node pointer;
// コンストラクタ
public Pointer(List list)
{
// インスタンスをメンバー変数から管理
this.list = list;
// ポインタを最初に移動
this.pointer = list.start;
}
// 現在値を出力
public object Current { get; set; }
// Currentポインタの値があるか確認する関数
public bool MoveNext()
{
// 無かったらfalseをリターンしてforeachを停止
if (this.pointer == null)
{
return false;
}
// 現在値をCurrentに格納
Current = this.pointer.Data;
// ポインタを移動する。
this.pointer = this.pointer.Next;
// 現ポインタ値はあるのでtrue
return true;
}
// ポインタリセット
public void Reset()
{
this.pointer = this.list.start;
}
}
// リストのポインタの頭
private Node start = null;
// リストのポインタの後
private Node end = null;
// foreachで使うpointer管理クラス
private Pointer pointer;
// コンストラクタ
public List()
{
// pointer管理クラスのインスタンスを生成
pointer = new Pointer(this);
}
// データ追加する。
public void Add(int val)
{
// リストのポインタの頭が無かったら
if (start == null)
{
// リストのポインタの頭にインスタンス生成
start = new Node()
{
Data = val
};
// リストのポインタの後とリストのポインタの頭を一致する。
end = start;
}
else
{
// リストのポインタの後に続けてNodeを連結
end.Next = new Node()
{
Data = val
};
}
}
// foreachから使うIEnumeratorタイプをリターンする。
public IEnumerator GetEnumerator()
{
// ポインタリセット
pointer.Reset();
// ポインタリターン
return pointer;
}
}
// 実行関数クラス
class Program
{
// 実行関数
static void Main(string[] args)
{
// 任意で作ったリストのインスタンスを生成
var list = new List();
// intタイプのデータを入力
list.Add(1);
list.Add(2);
// listの値を順番とおりに出力
foreach (var item in list)
{
// コンソール出力
Console.WriteLine(item);
}
// 任意のキーを押してください
Console.WriteLine("Press any key...");
Console.ReadLine();
}
}
}
上の例は簡単な連結リストのアルゴリズムです。
実は連結リストのアルゴリズムは凄く簡単なアルゴリズムですが、foreachでデータを出力するためにインラインクラスのPointerを作りました。その理由で少し複雑になりました。
上の例のListクラスはintタイプだけ使えます。Add関数のパラメータとNodeクラスのDataのタイプをintタイプに設定したのでintだけ使えます。
しかし、状況によりintタイプではなくStringタイプのリストも作りたいです。
現在の状況では上のクラスでAdd関数とNodeクラスのデータタイプだけ変更してコピペするべきですね。そしてC#.Net frameworkで提供するクラスや原始データではなければ、使いたい時たびに作成しなければならないです。
でも、我々は実際のリスト(List)クラスを使う時にはそのように使いません。括弧(<>)を使ってデータタイプを設定して使います。
using System;
using System.Collections;
namespace Example
{
// 連結リストアルゴリズム(データタイプをジェネリックで設定する。)
class List <T>
{
// リストから使うNodeクラス
private class Node
{
// データ - データタイプを決めなく、Listのジェネリックで設定したデータタイプで設定
public T Data { get; set; }
// リストの次のポインタ
public Node Next { get; set; }
}
// foreachを使うためにIEnumeratorインタフェースを継承して関数を再宣言
private class Pointer : IEnumerator
{
// Listインスタンスを受け取ってポインタ管理
private List<T> list;
// 現ポインタ
private Node pointer;
// コンストラクタ
public Pointer(List<T> list)
{
// インスタンスをメンバー変数から管理
this.list = list;
// ポインタを最初に移動
this.pointer = list.start;
}
// 現在値を出力
public object Current { get; set; }
// Currentポインタの値があるか確認する関数
public bool MoveNext()
{
// 無かったらfalseをリターンしてforeachを停止
if (this.pointer == null)
{
return false;
}
// 現在値をCurrentに格納
Current = this.pointer.Data;
// ポインタを移動する。
this.pointer = this.pointer.Next;
// 現ポインタ値はあるのでtrue
return true;
}
// ポインタリセット
public void Reset()
{
this.pointer = this.list.start;
}
}
// リストのポインタの頭
private Node start = null;
// リストのポインタの後
private Node end = null;
// foreachで使うpointer管理クラス
private Pointer pointer;
// コンストラクタ
public List()
{
// pointer管理クラスのインスタンスを生成
pointer = new Pointer(this);
}
/// データ追加する。(パラメータタイプをジェネリックタイプに設定する。)
public void Add(T val)
{
// リストのポインタの頭が無かったら
if (start == null)
{
// リストのポインタの頭にインスタンス生成
start = new Node()
{
Data = val
};
// リストのポインタの後とリストのポインタの頭を一致する。
end = start;
}
else
{
// リストのポインタの後に続けてNodeを連結
end.Next = new Node()
{
Data = val
};
}
}
// foreachから使うIEnumeratorタイプをリターンする。
public IEnumerator GetEnumerator()
{
// ポインタリセット
pointer.Reset();
// ポインタリターン
return pointer;
}
}
// 実行関数クラス
class Program
{
// 実行関数
static void Main(string[] args)
{
// 任意で作ったリストのインスタンスを生成(List中で使うデータタイプはstringで設定)
var list = new List<string>();
// stringタイプのデータを入力
list.Add("Node 1");
list.Add("Node 2");
// listの値を順番とおりに出力
foreach (var item in list)
{
// コンソール出力
Console.WriteLine(item);
}
// 任意のキーを押してください
Console.WriteLine("Press any key...");
Console.ReadLine();
}
}
}
ジェネリックはデータタイプが設定されてないのでTという任意の文字で置換します。
つまり、インスタンスを生成する場所でintやStringで設定すればTという文字が全部intやStringタイプで変更すると思えば良いです。
そうことでデータタイプによりリストコードを作成する必要せずに、ジェネリックを使えばどのタイプでも対応ができるという意味です。
ジェネリックは基本的にクラスで宣言する方法があるし、メソッドだけ使う方法もあります。
using System;
using System.Collections.Generic;
namespace Example
{
class Program
{
// リストを配列に変更する関数
static T[] ConvertToArrayFromList<T>(List<T> list)
{
// 配列のインデクス
int i = 0;
// ジェネリックタイプを利用して配列を生成
T[] ret = new T[list.Count];
// Listを繰り返しを通って抽出
foreach(var item in list)
{
// 配列に値を格納
ret[i++] = item;
}
return ret;
}
// 実行関数
static void Main(string[] args)
{
// stringタイプを扱うListのインスタンスを生成
var list = new List<string>();
// データを追加
list.Add("Node 1");
list.Add("Node 2");
// Listを配列に変換
var array = ConvertToArrayFromList<string>(list);
// 配列の2つ目を出力する。
Console.WriteLine(array[1]);
// 任意のキーを押してください
Console.WriteLine("Press any key...");
Console.ReadLine();
}
}
}
メソッドジェネリックは関数名の隣に設定します。
上の例では関数を呼び出す時に括弧(<>)を使いますが、実際にはパラメータにstring値を入れたら自動にジェネリックが設定されます。
なので関数ジェネリックを使う時は呼び出すときにジェネリックを設定しなくてもプログラムでエラーが発生しません。
ここまでC#のジェネリックタイプ(Generic Type)を使い方に関する説明でした。
ご不明なところや間違いところがあればコメントしてください。
- [C#] 39. lockキーワードとdeadlock(デッドロック)2019/07/24 00:57:35
- [C#] 38. ThreadPoolの使い方2019/07/23 00:05:40
- [C#] 37. スレッド(Thread)を使い方、Thread.Sleep関数を使い方2019/07/22 23:45:05
- [C#] 36. 拡張メソッドを使い方2019/07/22 23:30:17
- [C#] 35. 文字列クラス、StringとStringBuilderを使い方2019/07/22 23:15:42
- [C#] 34. 最上位クラス(Object クラス)2019/07/20 02:27:23
- [C#] 33. 匿名形式(Anonymous Types)を使い方2019/07/20 02:22:03
- [C#] 32. ジェネリックタイプ(Generic Type)を使い方2019/07/18 22:50:16
- [C#] 31. アトリビュート(Attribute)を使い方2019/07/18 20:22:16
- [C#] 30. Linq関数式を使う方法2019/07/17 23:06:42
- [C#] 29. Linqクエリ式を使い方2019/07/17 20:57:00
- [C#] 28. リスト(List)とディクショナリ(Dictionary)、そしてLinq式を使い方2019/07/16 22:40:03
- [C#] 27. varキーワードとdynamicキーワード2019/07/16 20:41:27
- [C#] 26. 例外処理(try ~ catch)する方法2019/07/16 00:59:34
- [C#] 25. イベント(event)キーワードを使い方2019/07/16 00:48:03
- check2024/04/10 19:03:53
- [Java] 64.Spring bootとReactを連結する方法(Buildする方法)2022/03/25 21:02:18
- [Javascript] Node.jsをインストールしてReactを使う方法2022/03/23 18:01:34
- [Java] 63. Spring bootでcronスケジューラとComponentアノテーション2022/03/16 18:57:30
- [Java] 62. Spring bootでWeb-Filterを設定する方法(Spring Security)2022/03/15 22:16:37
- [Java] JWT(Json Web Token)を発行、確認する方法2022/03/14 19:12:58
- [Java] 61. Spring bootでRedisデータベースを利用してセッションクラスタリング設定する方法2022/03/01 18:20:52
- [Java] 60. Spring bootでApacheの連結とロードバランシングを設定する方法2022/02/28 18:45:48
- [Java] 59. Spring bootのJPAでEntityManagerを使い方2022/02/25 18:27:48
- [Java] 58. EclipseでSpring bootのJPAを設定する方法2022/02/23 18:11:10
- [Java] 57. EclipseでSpring bootを設定する方法2022/02/22 19:04:49
- [Python] Redisデータベースに接続して使い方2022/02/21 18:23:49
- [Java] Redisデータベースを接続して使い方(Jedisライブラリ)2022/02/16 18:13:17
- [C#] Redisのデータベースを接続して使い方2022/02/15 18:46:09
- [CentOS] Redisデータベースをインストールする方法とコマンドを使い方2022/02/14 18:33:07