C#で配列のようにリストを初期化できるのはなぜですか?


131

今日、私はC#で次のことができることに驚きました。

List<int> a = new List<int> { 1, 2, 3 };

なぜこれができるのですか?どのコンストラクタが呼び出されますか?自分のクラスでこれを行うにはどうすればよいですか?これは配列を初期化する方法ですが、配列は言語項目であり、リストは単純なオブジェクトです...


1
この質問は助けになることがあります。stackoverflow.com/questions/1744967/...
ボブカウフマン

10
かなりすごいですか?あなたは辞書を初期化するために非常によく似たコードを実行できるだけでなく、:{ { "key1", "value1"}, { "key2", "value2"} }
danludwig

別の見方からすると、この配列のような初期化構文は、基礎となるaのデータ型List<int>が実際には配列のみであることを思い出させます。私はC#チームが名前を付けてArrayList<T>いないので、明白で自然に聞こえるのが嫌いです。
RBT 2017年

回答:


183

これは、.NETのコレクション初期化構文の一部です。この構文は、次の条件を満たす限り、作成する任意のコレクションで使用できます。

  • 実装するIEnumerable(できればIEnumerable<T>

  • というメソッドがあります Add(...)

デフォルトのコンストラクターが呼び出され、Add(...)初期化子の各メンバーに対して呼び出されます。

したがって、これらの2つのブロックはほぼ同じです。

List<int> a = new List<int> { 1, 2, 3 };

そして

List<int> temp = new List<int>();
temp.Add(1);
temp.Add(2);
temp.Add(3);
List<int> a = temp;

必要に応じて、別のコンストラクター呼び出すことができます。たとえば、List<T>成長中ののサイズ変更を防ぐことができます。

// Notice, calls the List constructor that takes an int arg
// for initial capacity, then Add()'s three items.
List<int> a = new List<int>(3) { 1, 2, 3, }

Add()メソッドは単一のアイテムを取る必要がないことに注意してください。たとえば、のAdd()メソッドDictionary<TKey, TValue>は2つのアイテムを取ります。

var grades = new Dictionary<string, int>
    {
        { "Suzy", 100 },
        { "David", 98 },
        { "Karen", 73 }
    };

ほぼ同じです:

var temp = new Dictionary<string, int>();
temp.Add("Suzy", 100);
temp.Add("David", 98);
temp.Add("Karen", 73);
var grades = temp;

したがって、これを独自のクラスに追加するために必要なのは、前述のように、実装IEnumerable(ここでも、できればIEnumerable<T>)して1つ以上のAdd()メソッドを作成することだけです。

public class SomeCollection<T> : IEnumerable<T>
{
    // implement Add() methods appropriate for your collection
    public void Add(T item)
    {
        // your add logic    
    }

    // implement your enumerators for IEnumerable<T> (and IEnumerable)
    public IEnumerator<T> GetEnumerator()
    {
        // your implementation
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }
}

次に、BCLコレクションと同じように使用できます。

public class MyProgram
{
    private SomeCollection<int> _myCollection = new SomeCollection<int> { 13, 5, 7 };    

    // ...
}

(詳細については、MSDNを参照してください)


29
正解ですが、最初の例では「完全に同一」と言うのは正確ではありません。それが全く同じであるList<int> temp = new List<int>(); temp.Add(1); ... List<int> a = temp; ということです、a変数がするまで初期化されませんすべてと呼ばれている追加されます。さもなければ、それをするのList<int> a = new List<int>() { a.Count, a.Count, a.Count };はクレイジーなことです。
Eric Lippert、2012年

1
@ user606723:間のいずれかの本当の違いありませんList<int> a; a = new List<int>() { a.Count };List<int> a = new List<int>() { a.Count };
Joren

4
@ヨレン:あなたは正しいです。実際、C#仕様でT x = y;T x; x = y;、これはと同じであると述べています。この事実は、いくつかの奇妙な状況につながる可能性があります。たとえば、int x = M(out x) + x;は合法であるため、完全にint x; x = M(out x) + x;合法です。
Eric Lippert、2012年

1
@JamesMichaelHare実装する必要はありませんIEnumerable<T>。非ジェネリックIEnumerableで十分なため、コレクション初期化構文を使用できます。
phoog 2012年

2
IDisposableを実装するある種のコレクションを使用している場合、Ericのポイントは重要です。呼び出しのusing (var x = new Something{ 1, 2 })1つAddが失敗した場合、オブジェクトは破棄されません。
ポージュ


8

C#バージョン3.0仕様によると、「コレクション初期化子が適用されるコレクションオブジェクトは、1つのTに対してSystem.Collections.Generic.ICollectionを実装するタイプである必要があります。」

ただし、この情報は、これを書いている時点では不正確であるようです。以下のコメントで、Eric Lippertの説明を参照してください。


10
そのページは正確ではありません。これを私の注意を喚起してくれてありがとう。それは、どういうわけか削除されなかったいくつかのプレリリース前のドキュメントでなければなりません。元の設計ではICollectionが必要でしたが、それが私たちが解決した最終的な設計ではありません。
Eric Lippert、2012年

1
説明していただきありがとうございます。
Olivier Jacot-Descombes 2012年


6

コレクション初期化子のもう1つの優れた点は、Addメソッドのオーバーロードを複数持つことができ、それらをすべて同じ初期化子で呼び出すことができることです。たとえば、これは動作します:

public class MyCollection<T> : IEnumerable<T>
{
    public void Add(T item, int number)
    {

    }
    public void Add(T item, string text) 
    {

    }
    public bool Add(T item) //return type could be anything
    {

    }
}

var myCollection = new MyCollection<bool> 
{
    true,
    { false, 0 },
    { true, "" },
    false
};

正しいオーバーロードを呼び出します。また、nameを持つメソッドだけを探しAddます。戻り値の型は何でもかまいません。


0

配列のような構文は、一連のAdd()呼び出しで変換されます。

より興味深い例でこれを確認するには、C#で最初に違法に聞こえる2つの興味深いことを行う次のコードを考えます。1)readonlyプロパティを設定する、2)初期化子のような配列でリストを設定する。

public class MyClass
{   
    public MyClass()
    {   
        _list = new List<string>();
    }
    private IList<string> _list;
    public IList<string> MyList 
    { 
        get
        { 
            return _list;
        }
    }
}
//In some other method
var sample = new MyClass
{
    MyList = {"a", "b"}
};

このコードは完全に機能しますが、1)MyListは読み取り専用で、2)配列初期化子を使用してリストを設定します。

これが機能する理由は、オブジェクト初期化子の一部であるコードでは、コンパイラーは常に、任意の{}構文をAdd()、読み取り専用フィールドでも完全に合法な一連の呼び出しに変換するためです。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.