web-dev-qa-db-fra.com

Passer un seul élément en tant que IEnumerable <T>

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

Actuellement, j'utilise une méthode d'assistance (c'est .Net 2.0, donc j'ai beaucoup de méthodes d'assistance de casting/projection similaires à LINQ), mais cela semble simplement ridicule:

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

Une autre solution serait bien sûr de créer et de renseigner un List<T> ou un Array et de le transmettre à la place de IEnumerable<T>.

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

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

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

[Edit2] Nous avons trouvé que someObject.Yield() (comme suggéré par Peter dans les commentaires ci-dessous) est le meilleur nom pour cette méthode d'extension, principalement par souci de brièveté.

public static class IEnumerableExt
{
    /// <summary>
    /// Wraps this object instance into an IEnumerable&lt;T&gt;
    /// consisting of a single item.
    /// </summary>
    /// <typeparam name="T"> Type of the object. </typeparam>
    /// <param name="item"> The instance that will be wrapped. </param>
    /// <returns> An IEnumerable&lt;T&gt; consisting of a single item. </returns>
    public static IEnumerable<T> Yield<T>(this T item)
    {
        yield return item;
    }
}
327
Groo

Votre méthode d'assistance est la manière la plus propre de le faire, IMO. Si vous transmettez une liste ou un tableau, un morceau de code peu scrupuleux pourrait alors le transtyper et en modifier le contenu, ce qui entraînerait un comportement étrange dans certaines situations. Vous pouvez utiliser une collection en lecture seule, mais cela impliquera probablement encore plus de wrapping. Je pense que votre solution est aussi soignée que possible.

84
Jon Skeet

Eh bien, si la méthode attend une 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

110
Mario F

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

// using System.Linq

Enumerable.Repeat(item, 1);

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

103
luksan

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<T> ToEnumerable<T>(this T item)
    {
        yield return item;
    }
}

le code client est alors item.ToEnumerable().

26
Ðаn

Cette méthode d'assistance fonctionne avec un ou plusieurs éléments.

public static IEnumerable<T> ToEnumerable<T>(params T[] items)
{
    return items;
}    
9
duraid

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<T>(IEnumerable<T> list)
{
    // Do Something
}

public void DoSomething<T>(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<MyItem> itemList = new List<MyItem>();
Obj.DoSomething(itemList);
9
Joshua Starner

Comme je viens de le découvrir et de voir que l'utilisateur LukeH a également suggéré, voici un moyen simple et agréable de procéder:

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

public static void PerformAction(IEnumerable<YourType> 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; une énumération, etc.

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

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

7
teppicymon

Bien que ce soit excessif pour une méthode, je pense que certaines personnes peuvent trouver les extensions interactives utiles.

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

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

Qui peut être utilisé comme ceci:

var result = EnumerableEx.Return(0);

Ix ajoute une nouvelle fonctionnalité non trouvée dans les méthodes d'extension Linq d'origine et résulte directement de la création des extensions réactives.

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

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

6
cwharris

Soit (comme cela a été dit précédemment)

MyMethodThatExpectsAnIEnumerable(new[] { myObject });

ou

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

En remarque, la dernière version peut également être Nice si vous voulez une liste vide d’un objet anonyme, par exemple.

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

Cela est 30% plus rapide que yield ou Enumerable.Repeat lorsqu’il est utilisé dans foreach en raison de cette optimisation du compilateur C # , et de la même performance dans d’autres cas.

public struct SingleSequence<T> : IEnumerable<T> {
    public struct SingleEnumerator : IEnumerator<T> {
        private readonly SingleSequence<T> _parent;
        private bool _couldMove;
        public SingleEnumerator(ref SingleSequence<T> 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<T> 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<int>(i)) {
                sum += single;
            }
        }
        sw.Stop();
        Console.WriteLine($"Elapsed {sw.ElapsedMilliseconds}");
        Console.WriteLine($"Mops {100000.0 / sw.ElapsedMilliseconds * 1.0}");
    }
5
V.B.

Je suis d'accord avec les commentaires de @ EarthEngine dans le message d'origine, selon lequel "AsSingleton" est un meilleur nom. Voir cette entrée wikipedia . Ensuite, il ressort de la définition de singleton que si une valeur null est transmise en tant qu'argument, 'AsSingleton' doit renvoyer un IEnumerable avec une seule valeur null au lieu d'un IEnumerable vide, qui réglerait le débat if (item == null) yield break;. Je pense que la meilleure solution consiste à utiliser deux méthodes: "AsSingleton" et "AsSingletonOrEmpty"; où, dans le cas où une valeur null est transmise en tant qu'argument, 'AsSingleton' renverra une valeur null unique et 'AsSingletonOrEmpty' renverra un IEnumerable vide. Comme ça:

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

public static IEnumerable<T> AsSingleton<T>(this T source)
{
    yield return source;
}

Ensuite, celles-ci seraient, plus ou moins, analogues aux méthodes d'extension 'First' et 'FirstOrDefault' sur IEnumerable qui se sentent bien.

4
Jason Boyd

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

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

IanG a un bon article sur le sujet , suggère EnumerableFrom() comme nom et mentionne que la discussion indique que Haskell et Rx l’appellent ReturnIIRC F # l'appelle aussi Return. F # ' Seq APPELLE L'OP&EACUTE;RATEUR singleton<'T> .

Pour tenter si vous êtes prêt à être centré sur C #, appelez-le Yield [faisant allusion au yield return impliqué dans sa réalisation].

Si vous êtes intéressé par les aspects parfaits de celui-ci, James Michael Hare a un renvoyant zéro ou un article après aussi, ce qui vaut bien une analyse.

2
Ruben Bartelink

La façon la plus simple de penser serait new T[]{item};; il n'y a pas de syntaxe pour le faire. L'équivalent le plus proche auquel je peux penser est le mot clé params, mais bien sûr, cela nécessite que vous ayez accès à la définition de la méthode et qu'il ne peut être utilisé qu'avec des tableaux.

1
FacticiusVir

Je suis un peu en retard pour la fête, mais je vais quand même partager mon chemin ... Mon problème était que je voulais lier ItemSource ou un arbre WPF à un seul objet. La hiérarchie ressemble à ceci:

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

Il devait toujours y avoir un seul projet, mais je voulais tout de même afficher le projet dans l’arborescence, sans avoir à passer une collection contenant uniquement cet objet, comme suggéré par certains.
Comme vous ne pouvez transmettre que des objets IEnumerable en tant que ItemSource, j'ai décidé de rendre ma classe IEnumerable:

public class ProjectClass : IEnumerable<ProjectClass>
{
    private readonly SingleItemEnumerator<AufmassProjekt> enumerator;

    ... 

    public IEnumerator<ProjectClass > 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<T> : IEnumerator<T>
{
    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 respecter le principe de responsabilité unique comme @Groo a souligné que 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<T> : IEnumerable<T>
{
    private readonly SingleItemEnumerator<T> enumerator;

    public SingleItemWrapper(T item)
    {
        this.enumerator = new SingleItemEnumerator<T>(item);
    }

    public T Item => this.enumerator.Current;

    public IEnumerator<T> GetEnumerator() => this.enumerator;

    IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator();
}

Que j'ai utilisé comme ça 

TreeView.ItemSource = new SingleItemWrapper(itemToWrap);

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

0
IDarkCoder
Enumerable.Range(1,1).Select(_, item); 
0
frhack