504 votes

Comment ajouter un élément à une collection IEnumerable<T> ?

Ma question correspond au titre ci-dessus. Par exemple,

IEnumerable<T> items = new T[]{new T("msg")};
items.ToList().Add(new T("msg2"));

mais après tout, il n'y a qu'un seul objet à l'intérieur.

Peut-on avoir une méthode comme items.Add(item) ?

comme le List<T>

6 votes

IEnumerable<T> est destiné à l'interrogation des collections uniquement. C'est l'épine dorsale du cadre LINQ. C'est toujours une abstraction d'une autre collection telle que Collection<T> , List<T> o Array . L'interface fournit uniquement un GetEnumerator qui renvoie une instance de IEnumerator<T> qui permet de parcourir la collection étendue un élément à la fois. Les méthodes d'extension LINQ sont limitées par cette interface. IEnumerable<T> est conçu pour être lu uniquement car il peut représenter une agrégation de parties de plusieurs collections.

3 votes

Pour cet exemple, il suffit de déclarer IList<T> au lieu de IEnumerable<T> : IList<T> items = new T[]{new T("msg")}; items.Add(new T("msg2"));

571voto

Pavel Minaev Points 60647

Vous ne pouvez pas, parce que IEnumerable<T> ne représente pas nécessairement une collection à laquelle on peut ajouter des éléments. En fait, il ne représente pas nécessairement une collection du tout ! Par exemple :

IEnumerable<string> ReadLines()
{
     string s;
     do
     {
          s = Console.ReadLine();
          yield return s;
     } while (!string.IsNullOrEmpty(s));
}

IEnumerable<string> lines = ReadLines();
lines.Add("foo") // so what is this supposed to do??

Ce que vous pouvez faire, en revanche, c'est créer un fichier nouveau IEnumerable (de type non spécifié), qui, lorsqu'il est énuméré, fournira tous les éléments de l'ancien, plus certains des vôtres. Vous utilisez Enumerable.Concat pour ça :

 items = items.Concat(new[] { "foo" });

Ce site ne modifiera pas l'objet tableau (de toute façon, vous ne pouvez pas insérer d'éléments dans les tableaux to). Mais il créera un nouvel objet qui listera tous les éléments du tableau, et ensuite "Foo". De plus, ce nouvel objet garder la trace des changements dans le tableau (c'est-à-dire qu'à chaque fois que vous l'énumérerez, vous verrez les valeurs actuelles des éléments).

3 votes

Créer un tableau pour ajouter une seule valeur est un peu lourd. Il est un peu plus réutilisable de définir une méthode d'extension pour une seule valeur Concat.

4 votes

Cela dépend - cela peut en valoir la peine, mais je suis tenté de parler d'optimisation prématurée, à moins qu'il ne s'agisse d'une erreur. Concat est appelé dans une boucle de façon répétée ; et si c'est le cas, vous avez de toute façon de plus gros problèmes, car chaque fois que vous Concat vous obtenez une couche supplémentaire d'énumérateur, de sorte qu'à la fin, l'appel unique à MoveNext entraînera une chaîne d'appels dont la longueur est égale au nombre d'itérations.

2 votes

@Pavel, heh. En général, ce n'est pas la surcharge mémoire liée à la création du tableau qui me dérange. C'est la saisie supplémentaire que je trouve ennuyeuse :).

125voto

JaredPar Points 333733

Le type IEnumerable<T> ne prend pas en charge de telles opérations. Le but de la IEnumerable<T> est de permettre à un consommateur de visualiser le contenu d'une collection. Pas de modifier les valeurs.

Lorsque vous effectuez des opérations telles que .ToList().Add(), vous créez un nouveau fichier de type List<T> et en ajoutant une valeur à cette liste. Il n'y a aucun lien avec la liste d'origine.

Ce que vous pouvez faire est d'utiliser la méthode d'extension Add pour créer une nouvelle IEnumerable<T> avec la valeur ajoutée.

items = items.Add("msg2");

Même dans ce cas, il ne modifiera pas l'original. IEnumerable<T> objet. Cela peut être vérifié en détenant une référence à cet objet. Par exemple

var items = new string[]{"foo"};
var temp = items;
items = items.Add("bar");

Après cet ensemble d'opérations, la variable temp ne fera toujours référence qu'à un énumérable avec un seul élément "foo" dans l'ensemble de valeurs, tandis que les éléments feront référence à un énumérable différent avec les valeurs "foo" et "bar".

EDIT

J'oublie constamment que la méthode Add n'est pas une méthode d'extension typique de l'application IEnumerable<T> parce que c'est l'un des premiers que je finis par définir. Le voici

public static IEnumerable<T> Add<T>(this IEnumerable<T> e, T value) {
  foreach ( var cur in e) {
    yield return cur;
  }
  yield return value;
}

3 votes

@Pavel, merci de l'avoir signalé. Même Concat ne fonctionnera pas car il prend un autre IEnumerable<T>. J'ai collé la méthode d'extension typique que je définis dans mes projets.

18 votes

Je n'appellerais pas ça Add cependant, parce que Add sur pratiquement tous les autres types .NET (pas seulement les collections) modifie la collection sur place. Peut-être With ? Ou cela pourrait même être juste une autre surcharge de Concat .

0 votes

@Pavel, j'ai fait des allers-retours sur ce sujet plusieurs fois. J'ai fini par m'en tenir à Add la plupart du temps parce que c'est simplement la première chose qui me vient à l'esprit. Je trouve rarement qu'il y a de la confusion avec les Add mutants.

78voto

Abhijeet Patel Points 2116

Avez-vous envisagé d'utiliser ICollection<T> o IList<T> Au lieu de cela, les interfaces existent pour la raison même que vous voulez avoir une Add sur un IEnumerable<T> .

IEnumerable<T> est utilisé pour "marquer" un type comme étant... énumérable ou simplement une séquence d'éléments sans nécessairement garantir que l'objet sous-jacent réel supporte l'ajout/le retrait d'éléments. Rappelez-vous également que ces interfaces mettent en œuvre IEnumerable<T> de sorte que vous obtenez toutes les méthodes d'extension que vous obtenez avec IEnumerable<T> également.

37voto

Will Points 76760

Quelques méthodes d'extension courtes et douces sur IEnumerable y IEnumerable<T> fais-le pour moi :

public static IEnumerable Append(this IEnumerable first, params object[] second)
{
    return first.OfType<object>().Concat(second);
}
public static IEnumerable<T> Append<T>(this IEnumerable<T> first, params T[] second)
{
    return first.Concat(second);
}   
public static IEnumerable Prepend(this IEnumerable first, params object[] second)
{
    return second.Concat(first.OfType<object>());
}
public static IEnumerable<T> Prepend<T>(this IEnumerable<T> first, params T[] second)
{
    return second.Concat(first);
}

Élégant (enfin, sauf pour les versions non génériques). Dommage que ces méthodes ne soient pas dans la BCL.

0 votes

La première méthode Prepend me donne une erreur. "'object[]' ne contient pas de définition pour 'Concat' et la meilleure méthode d'extension overload 'System.Linq.Enumerable.Concat<TSource>(System.Collections.Generic.IEnumerable<TSource>, System.Collections.Generic.IEnumerable<TSource>)' possède des arguments invalides".

0 votes

@TimNewton vous le faites mal. Qui sait pourquoi, car personne ne peut le dire à partir de ce bout de code d'erreur. Protip : toujours attraper l'exception et faire un ToString() sur elle, car cela permet de capturer plus de détails sur les erreurs. Je suppose que vous n'avez pas include System.Linq; en haut de votre fichier, mais qui sait ? Essayez de résoudre le problème par vous-même, créez un prototype minimum qui présente la même erreur si vous n'y arrivez pas, puis demandez de l'aide dans une question.

0 votes

J'ai juste copié et collé le code ci-dessus. Tous fonctionnent correctement sauf le Prepend qui donne l'erreur que j'ai mentionnée. Ce n'est pas une exception d'exécution, mais une exception de compilation.

31voto

pb. Points 4609

Non, l'IEnumerable ne permet pas d'ajouter des éléments.

Votre "alternative" est :

var myList = new List(items);
myList.Add(otherItem);

4 votes

Votre suggestion n'est pas la seule alternative. Par exemple, ICollection et IList sont deux options raisonnables ici.

7 votes

Mais vous ne pouvez pas non plus les instancier.

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