49 votes

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

Comme vous le savez, il n'est pas permis d'utiliser la syntaxe d'initialisation des tableaux avec les listes. Cela entraînera une erreur de 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 se compile très bien, mais lorsqu'il est exécuté, il donne une erreur d'exécution "Object references is not set to an object".

Je m'attendrais à ce que ce code donne une erreur de compilation. Ma question est la suivante : Pourquoi ne le fait-il pas, et y a-t-il de bonnes raisons pour qu'un tel scénario s'exécute correctement ?

Ce système a été testé en utilisant .NET 3.5, les compilateurs .Net et Mono.

A la vôtre.

47voto

Tomas Petricek Points 118959

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

La raison pour laquelle vous obtenez NullReferenceException c'est que Test es null . Si vous initialisez le Test à une nouvelle liste, alors 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 la liste. Si vous n'ajoutez pas de construction d'objet, il utilisera l'instance existante (ou null ). Pour autant que je sache, la spécification C# ne contient pas de règle de traduction explicite qui correspondrait à ce scénario, mais elle donne un exemple (voir Section 7.6.10.3 ) :

A List<Contact> peuvent être créés et initialisés 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);

donde __c1 et __c2 sont des variables temporaires qui sont par ailleurs invisibles et inaccessibles.

25voto

Eric Lippert Points 300275

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

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

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

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


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


quand un tel code s'exécuterait-il correctement ?

Comme le dit la spécification, les éléments donnés dans l'initialisateur sont ajoutés à la collection référencée par la propriété. La propriété ne fait pas référence à une collection ; elle est nulle. Par conséquent, au moment de l'exécution, une exception de référence nulle est générée. Quelqu'un doit initialiser la liste. Je recommande de modifier la classe "Test" pour que son constructeur initialise la liste.

Quel scénario motive cette fonctionnalité ?

Les requêtes LINQ ont besoin d'expressions, pas de déclarations. L'ajout d'un membre à une collection nouvellement créée dans une liste nouvellement créée nécessite d'appeler "Add". Étant donné que "Add" renvoie un résultat nul, un appel à cette fonction ne peut apparaître que dans une expression. Cette fonctionnalité vous permet soit de créer une nouvelle collection (avec "new") et de la remplir, soit de remplir une collection existante (sans "new"), où la collection est un membre d'un objet que vous créez comme résultat d'une requête LINQ.

18voto

Jordão Points 29221

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

Depuis Field es null vous obtenez le NullReferenceException .

C'est ce qu'on appelle un initialisateur de collection et cela fonctionnera dans votre exemple initial si vous faites cela :

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

Vous devez vraiment inventer quelque chose pour pouvoir utiliser cette syntaxe, c'est-à-dire qu'un initialisateur 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 un new expression. À l'intérieur de l'expression, vous pouvez utiliser un initialisateur de collection sans la balise 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

Ce qui 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 aquí .

3voto

Kris Ivanov Points 5350
var test = (new [] { 1, 2, 3}).ToList();

2voto

Andras Zoltan Points 24996

La raison en est que le deuxième exemple est un initialisateur 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 les exemples d'expressions LINQ de MemberBinding ?

Ce type d'initialisateur 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 - d'un point de vue syntaxique, il n'y a absolument rien d'anormal dans le code - l'élément NullReferenceException est une erreur d'exécution due au fait que la liste n'a pas été créée. Un constructeur par défaut qui new dans la liste, ou une ligne new dans le corps du code, permettra de résoudre l'erreur d'exécution.

Quant à la raison pour laquelle il y a une différence entre cela et la première ligne de code - dans votre exemple, ce n'est pas autorisé parce que ce type d'expression ne peut pas être à droite d'une affectation parce qu'elle n'est pas réellement créer quoi que ce soit, c'est seulement un raccourci pour Add .

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X