web-dev-qa-db-ja.com

List <int> test = {1、2、3}-機能かバグか?

ご存知のように、リストで配列初期化構文を使用することはできません。コンパイル時エラーが発生します。例:

List<int> test = { 1, 2, 3} 
// At compilation the following error is shown:
// Can only use array initializer expressions to assign to array types. 

しかし、今日私は次のことをしました(非常に単純化されています):

class Test
{
     public List<int> Field;
}

List<Test> list = new List<Test>
{
    new Test { Field = { 1, 2, 3 } }
};

上記のコードは問題なくコンパイルされますが、実行すると「オブジェクト参照がオブジェクトに設定されていません」という実行時エラーが発生します。

私はそのコードがコンパイル時エラーを出すと期待します。あなたへの私の質問は、次のとおりです。なぜそれができないのですか。また、そのようなシナリオが正しく実行されるのに適切な理由はありますか?

これは、.NETコンパイラとMonoコンパイラの両方である.NET 3.5を使用してテストされています。

乾杯。

49
mikabytes

これは仕様による動作だと思います。 Test = { 1, 2, 3 }は、Addフィールドに格納されているリストのTestメソッドを呼び出すコードにコンパイルされます。

NullReferenceExceptionが表示されるのは、Testnullであるためです。 Testフィールドを新しいリストに初期化すると、コードは機能します。

class Test {    
  public List<int> Field = new List<int>(); 
}  

// Calls 'Add' method three times to add items to 'Field' list
var t = new Test { Field = { 1, 2, 3 } };

それは非常に論理的です-new List<int> { ... }次に、listの新しいインスタンスを作成します。オブジェクト構築を追加しない場合、既存のインスタンス(またはnull)が使用されます。私の知る限り、C#仕様にはこのシナリオに一致する明示的な変換ルールは含まれていませんが、例を示しています(セクション7.6.10.を参照)。

A List<Contact>は次のように作成および初期化できます:

var contacts = new List<Contact> {
    new Contact {
        Name = "Chris Smith",
        PhoneNumbers = { "206-555-0101", "425-882-8080" }
    },
    new Contact {
        Name = "Bob Harris",
        PhoneNumbers = { "650-555-0199" }
    }
};

これはと同じ効果があります

var contacts = new List<Contact>();
Contact __c1 = new Contact();
__c1.Name = "Chris Smith";
__c1.PhoneNumbers.Add("206-555-0101");
__c1.PhoneNumbers.Add("425-882-8080");
contacts.Add(__c1);
Contact __c2 = new Contact();
__c2.Name = "Bob Harris";
__c2.PhoneNumbers.Add("650-555-0199");
contacts.Add(__c2);

場所__c1および__c2は一時変数であり、それ以外の場合は不可視でアクセスできません。

47
Tomas Petricek

私はそのコードがコンパイル時エラーを与えると期待します。

あなたの期待は仕様と実装の両方に反しているので、あなたの期待は満たされないでしょう。

コンパイル時に失敗しないのはなぜですか?

仕様はセクション7.6.10.2でそれが合法であることを明確に述べているので、あなたの便宜のためにここで引用します。


等号の後にコレクション初期化子を指定するメンバー初期化子は、埋め込みコレクションの初期化です。新しいコレクションをフィールドまたはプロパティに割り当てる代わりに、初期化子で指定された要素が、フィールドまたはプロパティによって参照されるコレクションに追加されます。


そのようなコードはいつ正しく実行されますか?

仕様にあるように、初期化子で指定された要素は、プロパティによって参照されるコレクションに追加されます。プロパティはコレクションを参照しません。それはnullです。したがって、実行時にnull参照例外が発生します。誰かがリストを初期化する必要があります。コンストラクターがリストを初期化するように、「Test」クラスを変更することをお勧めします。

この機能の動機となるシナリオは何ですか?

LINQクエリには、ステートメントではなく式が必要です。新しく作成されたリスト内の新しく作成されたコレクションにメンバーを追加するには、「追加」を呼び出す必要があります。 "Add"はvoidを返すため、呼び出しは式ステートメントでのみ行うことができます。この機能を使用すると、新しいコレクションを作成して( "new"を使用して)作成するか、既存のコレクションを作成して( "new"を使用せずに)作成できます。このコレクションは、LINQの結果として作成するオブジェクトのメンバーです。クエリ。

25
Eric Lippert

このコード:

Test t = new Test { Field = { 1, 2, 3 } };

これに翻訳されます:

Test t = new Test();
t.Field.Add(1);
t.Field.Add(2);
t.Field.Add(3);

Fieldnullなので、NullReferenceExceptionを取得します。

これは collection initializer と呼ばれ、これを行うと、最初の例で機能します。

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

この構文を使用できるようにするには、何かを新しくする必要があります。つまり、コレクション初期化子は、オブジェクト作成式のコンテキストでのみ表示できます。 C#仕様のセクション7.6.10.1では、これはオブジェクト作成式の構文です。

object-creation-expression:
  new type ( argument-list? ) object-or-collection-initializer?
  new type object-or-collection-initializer
object-or-collection-initializer:
  object-initializer
  collection-initializer

つまり、すべてnew式で始まります。式の中で、newなしのコレクション初期化子を使用できます(セクション7.6.10.2):

object-initializer:
  { member-initializer-list? }
  { member-initializer-list , }
member-initializer-list:
  member-initializer
  member-initializer-list , member-initializer
member-initializer:
  identifier = initializer-value
initializer-value:
  expression
  object-or-collection-initializer // here it recurses

さて、あなたが本当に欠けているのは、ある種のリストリテラルです。これは本当に便利でしょう。私は列挙型 here にそのようなリテラルを提案しました。

18
Jordão
var test = (new [] { 1, 2, 3}).ToList();
3
Kris Ivanov

これは、2番目の例がメンバーリストの初期化子であり、System.Linq.ExpressionsのMemberListBinding式がこれに対する洞察を与えるためです。詳細については、この他の質問に対する私の回答を参照してください: MemberBinding LINQ式の例は何ですか?

このタイプのイニシャライザでは、リストがすでに初期化されている必要があります。これにより、指定したシーケンスをリストに追加できます。

その結果-構文的にはコードにまったく問題はありません-NullReferenceExceptionは、実際には作成されていないリストが原因で発生するランタイムエラーです。リストをnewsするデフォルトのコンストラクタ、またはコード本文のインラインnewは、ランタイムエラーを解決します。

なぜそれとコードの最初の行の間に違いがあるのか​​については-あなたの例ではこのタイプの式は実際にはないので割り当ての右側にあることができないので許可されませんcreate何でも、それはAddの省略形にすぎません。

2
Andras Zoltan

コードを次のように変更します。

class Test
{
   public List<int> Field = new List<int>();
}

その理由は、アイテムを配置する前に、コレクションオブジェクトを明示的に作成する必要があるためです。

1
Al Kepp