「コレクションを作成して、それにいちいち要素を追加して」というのは面倒だ。そうではなく、コレクションの作成と同時にその要素を追加する方法を解説する。
対象:C# 3.0(Visual Studio 2008)/VB 10(Visual Studio 2010)以降
コレクションをnewするとき、同時に要素も追加できる(正確には、「一文でコーディングできる」)。本稿では、その方法を解説するとともに、それとよく似た配列初期化子やインデックス初期化子についても紹介する。
なお、掲載したサンプルコードの全てを試すにはVisual Studio 2015以降が必要である。
コレクション初期化子を使えばよい(次のコード)。
List<string> list = new List<string>{"aaa", "bbb", "ccc", };
Dim list As List(Of String) = New List(Of String) From {"aaa", "bbb", "ccc"}
コレクション初期化子には、追加するオブジェクトを「,」(カンマ)で区切って並べる。
分かりやすいのは上に示したような文字列を追加するケースだろう。実際にコンソールアプリを書いて試してみよう。コレクションの要素をコンソールに表示する「PrintCollection」メソッドを作り、それを使って想定通りの要素がコレクションに追加されたことを確認する(次のコード)。
以降では、このコードをベースに解説していく。
using System.Collections;
using System.Collections.Generic;
using static System.Console;
class Program
{
static void PrintCollection(IEnumerable col, string msg)
{
WriteLine(msg);
int index = 0;
foreach (var o in col)
WriteLine($"[{index++}] {o?.ToString() ?? "(null)"}");
}
static void Main(string[] args)
{
List<string> list = new List<string> { "aaa", "bbb", null, };
PrintCollection(list, "文字列のコレクション");
// 出力:
// 文字列のコレクション
// [0] aaa
// [1] bbb
// [2] (null)
#if DEBUG
ReadKey();
#endif
}
}
Imports System.Console
Module Module1
Sub PrintCollection(col As IEnumerable, msg As String)
WriteLine(msg)
Dim index As Integer = 0
For Each o In col
WriteLine($"[{index}] {If(o, "(Nothing)")}")
index += 1
Next
End Sub
Sub Main()
Dim list As List(Of String) _
= New List(Of String) From {"aaa", "bbb", Nothing}
PrintCollection(list, "文字列のコレクション")
' 出力:
' 文字列のコレクション
' [0] aaa
' [1] bbb
' [2] (Nothing)
#If DEBUG Then
ReadKey()
#End If
End Sub
End Module
配列に関しては、コレクション初期化子と似た機能として、それ以前から配列初期化子がある(次のコード)。C#では特に意識する必要はないだろうが、VBではコレクション初期化子と書式が違うので注意が必要だ。
int[] array = new int[] { 1, 2, int.MaxValue, };
// 「new int[]」でサイズ指定(「new int[3]」)は不要(指定しても構わない)
PrintCollection(array, "整数の配列");
// 出力:
// 整数の配列
// [0] 1
// [1] 2
// [2] 2147483647
Dim array() As Integer = New Integer() {1, 2, Integer.MaxValue}
' 「New Integer()」で上限指定(「New Integer(2)」)は不要(指定しても構わない)
PrintCollection(array, "整数の配列")
' 出力:
' 整数の配列
' [0] 1
' [1] 2
' [2] 2147483647
配列の宣言と初期化について詳しくは、「.NET TIPS:C#で配列を宣言するには?」「.NET TIPS:VB.NETで配列を宣言するには?」をご覧いただきたい。
冒頭のコレクション初期化子の例では、コレクションに文字列を追加した。実際には、何らかのクラスをインスタンス化し、さらに幾つかのプロパティを設定してからコレクションに追加することが多いだろう。
例えば、次のコードに示す「SampleClass」クラスのインスタンスを作り、「Year」プロパティなどをセットしてからコレクションに追加したいとする。
class SampleClass
{
public int Year { get; set; } = 2001;
public int Month { get; set; } = 1;
public int Day { get; set; } = 1;
public override string ToString() => $"{Year:0000}/{Month:00}/{Day:00}";
}
Class SampleClass
Public Property Year As Integer = 2001
Public Property Month As Integer = 1
Public Property Day As Integer = 1
Public Overrides Function ToString() As String
Return $"{Year:0000}/{Month:00}/{Day:00}"
End Function
End Class
そのようなときは、オブジェクト初期化子を使って、追加するオブジェクトの生成と初期化もいっぺんに書いてしまえる(次のコード)。
List<SampleClass> list = new List<SampleClass>
{
new SampleClass { Year=2017, Month=1, Day=18 },
new SampleClass { Year=2017, Month=2, Day=14 },
new SampleClass { Year=2017, Month=3, },
null,
};
PrintCollection(list, "一般的なクラスのコレクション");
// 出力:
// 一般的なクラスのコレクション
// [0] 2017/01/18
// [1] 2017/02/14
// [2] 2017/03/01
// [3] (null)
Dim list As List(Of SampleClass) = New List(Of SampleClass) _
From {
New SampleClass With {.Year = 2017, .Month = 1, .Day = 18},
New SampleClass With {.Year = 2017, .Month = 2, .Day = 14},
New SampleClass With {.Year = 2017, .Month = 3},
Nothing
}
PrintCollection(list, "一般的なクラスのコレクション")
' 出力:
' 一般的なクラスのコレクション
' [0] 2017/01/18
' [1] 2017/02/14
' [2] 2017/03/01
' [3] (Nothing)
このオブジェクト初期化子については、「.NET TIPS:構文:インスタンス化と同時にプロパティを設定するには?[C#/VB]」で解説している。
キーとバリューを格納するDictionaryクラスのように、要素を追加するときに複数の引数を要求するコレクションもある。そのときは、コレクション初期化子の中で引数のセットを中かっこで囲む(次のコード)。
Dictionary<int, SampleClass> dic = new Dictionary<int, SampleClass>
{
{1, new SampleClass { Year=2017, Month=1, Day=18 }},
{2, new SampleClass { Year=2017, Month=2, Day=14 }},
{3, new SampleClass { Year=2017, Month=3, }},
{4, null },
};
PrintCollection(dic, "Addメソッドに複数の引数が要求されるもの");
// 出力:
// Addメソッドに複数の引数が要求されるもの
// [0] [1, 2017/01/18]
// [1] [2, 2017/02/14]
// [2] [3, 2017/03/01]
// [3] [4, ]
Dim dic As Dictionary(Of Integer, SampleClass) _
= New Dictionary(Of Integer, SampleClass) _
From {
{1, New SampleClass With {.Year = 2017, .Month = 1, .Day = 18}},
{2, New SampleClass With {.Year = 2017, .Month = 2, .Day = 14}},
{3, New SampleClass With {.Year = 2017, .Month = 3}},
{4, Nothing}
}
PrintCollection(dic, "Addメソッドに複数の引数が要求されるもの")
' 出力:
' Addメソッドに複数の引数が要求されるもの
' [0] [1, 2017/01/18]
' [1] [2, 2017/02/14]
' [2] [3, 2017/03/01]
' [3] [4, ]
Dictionaryコレクションを初期化する方法について、詳しくは「.NET TIPS:Dictionaryクラスを簡単に初期化するには?[C# 3.0]」を参照してほしい。
Dictionaryコレクションを初期化するとき、C# 6.0ではインデックス初期化子を使っても上と同じ結果が得られる(次のコード)。
Dictionary<int, SampleClass> dic = new Dictionary<int, SampleClass>
{
[5] = new SampleClass { Year = 2017, Month = 1, Day = 18 },
[6] = new SampleClass { Year = 2017, Month = 2, Day = 14 },
[7] = new SampleClass { Year = 2017, Month = 3, },
[8] = null,
};
PrintCollection(dic, "インデックス初期化子(C# 6.0)");
// 出力:
// インデックス初期化子(C# 6.0)
// [0] [5, 2017/01/18]
// [1] [6, 2017/02/14]
// [2] [7, 2017/03/01]
// [3] [8, ]
コレクション初期化子は、コレクションのインスタンスを作ってから、そのAddメソッドを呼び出して要素を追加する。
その動作を確認するため、次のコードのようなコレクションクラスを作る。Listクラスに対して、コンストラクタが呼び出されたときとAddメソッドが呼び出されたときにコンソールにそのことを表示する機能を持たせたものだ。
class MyList<T> : List<T>
{
public MyList() : base()
{
WriteLine($"コンストラクタ完了(要素数={this.Count})");
}
new public void Add(T item)
{
base.Add(item);
WriteLine($"Addメソッド呼び出し完了: {item?.ToString() ?? "(null)"}
(要素数={this.Count})"); // 前の行に続けて1行に書く
}
}
Class MyList(Of T)
Inherits List(Of T)
Public Sub New()
MyBase.New()
WriteLine($"コンストラクタ完了(要素数={Me.Count})")
End Sub
Public Overloads Sub Add(item As T)
MyBase.Add(item)
WriteLine($"Addメソッド呼び出し完了: {If(item IsNot Nothing, item.ToString(),
"(Nothing)")}(要素数={Me.Count})") ' 前の行に続けて1行に書く
End Sub
End Class
このMyListクラスを使うと、コレクション初期化子の動作が分かる(次のコード)。
WriteLine("実際にはインスタンス化した後でAddメソッドが呼び出される");
MyList<SampleClass> list = new MyList<SampleClass>
{
new SampleClass { Year=2017, Month=1, Day=18 },
new SampleClass { Year=2017, Month=2, Day=14 },
new SampleClass { Year=2017, Month=3, },
null,
};
// 出力:
// 実際にはインスタンス化した後でAddメソッドが呼び出される
// コンストラクタ完了(要素数=0)
// Addメソッド呼び出し完了: 2017/01/18(要素数=1)
// Addメソッド呼び出し完了: 2017/02/14(要素数=2)
// Addメソッド呼び出し完了: 2017/03/01(要素数=3)
// Addメソッド呼び出し完了: (null)(要素数=4)
WriteLine("実際にはインスタンス化した後でAddメソッドが呼び出される")
Dim list As MyList(Of SampleClass) = New MyList(Of SampleClass) _
From {
New SampleClass With {.Year = 2017, .Month = 1, .Day = 18},
New SampleClass With {.Year = 2017, .Month = 2, .Day = 14},
New SampleClass With {.Year = 2017, .Month = 3},
Nothing
}
' 出力:
' 実際にはインスタンス化した後でAddメソッドが呼び出される
' コンストラクタ完了(要素数=0)
' Addメソッド呼び出し完了: 2017/01/18(要素数=1)
' Addメソッド呼び出し完了: 2017/02/14(要素数=2)
' Addメソッド呼び出し完了: 2017/03/01(要素数=3)
' Addメソッド呼び出し完了: (Nothing)(要素数=4)
上で見たように、コレクション初期化子はAddメソッドの呼び出しにコンパイルされる。ならば、自作のクラスでもコレクション初期化子に対応できそうだ。
実際には、Addメソッドを持っていることの他に、IEnumerableインタフェースを実装していることも条件になる(この2つの条件がMSDNにはOR条件であるように書かれているが、AND条件である)。
コレクション初期化子に対応したクラス(=IEnumerableインタフェースを実装しAddメソッドを持つクラス)の例を次のコードに示す。
class SampleClass2 : IEnumerable
{
private List<string> _strings = new List<string>();
// Addメソッド(C# 6.0からは拡張メソッドでも可)
public void Add(string item) => _strings.Add(item);
public override string ToString() => string.Join(" ", _strings);
// インタフェースIEnumerableの実装(IEnumerable<string>の実装でも可)
IEnumerator IEnumerable.GetEnumerator() => _strings.GetEnumerator();
}
Class SampleClass2
Implements IEnumerable
Private _strings As List(Of String) = New List(Of String)
' Addメソッド(拡張メソッドでも可)
Public Sub Add(item As String)
_strings.Add(item)
End Sub
Public Overrides Function ToString() As String
Return String.Join(" ", _strings)
End Function
' インタフェースIEnumerableの実装(IEnumerable(Of String)の実装でも可)
Private Function IEnumerable_GetEnumerator() As IEnumerator _
Implements IEnumerable.GetEnumerator
Return _strings.GetEnumerator()
End Function
End Class
上の「SampleClass2」クラスを、コレクション初期化子を使って初期化するコードを次に示す。
WriteLine("IEnumerableでAddメソッドを持っていればよい");
SampleClass2 sc = new SampleClass2 { "Hello,", "Collection Initializer", "!!" };
WriteLine(sc);
// 出力:
// IEnumerableでAddメソッドを持っていればよい
// Hello, Collection Initializer !!
WriteLine("IEnumerableでAddメソッドを持っていればよい")
Dim sc As SampleClass2 _
= New SampleClass2 From {"Hello,", "Collection Initializer", "!!"}
WriteLine(sc)
' 出力:
' IEnumerableでAddメソッドを持っていればよい
' Hello, Collection Initializer !!
コレクション初期化子を使うと、コレクションのインスタンス化と要素の追加をいっぺんに書ける。配列初期化子と似ているが、VBではコレクション初期化子にFromキーワードが必須なので気を付けよう。
コレクション初期化子の中でもオブジェクト初期化子は使える。VBでは、オブジェクト初期化子にWithキーワードが必要だ。
コレクション初期化子に対応したクラスを作るには、IEnumerableインタフェースを実装し、Addメソッドを用意する。
利用可能バージョン:C# 3.0(Visual Studio 2008)/VB 10(Visual Studio 2010)以降(サンプルコードにはそれ以降の構文も含む)
カテゴリ:C# 処理対象:言語構文
カテゴリ:Visual Basic .NET 処理対象:言語構文
関連TIPS:Visual Studioでコンソール・アプリケーションのデバッグ実行時にコマンド・プロンプトを閉じないようにするには?
関連TIPS:構文:クラス名を書かずに静的メソッドを呼び出すには?[C# 6.0]
関連TIPS:VB.NETでクラス名を省略してメソッドや定数を利用するには?
関連TIPS:数値を右詰めや0埋めで文字列化するには?[C#、VB]
関連TIPS:C#でnullチェックを簡潔に行うには?
関連TIPS:構文:nullチェックを簡潔に記述するには?[C# 6.0]
関連TIPS:C#で配列を宣言するには?
関連TIPS:VB.NETで配列を宣言するには?
関連TIPS:構文:インスタンス化と同時にプロパティを設定するには?[C#/VB]
関連TIPS:Dictionaryクラスを簡単に初期化するには?[C# 3.0]
Copyright© Digital Advantage Corp. All Rights Reserved.