Passer un seul élément comme IEnumerable

Existe-t-il un moyen courant de transmettre un seul élément de type T à une méthode qui attend un paramètre IEnumerable ? Le langage est C #, framework version 2.0.

Actuellement, j’utilise une méthode d’assistance (c’est .Net 2.0, donc j’ai tout un tas de méthodes d’assistance de casting / projection similaires à LINQ), mais cela semble idiot:

 public static class IEnumerableExt { // usage: IEnumerableExt.FromSingleItem(someObject); public static IEnumerable FromSingleItem(T item) { yield return item; } } 

Une autre manière serait bien sûr de créer et de remplir une List ou un Array et de la passer au lieu de IEnumerable .

[Edit] En tant que méthode d’extension, il pourrait être nommé:

 public static class IEnumerableExt { // usage: someObject.SingleItemAsEnumerable(); public static IEnumerable SingleItemAsEnumerable(this T item) { yield return item; } } 

Est-ce que j’ai râté quelque chose?

[Edit2] Nous avons trouvé someObject.Yield() (comme @Peter suggéré dans les commentaires ci-dessous) comme étant le meilleur nom pour cette méthode d’extension, principalement pour des raisons de brièveté.

 public static class IEnumerableExt { ///  /// Wraps this object instance into an IEnumerable<T> /// consisting of a single item. ///  ///  Type of the object.  ///  The instance that will be wrapped.  ///  An IEnumerable<T> consisting of a single item.  public static IEnumerable Yield(this T item) { yield return item; } } 

    Votre méthode d’assistance est la façon la plus propre de le faire, IMO. Si vous passez une liste ou un tableau, un morceau de code peu scrupuleux peut le lancer et modifier le contenu, ce qui entraîne un comportement étrange dans certaines situations. Vous pouvez utiliser une collection en lecture seule, mais cela risque de nécessiter encore plus d’emballage. Je pense que votre solution est aussi soignée que possible.

    Eh bien, si la méthode attend un IEnumerable vous devez passer quelque chose qui est une liste, même si elle ne contient qu’un seul élément.

    qui passe

     new T[] { item } 

    comme l’argument devrait être suffisant, je pense

    En C # 3.0, vous pouvez utiliser la classe System.Linq.Enumerable:

     // using System.Linq Enumerable.Repeat(item, 1); 

    Cela créera un nouveau IEnumerable qui ne contient que votre élément.

    En C # 3 (je sais que vous avez dit 2), vous pouvez écrire une méthode d’extension générique qui pourrait rendre la syntaxe un peu plus acceptable:

     static class IEnumerableExtensions { public static IEnumerable ToEnumerable(this T item) { yield return item; } } 

    le code client est alors item.ToEnumerable() .

    Je suis un peu surpris que personne n’ait suggéré une nouvelle surcharge de la méthode avec un argument de type T pour simplifier l’API client.

     public void DoSomething(IEnumerable list) { // Do Something } public void DoSomething(T item) { DoSomething(new T[] { item }); } 

    Maintenant, votre code client peut simplement faire ceci:

     MyItem item = new MyItem(); Obj.DoSomething(item); 

    ou avec une liste:

     List itemList = new List(); Obj.DoSomething(itemList); 

    Cette méthode d’assistance fonctionne pour un élément ou plusieurs.

     public static IEnumerable ToEnumerable(params T[] items) { return items; } 

    Comme je viens de le voir, et vu l’utilisateur de LukeH suggéré, une façon simple de procéder est la suivante:

     public static void PerformAction(params YourType[] items) { // Forward call to IEnumerable overload PerformAction(items.AsEnumerable()); } public static void PerformAction(IEnumerable items) { foreach (YourType item in items) { // Do stuff } } 

    Ce modèle vous permettra d’appeler la même fonctionnalité de multiples façons: un seul élément; plusieurs éléments (séparés par des virgules); un tableau; une liste; un dénombrement, etc.

    Je ne suis pas sûr à 100% de l’efficacité de l’utilisation de la méthode AsEnumerable, mais cela fonctionne très bien.

    Mise à jour: la fonction AsEnumerable semble assez efficace! ( référence )

    Soit (comme cela a déjà été dit)

     MyMethodThatExpectsAnIEnumerable(new[] { myObject }); 

    ou

     MyMethodThatExpectsAnIEnumerable(Enumerable.Repeat(myObject, 1)); 

    En conclusion, la dernière version peut également être intéressante si vous voulez une liste vide d’un object anonyme, par exemple

     var x = MyMethodThatExpectsAnIEnumerable(Enumerable.Repeat(new { a = 0, b = "x" }, 0)); 

    Ce n’est peut-être pas mieux, mais c’est plutôt cool:

     Enumerable.Range(0, 1).Select(i => item); 

    Bien que ce soit exagéré pour une méthode, je pense que certaines personnes trouveront les extensions interactives utiles.

    Les extensions interactives (Ix) de Microsoft incluent la méthode suivante.

     public static IEnumerable Return(TResult value) { yield return value; } 

    Qui peut être utilisé comme ça:

     var result = EnumerableEx.Return(0); 

    Ix ajoute de nouvelles fonctionnalités que l’on ne trouve pas dans les méthodes d’extension originales de Linq et résulte directement de la création des extensions réactives (Rx).

    Think, Linq Extension Methods + Ix = Rx pour IEnumerable .

    Vous pouvez trouver à la fois Rx et Ix sur CodePlex .

    Je suis d’accord avec les commentaires de @ EarthEngine sur le message original, à savoir que «AsSingleton» est un meilleur nom. Voir cette entrée wikipedia . Ensuite, il découle de la définition de singleton que si une valeur nulle est transmise en tant qu’argument que ‘AsSingleton’ doit renvoyer un IEnumerable avec une seule valeur nulle au lieu d’un IEnumerable vide qui réglerait la if (item == null) yield break; débat. Je pense que la meilleure solution est d’avoir deux méthodes: ‘AsSingleton’ et ‘AsSingletonOrEmpty’; dans le cas où un null est passé en tant qu’argument, ‘AsSingleton’ renverra une seule valeur null et ‘AsSingletonOrEmpty’ renverra un IEnumerable vide. Comme ça:

     public static IEnumerable AsSingletonOrEmpty(this T source) { if (source == null) { yield break; } else { yield return source; } } public static IEnumerable AsSingleton(this T source) { yield return source; } 

    Celles-ci seraient plus ou moins analogues aux méthodes d’extension «First» et «FirstOrDefault» sur IEnumerable, ce qui semble juste.

    Cela est 30% plus rapide que le yield ou Enumerable.Repeat lorsqu’il est utilisé dans foreach raison de l’ optimisation du compilateur C # et des mêmes performances dans d’autres cas.

     public struct SingleSequence : IEnumerable { public struct SingleEnumerator : IEnumerator { private readonly SingleSequence _parent; private bool _couldMove; public SingleEnumerator(ref SingleSequence parent) { _parent = parent; _couldMove = true; } public T Current => _parent._value; object IEnumerator.Current => Current; public void Dispose() { } public bool MoveNext() { if (!_couldMove) return false; _couldMove = false; return true; } public void Reset() { _couldMove = true; } } private readonly T _value; public SingleSequence(T value) { _value = value; } public IEnumerator GetEnumerator() { return new SingleEnumerator(ref this); } IEnumerator IEnumerable.GetEnumerator() { return new SingleEnumerator(ref this); } } 

    dans ce test:

      // Fastest among seqs, but still 30x times slower than direct sum // 49 mops vs 37 mops for yield, or c.30% faster [Test] public void SingleSequenceStructForEach() { var sw = new Stopwatch(); sw.Start(); long sum = 0; for (var i = 0; i < 100000000; i++) { foreach (var single in new SingleSequence(i)) { sum += single; } } sw.Stop(); Console.WriteLine($"Elapsed {sw.ElapsedMilliseconds}"); Console.WriteLine($"Mops {100000.0 / sw.ElapsedMilliseconds * 1.0}"); } 

    IanG a un bon article sur le sujet , suggérant EnumerableFrom() comme nom et mentionne que la discussion souligne que Haskell et Rx l’appellent Return . IIRC F # appelle aussi Return . La Seq F # appelle l’opérateur singleton<'T> .

    Si vous êtes prêt à être centré sur C #, il est tentant de l’appeler Yield [en faisant allusion à la yield return de la réalisation].

    Si vous vous intéressez aux aspects de la performance, James Michael Hare a aussi un retour de zéro article ou un article qui vaut bien un scan.

    La manière la plus simple serait de new T[]{item}; ; il n’y a pas de syntaxe pour faire cela. L’équivalent le plus proche que je puisse imaginer est le mot clé params , mais bien sûr, vous devez avoir access à la définition de la méthode et vous ne pouvez l’utiliser qu’avec des tableaux.

    Je suis un peu en retard à la fête mais je partagerai mon chemin quand même. Mon problème était que je voulais lier le ItemSource ou un TreeView WPF à un seul object. La hiérarchie ressemble à ceci:

    Projet> Terrain (s)> Pièce (s)

    Il y avait toujours un seul projet, mais je voulais quand même montrer le projet dans l’arbre, sans avoir à passer une collection avec seulement un object, comme certains l’ont suggéré.
    Puisque vous ne pouvez transmettre que des objects IEnumerable en tant que ItemSource, j’ai décidé de rendre ma classe IEnumerable:

     public class ProjectClass : IEnumerable { private readonly SingleItemEnumerator enumerator; ... public IEnumerator GetEnumerator() => this.enumerator; IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator(); } 

    Et créez mon propre énumérateur en conséquence:

     public class SingleItemEnumerator : IEnumerator { private bool hasMovedOnce; public SingleItemEnumerator(object current) { this.Current = current; } public bool MoveNext() { if (this.hasMovedOnce) return false; this.hasMovedOnce = true; return true; } public void Reset() { } public object Current { get; } } public class SingleItemEnumerator : IEnumerator { private bool hasMovedOnce; public SingleItemEnumerator(T current) { this.Current = current; } public void Dispose() => (this.Current as IDisposable).Dispose(); public bool MoveNext() { if (this.hasMovedOnce) return false; this.hasMovedOnce = true; return true; } public void Reset() { } public T Current { get; } object IEnumerator.Current => this.Current; } 

    Ce n’est probablement pas la solution “la plus propre” mais cela a fonctionné pour moi.

    MODIFIER
    Pour faire respecter le principe de la responsabilité unique tel que @Groo l’a souligné, j’ai créé une nouvelle classe de wrapper:

     public class SingleItemWrapper : IEnumerable { private readonly SingleItemEnumerator enumerator; public SingleItemWrapper(object item) { this.enumerator = new SingleItemEnumerator(item); } public object Item => this.enumerator.Current; public IEnumerator GetEnumerator() => this.enumerator; } public class SingleItemWrapper : IEnumerable { private readonly SingleItemEnumerator enumerator; public SingleItemWrapper(T item) { this.enumerator = new SingleItemEnumerator(item); } public T Item => this.enumerator.Current; public IEnumerator GetEnumerator() => this.enumerator; IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator(); } 

    Qui j’ai utilisé comme ça

     TreeView.ItemSource = new SingleItemWrapper(itemToWrap); 

    EDIT 2
    J’ai corrigé une erreur avec la méthode MoveNext() .