テキスト篇:p.363「ジェネリックとは」から
ゲーム開発演習:背景画面のスクロール、キーボードの状態を知る など
p.363 ジェネリックとは
・ジェネリックとは「総称」という意味で「型を変数で扱えば、型だけが異なるクラスやメソッドを1つにできる」仕組み ・つまり、メソッドのオーバーロードや引数の可変個化(p.201)などをさらに進化させた位置づけ
p.363 ジェネリッククラス
・利用時に型を指定できるクラス
・定義書式: class クラス名<型パラメータ> {…}
・通常、型パラメータには大文字1文字を用いることが多い(C#が提供するジェネリッククラスがそうなので)
・例: class Monster<T> { public T power; } //型パラメータで指定した型のデータメンバを持つクラス
・ジェネリッククラスを通常のクラスから利用するには、宣言時と生成時に型を指定する
・宣言書式: クラス名<型> 参照変数;
・生成書式: 参照変数 = new クラス名<型>(…);
・宣言と生成を同時に行う場合、型を2箇所指定できる: クラス名<型> 参照変数 = new クラス名<型>(…);
・例: Monster<int> rimuru = new Monster<int>(); //①powerを整数で扱うモンスターrimuruの初期化
・例: Monster<double> gobuta = new Monster<double>(); //②powerを実数で扱うモンスターgobutaの初期化
・例: Monster<string> shuna = new Monster<string>(); //③powerを文字列で扱うモンスターshunaの初期化
・つまり、内部的に型パラメータの置き換えが行われ、その結果のオブジェクトが生成される
・例: class Monster<T> { public T power; } ⇒ class Monster { public int power; } //①の場合
p.364 generic01.cs
//p.364 generic01.cs
using System;
class MyClass<T> { //型パラメータTを持つジェネリッククラス
public T name; //宣言時に型が決まるデータメンバ
public T GetVal() { //宣言時戻り値が決まるメソッド(アクセッサ)
return name; //nameの型が戻り値型に一致するのでどんな型でもOK
}
}
class generic01 {
public static void Main() {
MyClass<int> mca = new MyClass<int>(); //型にintを指定してインスタンスを生成
mca.name = 10; //インスタンス変数nameの型はintなので整数を代入できる
Console.WriteLine(mca.GetVal()); //GetValメソッドの戻り値型はintなので整数が返る
MyClass<string> mcb = new MyClass<string>(); //型にstringを指定してインスタンスを生成
mcb.name = "猫"; //インスタンス変数nameの型はstringなので文字列を代入できる
Console.WriteLine(mcb.GetVal()); //GetValメソッドの戻り値型はstringなので文字列が返る
}
}
アレンジ演習:p.364 generic01.cs
・mca.GetVal()は戻り値型がintなので、これに+10すると加算になる ・mcb.GetVal()は戻り値型がstringなので、これに+10すると連結になる ・以上を追記して確認しよう
作成例
//アレンジ演習:p.364 generic01.cs
using System;
class MyClass<T> { //型パラメータTを持つジェネリッククラス
public T name; //宣言時に型が決まるデータメンバ
public T GetVal() { //宣言時戻り値が決まるメソッド(アクセッサ)
return name; //nameの型が戻り値型に一致するのでどんな型でもOK
}
}
class generic01 {
public static void Main() {
MyClass<int> mca = new MyClass<int>(); //型にintを指定してインスタンスを生成
mca.name = 10; //インスタンス変数nameの型はintなので整数を代入できる
Console.WriteLine(mca.GetVal() + 10); //GetValメソッドの戻り値型はintなので整数が返り加算
MyClass<string> mcb = new MyClass<string>(); //型にstringを指定してインスタンスを生成
mcb.name = "猫"; //インスタンス変数nameの型はstringなので文字列を代入できる
Console.WriteLine(mcb.GetVal() + 10); //GetValメソッドの戻り値型はstringなので文字列が返り連結
}
}
p.365(複数の型パラメータを持つジェネリッククラス)
・ジェネリッククラスの型パラメータ数には制限はない
・2つ以上を用いる場合は、カンマで区切る
・定義書式例: class クラス名<型パラメータ①, 型パラメータ②> {…}
・例: class Monster<T, U> { public T hp; public U mp; } //型パラメータで指定した型のデータメンバを持つクラス
・複数の型パラメータを持つジェネリッククラスを通常のクラスから利用するには、宣言時と生成時に全ての型パラメータに型を指定する
・宣言書式: クラス名<型①,型②> 参照変数;
・生成書式: 参照変数 = new クラス名<型①,型②>(…);
・宣言と生成を同時に行う場合: クラス名<型①,型②> 参照変数 = new クラス名<型①,型②>(…);
・例: Monster<int, double> rimuru = new Monster<int, double>(); //hpを整数、mpを実数で扱うモンスターrimuruの初期化
p.365(配列の型パラメータを持つジェネリッククラス)
・データメンバとして配列を持ち、その型をジェネリックで指定することも可能 ・宣言書式: 型パラメータ[] 配列名; ・生成書式: 配列名 = new 型パラメータ[要素数]; ・型が宣言時に決まらないので、初期化はできないが、要素数指定の宣言+生成は可能 例: public T[] a = new T[10];
p.365 generic02.cs
//p.365 generic02.cs
using System;
class MyClass<T, U> { //2個の型パラメータT,Uを持つジェネリッククラス
public T[] x; //T型の配列xの宣言のみ
public U[] y; //U型の配列yの宣言のみ
public MyClass(int n) { //コンストラクタ(引数は要素数)
x = new T[n]; //要素数nでT型の配列xの要素を生成
y = new U[n]; //要素数nでU型の配列yの要素を生成
}
}
class generic02 {
public static void Main() {
int n; //配列の要素数
Console.Write("n = ");
string strN = Console.ReadLine();
if (!Char.IsDigit(strN[0])) { //先頭文字が数字ではない?
Console.WriteLine("入力が不適切です");
return; //終了
}
n = int.Parse(strN); //整数化
//2引数を指定してインスタンス生成、コンストラクタが動作し2配列の要素を各n個生成
MyClass<int, string> mc = new MyClass<int, string>(n);
for (int i = 0; i < n; i++) { //全要素について繰返す
Console.Write("番号--- ");
string strNo = Console.ReadLine();
if (!Char.IsDigit(strNo[0])) { //先頭文字が数字ではない?
Console.WriteLine("不適切な番号です");
break; //終了
}
mc.x[i] = int.Parse(strNo); //整数化して配列xの要素[i]に代入
Console.Write("氏名--- ");
string strName = Console.ReadLine();
mc.y[i] = strName; //そのまま配列yの要素[i]に代入
}
Console.WriteLine(); //改行
for (int i = 0; i < n; i++) { //全要素について繰返す
Console.WriteLine("[{0}] {1}", mc.x[i], mc.y[i]);
}
}
}
【補足】ジェネリックプロパティ
・プロパティの戻り値型を型パラメータにできる
・定義書式: public 型パラメータ プロパティ名 { get {…} set {…} }
・例: public T HP { get { return hp; } set { hp = value; } }
・valueの内部的な型は、自動的に型パラメータと同じ型になる
アレンジ演習:p.364 generic01.cs
・GatValメソッドをプロパティ(getのみ)にしよう
作成例
//アレンジ演習:p.364 generic01.cs
using System;
class MyClass<T> { //型パラメータTを持つジェネリッククラス
public T name; //宣言時に型が決まるデータメンバ
public T GetVal { //宣言時戻り値が決まるプロパティ
get { return name; } //nameの型が戻り値型に一致するのでどんな型でもOK
}
}
class generic01 {
public static void Main() {
MyClass<int> mca = new MyClass<int>(); //型にintを指定してインスタンスを生成
mca.name = 10; //インスタンス変数nameの型はintなので整数を代入できる
Console.WriteLine(mca.GetVal); //GetValプロパティの戻り値型はintなので整数が返る
MyClass<string> mcb = new MyClass<string>(); //型にstringを指定してインスタンスを生成
mcb.name = "猫"; //インスタンス変数nameの型はstringなので文字列を代入できる
Console.WriteLine(mcb.GetVal); //GetValプロパティの戻り値型はstringなので文字列が返る
}
}
【補足】ジェネリックインデクサ
・インデクサの型を型パラメータにできる
・定義書式: public 型パラメータ this[型 インデックス] { get {…} set {…} }
・例: public T this[int i] { get { return a[i]; } set { a[i] = value; } }
アレンジ演習:p.365 generic02.cs
・配列xをジェネリックインデクサで扱うようにしよう
作成例
//アレンジ演習:p.365 generic02.cs
using System;
class MyClass<T, U> { //2個の型パラメータT,Uを持つジェネリッククラス
public T[] x; //T型の配列xの宣言のみ
public U[] y; //U型の配列yの宣言のみ
public MyClass(int n) { //コンストラクタ(引数は要素数)
x = new T[n]; //要素数nでT型の配列xの要素を生成
y = new U[n]; //要素数nでU型の配列yの要素を生成
}
public T this[int i] { //【以下追加】T型の配列xによるインデクサ
get { return x[i]; } set { x[i] = value; }
}
}
class generic02 {
public static void Main() {
int n; //配列の要素数
Console.Write("n = ");
string strN = Console.ReadLine();
if (!Char.IsDigit(strN[0])) { //先頭文字が数字ではない?
Console.WriteLine("入力が不適切です");
return; //終了
}
n = int.Parse(strN); //整数化
//2引数を指定してインスタンス生成、コンストラクタが動作し2配列の要素を各n個生成
MyClass<int, string> mc = new MyClass<int, string>(n);
for (int i = 0; i < n; i++) { //全要素について繰返す
Console.Write("番号--- ");
string strNo = Console.ReadLine();
if (!Char.IsDigit(strNo[0])) { //先頭文字が数字ではない?
Console.WriteLine("不適切な番号です");
break; //終了
}
mc[i] = int.Parse(strNo); //【変更】整数化して配列xの要素[i]にインデクサ経由で代入
Console.Write("氏名--- ");
string strName = Console.ReadLine();
mc.y[i] = strName; //そのまま配列yの要素[i]に代入
}
Console.WriteLine(); //改行
for (int i = 0; i < n; i++) { //全要素について繰返す
Console.WriteLine("[{0}] {1}", mc[i], mc.y[i]); //【変更】配列xはインデクサ経由で利用
}
}
}
p.367(ジェネリックとコレクションクラス)
・ArrayListクラスのようにデータ構造を提供するクラスの中には、ジェネリッククラスもあり、その代表例がList<T>クラス ・List<T>クラスはデータ構造に格納したい要素の型を型パラメータで与えて用いる ・すると、配列と同様に使えて配列より柔軟なデータ構造を型指定で利用できる ・ArrayListクラスでは格納すると型情報が失われ、取り出し時にキャストで与える必要があるが、 List<T>クラスでは型情報を与えられるのでその必要はなく、ミスを防止できる ・List<T>クラスにも、要素を格納するAddメソッド、要素数を返すCountプロパティ、整列するSortメソッドなどがある ・using System.Collections.Generic; を指定すると良い
p.368 list01.cs
// p.368 list01.cs
using System;
using System.Collections.Generic; //List用
class list01 {
public static void Main() {
bool bEnd = false; //終了フラグをオフに
List<int> mylist = new List<int>(); //int型を指定してジェネリックコレクションクラスを利用
while (true) { //無限ループ
Console.Write("Data = ");
string strData = Console.ReadLine();
if (!Char.IsDigit(strData[0])) { //先頭文字が数字ではない?
bEnd = true; //終了フラグをオンに
} else {
mylist.Add(int.Parse(strData)); //整数化してリストに格納(いくつでもOK)
}
if (bEnd) { //終了フラグ:オン?
break; //繰返し終了
}
}
Console.WriteLine(); //改行
for (int i = 0; i < mylist.Count; i++) { //全要素について繰返す
Console.WriteLine("[{0}] {1}", i, mylist[i]);
}
Console.WriteLine();
mylist.Sort(); //昇順に整列
for (int i = 0; i < mylist.Count; i++) { //全要素について繰返す
Console.WriteLine("[{0}] {1}", i, mylist[i]);
}
}
}