it-swarm.com.de

Einzelnes Element als IEnumerable <T> übergeben

Gibt es eine übliche Methode, ein einzelnes Element des Typs T an eine Methode zu übergeben, die einen IEnumerable<T>-Parameter erwartet? Sprache ist C #, Framework-Version 2.0.

Derzeit verwende ich eine Hilfsmethode (es ist .Net 2.0, also habe ich eine ganze Reihe von Casting-/Projektions-Hilfsmethoden, die LINQ ähneln), aber dies scheint einfach dumm zu sein:

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

Eine andere Möglichkeit wäre natürlich, einen List<T> oder eine Array zu erstellen und auszufüllen und ihn statt IEnumerable<T> zu übergeben.

[Edit] Als Erweiterungsmethode kann es benannt werden:

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

Fehlt mir hier etwas?

[Edit2] Wir haben festgestellt, dass someObject.Yield() (wie @Peter in den Kommentaren unten vorgeschlagen hat) der beste Name für diese Erweiterungsmethode ist, hauptsächlich aus Gründen der Kürze. Daher ist hier der XML-Kommentar enthalten, wenn jemand sie ergreifen möchte:

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

Ihre Hilfsmethode ist die sauberste Methode, IMO. Wenn Sie eine Liste oder ein Array übergeben, kann dies durch einen skrupellosen Code umgewandelt und der Inhalt geändert werden, was in einigen Situationen zu ungewöhnlichem Verhalten führen kann. Sie können eine schreibgeschützte Auflistung verwenden, aber dies erfordert wahrscheinlich noch mehr Umbruch. Ich denke, Ihre Lösung ist so ordentlich wie es nur geht.

84
Jon Skeet

Wenn die Methode eine IEnumerable erwartet, müssen Sie etwas übergeben, das eine Liste ist, auch wenn sie nur ein Element enthält.

vorbeigehen 

new T[] { item }

als Argument sollte es reichen, denke ich

110
Mario F

In C # 3.0 können Sie die Klasse System.Linq.Enumerable verwenden:

// using System.Linq

Enumerable.Repeat(item, 1);

Dadurch wird ein neues IEnumerable erstellt, das nur Ihren Artikel enthält.

103
luksan

In C # 3 (ich weiß, dass Sie 2 sagten), können Sie eine generische Erweiterungsmethode schreiben, die die Syntax ein wenig akzeptabler macht:

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

der Client-Code lautet dann item.ToEnumerable().

26
Ðаn

Diese Hilfsmethode funktioniert für Artikel oder viele.

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

Ich bin überrascht, dass niemand eine neue Überladung der Methode mit einem Argument vom Typ T vorgeschlagen hat, um die Client-API zu vereinfachen.

public void DoSomething<T>(IEnumerable<T> list)
{
    // Do Something
}

public void DoSomething<T>(T item)
{
    DoSomething(new T[] { item });
}

Jetzt kann Ihr Client-Code dies einfach tun:

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

oder mit einer Liste:

List<MyItem> itemList = new List<MyItem>();
Obj.DoSomething(itemList);
9
Joshua Starner

Wie ich soeben herausgefunden habe und gesehen habe, dass der Benutzer LukeH auch vorgeschlagen hat, kann man dies auf folgende einfache Weise tun:

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
    }
}

Mit diesem Muster können Sie dieselbe Funktionalität auf verschiedene Weise aufrufen: ein einzelnes Element; mehrere Elemente (durch Kommas getrennt); eine Anordnung; eine Liste; eine Aufzählung usw.

Ich bin zwar nicht zu 100% sicher, wie effizient die AsEnumerable-Methode ist, aber sie funktioniert sehr gut.

Update: Die AsEnumerable-Funktion sieht ziemlich effizient aus! ( Referenz )

7
teppicymon

Obwohl es für eine Methode ein Overkill ist, glaube ich, dass einige Benutzer die interaktiven Erweiterungen als nützlich erachten.

Die Interactive Extensions (Ix) von Microsoft umfasst die folgende Methode.

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

Was kann so genutzt werden:

var result = EnumerableEx.Return(0);

Ix fügt neue Funktionen hinzu, die in den ursprünglichen Linq-Erweiterungsmethoden nicht enthalten sind, und ist ein direktes Ergebnis der Erstellung der Reactive Extensions (Rx).

Denken Sie, Linq Extension Methods + Ix = Rx für IEnumerable.

Sie finden sowohl Rx als auch Ix in CodePlex .

6
cwharris

Entweder (wie zuvor gesagt)

MyMethodThatExpectsAnIEnumerable(new[] { myObject });

oder

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

Als Randbemerkung kann die letzte Version auch Nice sein, wenn Sie eine leere Liste eines anonymen Objekts, z.

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

Dies ist um 30% schneller als yield oder Enumerable.Repeat, wenn es in foreach verwendet wird, weil diese C # -Compileroptimierung und in anderen Fällen dieselbe Leistung aufweist.

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

in diesem 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.

Ich stimme den Kommentaren von @ EarthEngine zu dem ursprünglichen Beitrag zu, der besagt, dass 'AsSingleton' ein besserer Name ist. Siehe diesen Wikipedia-Eintrag . Aus der Definition von Singleton folgt dann, dass, wenn ein Nullwert als Argument übergeben wird, 'AsSingleton' ein IEnumerable mit einem einzelnen Nullwert anstelle eines leeren IEnumerable zurückgeben sollte, das die if (item == null) yield break;-Debatte regelt. Ich denke, die beste Lösung ist, zwei Methoden zu haben: 'AsSingleton' und 'AsSingletonOrEmpty'; In dem Fall, dass eine Null als Argument übergeben wird, gibt 'AsSingleton' einen einzelnen Nullwert zurück, und 'AsSingletonOrEmpty' gibt ein leeres IEnumerable zurück. So was:

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

Dann wären diese mehr oder weniger analog zu den Erweiterungsmethoden 'First' und 'FirstOrDefault' auf IEnumerable, was sich gerade richtig anfühlt.

4
Jason Boyd

Das ist vielleicht nicht besser, aber irgendwie cool:

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

IanG hat einen guten Beitrag zum Thema und schlägt EnumerableFrom() als Namen vor und erwähnt, dass die Diskussion darauf hinweist, dass Haskell und Rx es Return nennen. IIRC F # ruft es auch Return auf. F # 's Seq RUFT DEN OPERATOR singleton<'T> auf.

Verlockend, wenn Sie bereit sind, C # -zentrisch zu sein, heißt es Yield [und verweist auf den an der Realisierung beteiligten yield return].

Wenn Sie an den perfekten Aspekten interessiert sind, hat James Michael Hare ein , das null oder einen Artikel zurückgibt , was ebenfalls einen Scan wert ist.

2
Ruben Bartelink

Der einfachste Weg, den ich sagen würde, wäre new T[]{item};; Es gibt keine Syntax dafür. Das nächstliegende Äquivalent, das ich mir vorstellen kann, ist das Schlüsselwort params. Natürlich müssen Sie jedoch auf die Methodendefinition zugreifen können und können nur mit Arrays verwendet werden.

1
FacticiusVir

Ich bin ein bisschen zu spät zur Party, aber ich teile es mir trotzdem mit ... __ Mein Problem war, dass ich die ItemSource oder eine WPF-TreeView an ein einzelnes Objekt binden wollte. Die Hierarchie sieht folgendermaßen aus:

Projekt> Plot (s)> Raum (e)

Es würde immer nur ein Projekt geben, aber ich wollte das Projekt trotzdem in der Baumstruktur anzeigen, ohne eine Sammlung mit nur diesem einen Objekt übergeben zu müssen, wie es von einigen vorgeschlagen wurde.
Da Sie nur IEnumerable-Objekte als ItemSource übergeben können, habe ich mich entschlossen, meine Klasse IEnumerable zu machen:

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

    ... 

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

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

Und entsprechend meinen eigenen Enumerator erstellen:

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

Dies ist wahrscheinlich nicht die "sauberste" Lösung, aber es hat für mich funktioniert.

EDIT
Um das Single-Responsibility-Prinzip aufrechtzuerhalten, wie @Groo hervorgehoben hat, habe ich eine neue Wrapper-Klasse erstellt:

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

Was ich so benutzt habe 

TreeView.ItemSource = new SingleItemWrapper(itemToWrap);

EDIT 2
Ich habe einen Fehler mit der MoveNext()-Methode korrigiert.

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