コレクション【C#】

C#文法:コレクション:B04_型推論による生成

前回までのレクチャーでは、int[] のように型を明示して配列を生成する方法を見てきました。今回は、右辺に並べた値からコンパイラーに要素の型を判断してもらう「型推論による生成」を扱います。書き方そのものは短いのですが、「どういうときに推論できて、どういうときにできないのか」を押さえておくことが大切です。

図1 今回のテーマは「型推論による生成」

図1 今回のテーマは「型推論による生成」

テスト用のボタンを追加する

これまでと同じように、フォームに新しいボタンを1つ追加します。ボタンをダブルクリックすると、クリックイベントのハンドラー button3_Click が自動生成されるので、この中にコードを書いていきます。

図2 フォームにボタンを追加してダブルクリックする

図2 フォームにボタンを追加してダブルクリックする

最初に、今回のポイントをコメントで書いておきます。型推論による生成は「型が同じ場合はできる」というのが結論です。

リスト1 button3_Clickにコメントを書く

private void button3_Click(object sender, EventArgs e)
{
    //型推論による生成
    //型が同じ場合はできる
}

これまでの生成方法を振り返る

前回の button2_Click では、生成と同時に値を設定する書き方をいくつか見ました。どの書き方でも、左辺か右辺のどこかに int[] と書いて「intの配列ですよ」ということを宣言していました。

図3 前回のレクチャーで書いた生成のパターン

図3 前回のレクチャーで書いた生成のパターン

リスト2 前回までの書き方(button2_Click)

//生成と同時に値を設定する
int[] haiA = new int[10];
int[] haiB = new int[] { 11, 22, 33, 44 };
var haiC = new int[] { 11, 22, 33, 44 };
int[] haiD = { 11, 22, 33, 44 };
 
//違う型はNG
//int[] haiE = { 11, 22, 33, "" };

haiC は左辺を var にしていますが、右辺には new int[] と書いてあります。つまり、型の情報は右辺が持っている状態です。

new[] で型を書かずに生成する

今回の書き方では、右辺からも int を取り除きます。new のあとに角括弧 [] だけを書き、中括弧の中に値を並べます。

リスト3 型推論による配列の生成

private void button3_Click(object sender, EventArgs e)
{
    //型推論による生成
    //型が同じ場合はできる
    var haiE = new[] { 10, 22, 99 };
}
図4 new[] のあとに中括弧で値を並べる

図4 new[] のあとに中括弧で値を並べる

左辺は var、右辺は new[] なので、コード上には int という文字がどこにも出てきません。それでもコンパイルエラーにはならず、これで配列を宣言できます。

実行して中身を確認する

メソッドの終わりにブレークポイントを置いて実行し、追加したボタンをクリックします。止まったところで haiE にマウスカーソルを合わせると、データヒントに {int[3]} と表示されます。

図5 haiEは int[3] として生成され、10・22・99 が入っている

図5 haiEは int[3] として生成され、10・22・99 が入っている

型を書いていないにもかかわらず、haiE は要素数3の int 配列になっています。インデックス0に10、1に22、2に99と、値も書いた順に入っています。

なぜ型を書かなくてもよいのか

ポイントは右辺の中括弧の中身です。10、22、99 はどれも int です。並んでいる値が int、int、int と全部同じ型になっているので、わざわざ「intの配列」と宣言しなくても、コンパイラーが型推論で「これは int[] だ」と理解してくれます。

図6 前回の haiC から int を消した形が今回の書き方

図6 前回の haiC から int を消した形が今回の書き方

前回の haiC と見比べると違いがよくわかります。new int[] の int を消しただけ、というのが今回の haiE です。

リスト4 haiCとhaiEの比較

//右辺で型を明示している
var haiC = new int[] { 11, 22, 33, 44 };
 
//右辺の int を省略(要素から int[] と推論される)
var haiE = new[] { 10, 22, 99 };

クラスの配列でも同じように書ける

型推論は int のような値型に限った話ではありません。たとえば TextBox を2つ並べた場合も、要素がすべて TextBox なので TextBox[] と推論されます。

リスト5 TextBoxだけを並べた場合はOK

var haiE = new[] { 10, 22, 99 };
var textboxs = new[] { new TextBox(), new TextBox() };
図7 TextBoxだけを並べた配列は問題なく宣言できる

図7 TextBoxだけを並べた配列は問題なく宣言できる

違う型が混ざるとNG

一方で、型が違うものが混ざっていると型推論はできません。TextBox と ComboBox を並べてみると、赤い波線が出てコンパイルエラーになります。

リスト6 TextBoxとComboBoxを混ぜるとコンパイルエラー

var textboxs = new[] { new TextBox(), new TextBox() };
 
//NG:要素の型がそろっていないので推論できない
var textboxsNg = new[] { new TextBox(), new ComboBox() };
図8 TextBoxとComboBoxを混ぜるとエラーになる

図8 TextBoxとComboBoxを混ぜるとエラーになる

TextBox と ComboBox はどちらも Control を継承しているので、人間から見れば「Controlの配列にしてくれればいいのに」と思うかもしれません。しかしコンパイラーは、並んでいる要素の型の中から配列の型を選ぼうとします。TextBox は ComboBox ではなく、ComboBox も TextBox ではないため、どちらにも決められずエラーになるわけです。

補足:このときのエラーは CS0826(暗黙的に型指定された配列の最適な型が見つかりません)です。また、厳密には「完全に同じ型」でなくても、要素のどれか1つの型にほかの要素がすべて暗黙的に変換できれば推論は成立します。たとえば new[] { 1, 2.5 } は double[] になります。

ベースの型を指定すれば混ぜられる

サブクラスが混ざった配列を作りたい場合は、new のあとにベースになる型を配列として指定します。TextBox と ComboBox の共通の基底クラスは Control なので、new Control[] と書きます。

リスト7 ベースの型を指定してサブクラスを混在させる

//var textboxsNg = new[] { new TextBox(), new ComboBox() };
var textboxsOk =
    new Control[] { new TextBox(), new ComboBox() };
図9 new Control[] と指定すれば TextBox と ComboBox を混在できる

図9 new Control[] と指定すれば TextBox と ComboBox を混在できる

こうしておけば、中括弧の中に Control のサブクラスがいくつ混ざっていても問題ありません。textboxsOk の型は Control[] になります。

ただし、これは型推論というよりは「型の指定のやり方」の話です。右辺で Control[] と明示しているので、推論しているのは左辺の var だけ、という点は前回の haiC と同じです。サブクラスが混ざる場合は型推論に任せず、ベースになる型を自分で指定する、と覚えておいてください。

まとめ

今回書いたコードの全体は次のとおりです。

リスト8 button3_Clickの完成形

private void button3_Click(object sender, EventArgs e)
{
    //型推論による生成
    //型が同じ場合はできる
    var haiE = new[] { 10, 22, 99 };
    var textboxs = new[] { new TextBox(), new TextBox() };
    //var textboxsNg = new[] { new TextBox(), new ComboBox() };
    var textboxsOk =
        new Control[] { new TextBox(), new ComboBox() };
}

new[] { … } と書くと、中括弧の中の要素から配列の型が推論されます。要素の型がそろっていれば int でもクラスでも使えますが、TextBox と ComboBox のように違う型が混ざると推論できずエラーになります。その場合は new Control[] { … } のように、ベースになる型を明示して宣言します。ぜひ実際に書いて試してみてください。

C#文法:コレクション

■非公開コース「C#14新機能」プレゼント:
非公開コース「C#14新機能」(80分)をご覧になりたい方は
こちらからURLとパスワードを発行していますので、ご覧になってみてください。
非公開コース「C#14新機能」を観る

A01_はじめに
A02_プロジェクトの作成
B01_配列とは
B02_配列の生成とアクセス
B03_生成と同時に値を設定する
B04_型推論による生成
B05_メソッドの引数などにする場合の注意点
B06_Length
B07_IndexOfでの検索
B08_FindIndexでの検索
B09_Find
B10_Exists
B11_FindAllとFindLast
B12_誤ったコピー
B13_Array.Copy
B14_範囲指定のコピー
B15_Resize
C01_ArrayList
C02_List
C03_List 動的な要素の変更
C04_Listのコンストラクタ
C05_Listのコンストラクタ_Capacity
C06_ListTからArrayクラスのメソッドが呼ばれている
D01_コレクションインタフェースとは
D02_異なるコレクションクラスに互換性を持たせる
D03_インタフェースの階層構造
D04_クライアントができる操作を制限させる
D05_クライアントができる操作を制限させる_後半
D06_Enumerableの拡張メソッドに関して
D07_ReadOnly系のコレクションインタフェース
D08_AsReadOnly
D09_ToListでコピーする
D10_ListTはprivateで使う

■非公開コース「C#14新機能」プレゼント:
非公開コース「C#14新機能」(80分)をご覧になりたい方は
こちらからURLとパスワードを発行していますので、ご覧になってみてください。
非公開コース「C#14新機能」を観る