構文:コレクションのインスタンス化と同時に要素を追加するには?[C#/VB]:.NET TIPS
「コレクションを作成して、それにいちいち要素を追加して」というのは面倒だ。そうではなく、コレクションの作成と同時にその要素を追加する方法を解説する。
対象: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"}
太字の部分がコレクション初期化子である。作成したコレクションのインスタンス(「list」変数)に、3つの要素を追加している。
C#では、コレクション初期化子を使う場合は「new List<string>」の後ろに「()」を付けなくてもよい。
VBでは、中かっこの前にFromキーワードを置く。また、追加する最後のオブジェクト(ここではNothing)の後ろに「,」を付けてはいけない(C#では最後に「,」を付けても構わない)。
文字列を追加する場合
コレクション初期化子には、追加するオブジェクトを「,」(カンマ)で区切って並べる。
分かりやすいのは上に示したような文字列を追加するケースだろう。実際にコンソールアプリを書いて試してみよう。コレクションの要素をコンソールに表示する「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
Mainメソッドの先頭で、文字列を格納するListコレクションを作り、そこに3つの要素を追加している。コレクション初期化子にnull/Nothingを与えた場合でも要素は追加される(追加された要素の内容がnull/Nothingになる)。
C#コードの冒頭にある「using static System.Console;」という書き方は、Visual Studio 2015からのものだ。詳しくは、「.NET TIPS:構文:クラス名を書かずに静的メソッドを呼び出すには?[C# 6.0]」をご覧いただきたい。同様な機能がVBには以前から備わっており、「.NET TIPS:VB.NETでクラス名を省略してメソッドや定数を利用するには?」で解説している。
また、「Main」メソッド末尾にReadKeyメソッドを置く意味は、「.NET TIPS:Visual Studioでコンソール・アプリケーションのデバッグ実行時にコマンド・プロンプトを閉じないようにするには?」をご覧いただきたい。
「PrintCollection」メソッドに出てくる先頭に「$」記号が付いた文字列については、「.NET TIPS:数値を右詰めや0埋めで文字列化するには?[C#、VB]」の後半(「C# 6.0/VB 14で追加された補間文字列機能を使用する」)を見てほしい。また、その中に埋め込んだC#コードの「o?.ToString() ?? "(null)"」という書き方は、「.NET TIPS:C#でnullチェックを簡潔に行うには?」と「.NET TIPS:構文:nullチェックを簡潔に記述するには?[C# 6.0]」で説明している。VBコードの同じ箇所に登場する「If(o, "(Nothing)")」という書き方は、If文ではなく、If演算子である(C#の三項演算子とNull条件演算子に相当)。
配列初期化子
配列に関しては、コレクション初期化子と似た機能として、それ以前から配列初期化子がある(次のコード)。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
VBでは、配列初期化子の場合はFromキーワードを書かない(書くとコンパイルエラーになる)。
配列の宣言と初期化について詳しくは、「.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
自動実装プロパティの宣言と同時に初期値を設定している(C#/VB)。
また、C#コードのToStringメソッドは、ラムダ式で実装している。詳しくは「.NET TIPS:構文:メソッドやプロパティをラムダ式で簡潔に実装するには?[C# 6.0]」をご覧いただきたい。
そのようなときは、オブジェクト初期化子を使って、追加するオブジェクトの生成と初期化もいっぺんに書いてしまえる(次のコード)。
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)
太字の部分がオブジェクト初期化子である。VBではWithキーワードが必要だ。
このオブジェクト初期化子については、「.NET TIPS:構文:インスタンス化と同時にプロパティを設定するには?[C#/VB]」で解説している。
Dictionaryコレクションの場合
キーとバリューを格納する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クラスのAddメソッドは、キーとバリューの2つの引数を要求する。ここでは、キーとして1〜4の整数を、バリューとしてSampleClassオブジェクトを与えている。
Dictionaryコレクションを初期化する方法について、詳しくは「.NET TIPS:Dictionaryクラスを簡単に初期化するには?[C# 3.0]」を参照してほしい。
インデックス初期化子(C#)
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, ]
書く手間はコレクション初期化子と大して変わらないが、インデクサーに対して値を設定していることが分かりやすい。コレクション初期化子とは混在できない。
なお、インデックス初期化子は、インデクサーを持っているクラスに対して機能する。そのクラスがIEnumerableインタフェースの実装やAddメソッドを持っている必要はない。
コレクション初期化子の動作を確かめる
コレクション初期化子は、コレクションのインスタンスを作ってから、その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)
コンストラクタの完了時点では「要素数=0」と報告された。その時点では、要素はまだ何も追加されていない。
その後、追加する要素の数だけ繰り返しAddメソッドが呼び出されている。
コレクション初期化子に対応した自作コレクション
上で見たように、コレクション初期化子は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
IEnumerableインタフェースの実装とAddメソッドの他に、ToStringメソッドもオーバーライドした。ToStringメソッドでは、保持している文字列の全てを空白をはさんで繋ぎ合わせて返すようにしている。
上の「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.