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

405

Ma question comme 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'a qu'un seul élément à l'intérieur.

Pouvons-nous avoir une méthode comme items.Add(item)?

comme le List<T>

ldsenow
la source
4
IEnumerable<T>est destiné à interroger les collections uniquement. C'est l'épine dorsale du framework LINQ. Il est toujours une abstraction d'une autre collection tels que Collection<T>, List<T>ou Array. L'interface fournit uniquement une GetEnumeratorméthode qui retourne une instance 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 en lecture seule car il peut représenter une agrégation de parties de plusieurs collections.
Jordan
3
Pour cet exemple, déclarez simplement IList<T>au lieu de IEnumerable<T>:IList<T> items = new T[]{new T("msg")}; items.Add(new T("msg2"));
NightOwl888

Réponses:

480

Vous ne pouvez pas, car IEnumerable<T>ne représente pas nécessairement une collection à laquelle des éléments peuvent être ajoutés. En fait, ce n'est pas du tout une collection! 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, cependant, est de créer un nouvel IEnumerable objet (de type non spécifié), qui, une fois énuméré, fournira tous les éléments de l'ancien, ainsi que certains des vôtres. Vous utilisez Enumerable.Concatpour cela:

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

Cela ne changera pas l'objet tableau (vous ne pouvez pas insérer d'éléments dans des tableaux, de toute façon). Mais cela créera un nouvel objet qui listera tous les éléments du tableau, puis "Foo". De plus, ce nouvel objet gardera une trace des changements dans le tableau (c'est-à-dire que chaque fois que vous l'énumérez, vous verrez les valeurs actuelles des éléments).

Pavel Minaev
la source
3
La création d'un tableau pour ajouter une valeur unique est un peu élevée sur la surcharge. Il est un peu plus réutilisable de définir une méthode d'extension pour un Concat à valeur unique.
JaredPar
4
Cela dépend - cela peut valoir la peine, mais je suis tenté d'appeler cela une optimisation prématurée à moins qu'elle ne Concatsoit appelée à plusieurs reprises dans une boucle; et si c'est le cas, vous avez de plus gros problèmes de toute façon, parce que chaque fois que vous Concat, vous obtenez une couche d'énumérateur supplémentaire - donc à la fin de celui-ci, l'appel unique MoveNextentraînera une chaîne d'appels de longueur égale au nombre d'itérations.
Pavel Minaev
2
@ Pavel, heh. Habituellement, ce n'est pas la surcharge de mémoire de la création de la baie qui me dérange. C'est la frappe supplémentaire que je trouve ennuyeuse :).
JaredPar
2
Je comprends donc un peu plus ce que fait IEnumerable. Il convient de ne voir que l'intention de ne pas le modifier. Merci les gars
ldsenow
7
J'ai eu une demande de fonctionnalité Connect pour le sucre syntaxique IEnumerable<T>en C #, y compris la surcharge en operator+tant que Concat(au fait, savez-vous qu'en VB, vous pouvez indexer IEnumerablecomme s'il s'agissait d'un tableau - il utilise ElementAtsous le capot): connect.microsoft.com / VisualStudio / feedback /… . Si nous obtenons également deux surcharges supplémentaires Concatpour prendre un seul élément à gauche et à droite, cela réduirait la frappe xs +=x - alors allez piquer Mads (Torgersen) pour prioriser cela en C # 5.0 :)
Pavel Minaev
98

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

Lorsque vous effectuez des opérations comme .ToList (). Add (), vous créez une nouvelle List<T>et ajoutez une valeur à cette liste. Il n'a aucun lien avec la liste d'origine.

Ce que vous pouvez faire, c'est utiliser la méthode Ajouter une extension pour en créer un nouveau IEnumerable<T>avec la valeur ajoutée.

items = items.Add("msg2");

Même dans ce cas, cela ne modifiera pas l' IEnumerable<T>objet d' origine . Cela peut être vérifié en y tenant une référence. 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 autre énumérable avec les valeurs "foo" et "bar".

ÉDITER

J'oublie toujours que Add n'est pas une méthode d'extension typique IEnumerable<T>car c'est l'une des premières que je finis par définir. C'est ici

public static IEnumerable<T> Add<T>(this IEnumerable<T> e, T value) {
  foreach ( var cur in e) {
    yield return cur;
  }
  yield return value;
}
JaredPar
la source
12
Ça s'appelle Concat:)
Pavel Minaev
3
@ Pavel, merci de l'avoir signalé. Même Concat ne fonctionnera pas car il faut un autre IEnumerable <T>. J'ai collé la méthode d'extension typique que je définis dans mes projets.
JaredPar
10
Je n'appellerais pas cela comme ça Add, car Addsur pratiquement n'importe quel autre type .NET (pas seulement les collections) mute la collection en place. Peut With- être ? Ou cela pourrait même être juste une autre surcharge de Concat.
Pavel Minaev
4
Je suis d'accord que la Concatsurcharge serait probablement un meilleur choix car cela Addimplique généralement une mutabilité.
Dan Abramov
15
Qu'en est-il de la modification du corps return e.Concat(Enumerable.Repeat(value,1));?
Roy Tinker
58

Avez-vous envisagé d'utiliser ICollection<T>ou des IList<T>interfaces à la place, elles existent pour la seule raison que vous souhaitez avoir une Addméthode sur un IEnumerable<T>.

IEnumerable<T>est utilisé pour «marquer» un type comme étant ... enfin, énumérable ou simplement une séquence d'éléments sans nécessairement garantir que le véritable objet sous-jacent prend en charge l'ajout / la suppression d'éléments. Souvenez-vous également que ces interfaces implémentent IEnumerable<T>donc vous obtenez également toutes les méthodes d'extensions que vous obtenez IEnumerable<T>.

Abhijeet Patel
la source
31

Quelques méthodes d'extension courtes et douces IEnumerableet IEnumerable<T>faites-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.


la source
La première méthode Prepend me donne une erreur. "'objet []' ne contient pas de définition pour 'Concat' et la meilleure surcharge d'extension de méthode 'System.Linq.Enumerable.Concat <TSource> (System.Collections.Generic.IEnumerable <TSource>, System.Collections.Generic. IEnumerable <TSource>) 'contient des arguments invalides "
Tim Newton
@TimNewton vous vous trompez. Qui sait pourquoi, comme personne ne peut le dire à partir de cet extrait de code d'erreur. Protip: attrapez toujours l'exception et faites- ToString()y, car cela capture plus de détails sur l'erreur. Je suppose que vous ne l'avez pas fait include System.Linq;en haut de votre dossier, mais qui sait? Essayez de le découvrir par vous-même, créez un prototype minimum qui présente la même erreur si vous ne le pouvez pas, puis demandez de l'aide dans une question.
Je viens de copier et coller le code ci-dessus. Tous fonctionnent bien sauf le Prepend qui donne l'erreur que j'ai mentionnée. Ce n'est pas une exception d'exécution, c'est une exception de compilation.
Tim Newton
@TimNewton: Je ne sais pas de quoi vous parlez, cela fonctionne parfaitement, vous vous trompez évidemment, ignorez les changements dans l'édition maintenant je dois être en route, bonjour, monsieur.
c'est peut-être quelque chose de différent dans notre environnement. J'utilise VS2012 et .NET 4.5. Ne perdez plus de temps à ce sujet, je pensais simplement souligner qu'il y avait un problème avec le code ci-dessus, même s'il était petit. J'ai de toute façon surévalué cette réponse comme étant utile. Merci.
Tim Newton
25

Dans .net Core, il existe une méthode Enumerable.Appendqui fait exactement cela.

Le code source de la méthode est disponible sur GitHub. .... L'implémentation (plus sophistiquée que les suggestions des autres réponses) vaut le détour :).

JanDotNet
la source
3
Ce n'est pas seulement dans le noyau, également le cadre 4.7.1 et plus récent: docs.microsoft.com/en-us/dotnet/api/…
sschoof
Notez que Prepend
aloisdg passe à codidact.com le
22

Non, l'IEnumerable ne prend pas en charge l'ajout d'éléments.

Votre «alternative» est:

var List = new List(items);
List.Add(otherItem);
Paul van Brenk
la source
4
Votre suggestion n'est pas la seule alternative. Par exemple, ICollection et IList sont tous deux des options raisonnables ici.
Jason
6
Mais vous ne pouvez pas non plus instancier.
Paul van Brenk
16

Pour ajouter un deuxième message, vous devez -

IEnumerable<T> items = new T[]{new T("msg")};
items = items.Concat(new[] {new T("msg2")})
Aamol
la source
1
Mais vous devez également affecter le résultat de la méthode Concat à l'objet items.
Tomasz Przychodzki
1
Oui, Tomasz, tu as raison. J'ai modifié la dernière expression pour attribuer des valeurs concaténées aux éléments.
Aamol
8

Non seulement vous ne pouvez pas ajouter des éléments comme vous le déclarez, mais si vous ajoutez un élément à une List<T>(ou à peu près toute autre collection non en lecture seule) pour laquelle vous avez un énumérateur existant, l'énumérateur est invalidé (jetteInvalidOperationException partir de là).

Si vous regroupez les résultats d'un certain type de requête de données, vous pouvez utiliser le Concat méthode d'extension:

Edit: j'ai utilisé à l'origine l' Unionextension dans l'exemple, ce qui n'est pas vraiment correct. Mon application l'utilise largement pour s'assurer que les requêtes qui se chevauchent ne reproduisent pas les résultats.

IEnumerable<T> itemsA = ...;
IEnumerable<T> itemsB = ...;
IEnumerable<T> itemsC = ...;
return itemsA.Concat(itemsB).Concat(itemsC);
Sam Harwell
la source
8

Je viens ici pour dire que, à part la Enumerable.Concatméthode d'extension, il semble y avoir une autre méthode nommée Enumerable.Appenddans .NET Core 1.1.1. Ce dernier vous permet de concaténer un seul élément à une séquence existante. Donc, la réponse d'Aamol peut également être écrite comme

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

Néanmoins, veuillez noter que cette fonction ne changera pas la séquence d'entrée, elle renvoie simplement un wrapper qui associe la séquence donnée et l'élément ajouté.

CXuesong
la source
4

D'autres ont déjà donné de grandes explications sur les raisons pour lesquelles vous ne pouvez pas (et ne devriez pas!) Être en mesure d'ajouter des éléments à un IEnumerable. J'ajouterai seulement que si vous cherchez à continuer de coder vers une interface qui représente une collection et que vous voulez une méthode add, vous devez coder vers ICollectionou IList. Comme une aubaine supplémentaire, ces interfaces implémentent IEnumerable.

Jason
la source
1

tu peux le faire.

//Create IEnumerable    
IEnumerable<T> items = new T[]{new T("msg")};

//Convert to list.
List<T> list = items.ToList();

//Add new item to list.
list.add(new T("msg2"));

//Cast list to IEnumerable
items = (IEnumerable<T>)items;
Lrodriguez84
la source
8
Il y a 5 ans, cette question a reçu une réponse plus complète. Considérez la réponse acceptée comme un exemple de bonne réponse à une question.
pquest
1
La dernière ligne était: items = (IEnumerable <T>) list;
Belen Martin
0

Le moyen le plus simple de le faire est tout simplement

IEnumerable<T> items = new T[]{new T("msg")};
List<string> itemsList = new List<string>();
itemsList.AddRange(items.Select(y => y.ToString()));
itemsList.Add("msg2");

Ensuite, vous pouvez renvoyer la liste sous la forme IEnumerable, car elle implémente l'interface IEnumerable

Juha Sinkkonen
la source
0

Si vous créez votre objet comme IEnumerable<int> Ones = new List<int>(); alors vous pouvez le faire((List<int>)Ones).Add(1);

Erik Hosszú
la source
-8

Bien sûr, vous pouvez (je laisse votre T-business de côté):

public IEnumerable<string> tryAdd(IEnumerable<string> items)
{
    List<string> list = items.ToList();
    string obj = "";
    list.Add(obj);

    return list.Select(i => i);
}
Just N Observer
la source