web-dev-qa-db-fra.com

List <int> test = {1, 2, 3} - est-ce une fonctionnalité ou un bug?

Comme vous le savez, il n'est pas autorisé d'utiliser la syntaxe d'initialisation de tableau avec des listes. Cela donnera une erreur au moment de la compilation. Exemple:

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

Cependant, aujourd'hui, j'ai fait ce qui suit (très simplifié):

class Test
{
     public List<int> Field;
}

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

Le code ci-dessus compile très bien, mais lors de l'exécution, il donnera une erreur d'exécution "Les références d'objet ne sont pas définies sur un objet".

Je m'attendrais à ce que ce code donne une erreur de compilation. Ma question est la suivante: pourquoi ne le fait-il pas et existe-t-il de bonnes raisons pour lesquelles un tel scénario fonctionnerait correctement?

Cela a été testé en utilisant .NET 3.5, les compilateurs .Net et Mono.

À votre santé.

49
mikabytes

Je pense que c'est un comportement voulu. Le Test = { 1, 2, 3 } est compilé en code qui appelle la méthode Add de la liste stockée dans le champ Test.

La raison pour laquelle vous obtenez NullReferenceException est que Test est null. Si vous initialisez le champ Test dans une nouvelle liste, le code fonctionnera:

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 } };

C'est assez logique - si vous écrivez new List<int> { ... } puis il crée une nouvelle instance de liste. Si vous n'ajoutez pas de construction d'objet, il utilisera l'instance existante (ou null). Pour autant que je puisse voir, la spécification C # ne contient aucune règle de traduction explicite qui correspondrait à ce scénario, mais elle donne un exemple (voir Section 7.6.10.):

A List<Contact> peut être créé et initialisé comme suit:

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" }
    }
};

qui a le même effet que

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 et __c2 sont des variables temporaires qui sont autrement invisibles et inaccessibles.

47
Tomas Petricek

Je m'attendrais à ce que ce code donne une erreur de compilation.

Étant donné que votre attente est contraire à la fois à la spécification et à l'implémentation, votre attente ne sera pas satisfaite.

Pourquoi ne échoue-t-il pas au moment de la compilation?

Parce que la spécification stipule spécifiquement que c'est légal dans la section 7.6.10.2, que je cite ici pour votre commodité:


Un initialiseur de membre qui spécifie un initialiseur de collection après le signe égal est une initialisation d'une collection incorporée. Au lieu d'affecter une nouvelle collection au champ ou à la propriété, les éléments donnés dans l'initialiseur sont ajoutés à la collection référencée par le champ ou la propriété.


quand un tel code fonctionnerait-il correctement?

Comme le dit la spécification, les éléments donnés dans l'initialiseur sont ajoutés à la collection référencée par la propriété. La propriété ne fait pas référence à une collection; c'est nul. Par conséquent, lors de l'exécution, il donne une exception de référence nulle. Quelqu'un doit initialiser la liste. Je recommanderais de changer la classe "Test" afin que son constructeur initialise la liste.

Quel scénario motive cette fonctionnalité?

Les requêtes LINQ ont besoin d'expressions, pas d'instructions. L'ajout d'un membre à une collection nouvellement créée dans une liste nouvellement créée nécessite d'appeler "Ajouter". Étant donné que "Ajouter" renvoie un vide, un appel à celui-ci ne peut apparaître que dans une instruction d'expression. Cette fonctionnalité vous permet soit de créer une nouvelle collection (avec "nouveau") et de la remplir, soit de remplir une collection existante (sans "nouvelle"), où la collection est membre d'un objet que vous créez à la suite d'un LINQ requete.

25
Eric Lippert

Ce code:

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

Se traduit par ceci:

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

Puisque Field est null, vous obtenez le NullReferenceException.

Cela s'appelle un initialiseur de collection , et cela fonctionnera dans votre exemple initial si vous faites ceci:

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

Vous devez vraiment actualiser quelque chose pour pouvoir utiliser cette syntaxe, c'est-à-dire qu'un initialiseur de collection ne peut apparaître que dans le contexte d'une expression de création d'objet. Dans la spécification C #, section 7.6.10.1, voici la syntaxe d'une expression de création d'objet:

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

Tout commence donc par une expression new. À l'intérieur de l'expression, vous pouvez utiliser un initialiseur de collection sans new (section 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

Maintenant, ce qui vous manque vraiment, c'est une sorte de liste littérale, ce qui serait vraiment pratique. J'ai proposé un tel littéral pour les énumérables ici .

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

La raison en est que le deuxième exemple est un initialiseur de liste de membres - et l'expression MemberListBinding de System.Linq.Expressions donne un aperçu de cela - veuillez voir ma réponse à cette autre question pour plus de détails: Quels sont certains exemples d'expressions LINQ MemberBinding?

Ce type d'initialiseur nécessite que la liste soit déjà initialisée, afin que la séquence que vous fournissez puisse y être ajoutée.

En conséquence - syntaxiquement, il n'y a absolument rien de mal avec le code - le NullReferenceException est une erreur d'exécution causée par la non-création de la liste. Un constructeur par défaut qui news la liste, ou un new inline dans le corps du code, résoudra l'erreur d'exécution.

Quant à savoir pourquoi il y a une différence entre cela et la première ligne de code - dans votre exemple, ce n'est pas autorisé car ce type d'expression ne peut pas être sur le côté droit d'une affectation, car il ne le fait pas réellement créer n'importe quoi, ce n'est qu'un raccourci pour Add.

2
Andras Zoltan

Changez votre code en ceci:

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

La raison en est que vous devez créer explicitement un objet de collection avant de pouvoir y mettre des éléments.

1
Al Kepp