Passando un singolo elemento come IEnumerable <T>


377

Esiste un modo comune per passare un singolo elemento di tipo Ta un metodo che prevede un IEnumerable<T>parametro? La lingua è C #, framework versione 2.0.

Attualmente sto usando un metodo helper (è .Net 2.0, quindi ho un sacco di metodi helper di casting / proiezione simili a LINQ), ma questo sembra sciocco:

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

Un altro modo sarebbe ovviamente quello di creare e popolare un List<T>o un Arraye passarlo invece di IEnumerable<T>.

[Modifica] Come metodo di estensione potrebbe essere chiamato:

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

Mi sto perdendo qualcosa qui?

[Modifica2] Abbiamo trovato someObject.Yield()(come suggerito da @Peter nei commenti seguenti) il nome migliore per questo metodo di estensione, principalmente per brevità, quindi eccolo insieme al commento XML se qualcuno vuole afferrarlo:

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

6
Vorrei apportare una leggera modifica nel corpo del metodo di estensione: if (item == null) yield break; ora ti viene impedito di passare a null e di sfruttare il modello (banale) di oggetti null per IEnumerable. ( foreach (var x in xs)gestisce un vuoto xsbenissimo). Per inciso, questa funzione è l'unità monadica della lista monade che è IEnumerable<T>, e data la festa d'amore della monade alla Microsoft, sono sorpreso che qualcosa del genere non sia nel framework in primo luogo.
Matt Enright,

2
Per il metodo di estensione, non dovresti nominarlo AsEnumerableperché esiste già un'estensione integrata con quel nome . (Quando Timplementa IEnumerable, ad es string.)
Jon-Eric

22
Che ne dici di nominare il metodo Yield? Niente batte la brevità.
Philip Guin,

4
Suggerimenti di denominazione qui. "SingleItemAsEnumerable" un po 'dettagliato. "Rendimento" descrive l'implementazione piuttosto che l'interfaccia, il che non va bene. Per un nome migliore, suggerisco "AsSingleton", che corrisponde al significato esatto del comportamento.
Earth Engine

4
Odio il left==nullcontrollo qui. Rompe il beauti del codice e impedisce che il codice sia più flessibile - cosa succede se un giorno si scopre la necessità di generare un singleton con qualcosa che può essere nullo? Voglio dire, new T[] { null }non è la stessa cosa new T[] {}, e un giorno potresti aver bisogno di distinguerli.
Earth Engine

Risposte:


100

Il tuo metodo di supporto è il modo più pulito per farlo, IMO. Se passi in un elenco o in un array, un pezzo di codice senza scrupoli potrebbe lanciarlo e modificare i contenuti, portando a comportamenti strani in alcune situazioni. È possibile utilizzare una raccolta di sola lettura, ma è probabile che comporti ancora più wrapping. Penso che la tua soluzione sia accurata.


bene se la lista / matrice è costruita ad-hoc, il suo ambito termina dopo la chiamata del metodo, quindi non dovrebbe causare problemi
Mario F

Se inviato come array, come può essere modificato? Immagino che potrebbe essere lanciato su un array, e quindi cambiare il riferimento a qualcos'altro, ma a che cosa servirebbe? (Probabilmente mi manca qualcosa però ...)
Svish,

2
Supponiamo che tu abbia deciso di crearne uno enumerabile per passare a due metodi diversi ... quindi il primo lo ha castato su un array e ha cambiato il contenuto. Lo si passa quindi come argomento a un altro metodo, ignaro del cambiamento. Non sto dicendo che è probabile, solo che avere qualcosa di mutevole quando non è necessario sia meno pulito dell'immutabilità di Naturla.
Jon Skeet,

3
Questa è una risposta accettata e molto probabilmente da leggere, quindi aggiungerò la mia preoccupazione qui. Ho provato questo metodo, ma questo ha interrotto la mia chiamata precedentemente compilata a myDict.Keys.AsEnumerable()dove myDictera di tipo Dictionary<CheckBox, Tuple<int, int>>. Dopo aver rinominato il metodo di estensione in SingleItemAsEnumerable, tutto ha iniziato a funzionare. Impossibile convertire IEnumerable <Dizionario <CheckBox, System.Tuple <int, int >>. KeyCollection> 'in' IEnumerable <CheckBox> '. Esiste una conversione esplicita (ti manca un cast?) Posso vivere con un hack per un po ', ma altri power hacker possono trovare un modo migliore?
Hamish Grubijan,

3
@HamishGrubijan: Sembra che tu volessi dictionary.Valuesiniziare. Fondamentalmente non è chiaro cosa stia succedendo, ma ti suggerisco di iniziare una nuova domanda al riguardo.
Jon Skeet,

133

Bene, se il metodo prevede IEnumerableche devi passare qualcosa che è un elenco, anche se contiene solo un elemento.

passaggio

new[] { item }

poiché l'argomento dovrebbe essere sufficiente, penso


32
Penso che potresti anche far cadere la T, poiché sarà dedotta (a meno che non mi sbagli di
grosso

2
Penso che non sia dedotto in C # 2.0.
Groo,

4
"Language is C #, framework version 2" Il compilatore C # 3 può dedurre T, e il codice sarà pienamente compatibile con .NET 2.
sisve

la migliore risposta è in fondo ?!
Falco Alexander,

2
@Svish Ho suggerito e modificato la risposta per fare proprio questo - siamo nel 2020 ora, quindi dovrebbe essere l'imho "standard"
OschtärEi

120

In C # 3.0 è possibile utilizzare la classe System.Linq.Enumerable:

// using System.Linq

Enumerable.Repeat(item, 1);

Questo creerà un nuovo IEnumerable che contiene solo il tuo articolo.


26
Penso che questa soluzione renderà il codice più difficile da leggere. :( Ripetere su un singolo elemento è abbastanza controintuitivo, non credi?
Drahakar,

6
Ripeti una volta mi sembra ok. Soluzione molto più semplice senza doversi preoccupare di aggiungere un altro metodo di estensione e garantire che lo spazio dei nomi sia incluso ovunque si desideri utilizzarlo.
si618

13
Sì, in realtà uso solo new[]{ item }me stesso, ho pensato che fosse una soluzione interessante.
luksan,

7
Questa soluzione è denaro.
ProVega,

IMHO questa dovrebbe essere la risposta accettata. È facile da leggere, conciso ed è implementato in una sola riga sul BCL, senza un metodo di estensione personalizzato.
Ryan

35

In C # 3 (so che hai detto 2), puoi scrivere un metodo di estensione generico che potrebbe rendere la sintassi un po 'più accettabile:

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

il codice client è quindi item.ToEnumerable().


Grazie, ne sono consapevole (funziona in .Net 2.0 se uso C # 3.0), mi stavo solo chiedendo se esistesse un meccanismo incorporato per questo.
Groo,

12
Questa è davvero una bella alternativa. Voglio solo suggerire di rinominare per ToEnumerableevitare di sbagliareSystem.Linq.Enumerable.AsEnumerable
Fede,

3
Come nota a margine, esiste già un ToEnumerablemetodo di estensione per IObservable<T>, quindi questo nome di metodo interferisce anche con le convenzioni esistenti. Onestamente, suggerirei di non usare affatto un metodo di estensione, semplicemente perché è troppo generico.
cwharris,

14

Questo metodo di supporto funziona per molti articoli.

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

1
Variazione utile, poiché supporta più di un elemento. Mi piace che paramsfaccia affidamento per costruire l'array, quindi il codice risultante è pulito. Non ho deciso se mi piace più o meno rispetto new T[]{ item1, item2, item3 };a più articoli.
ToolmakerSteve

Inteligente ! Lo
adoro

10

Sono un po 'sorpreso che nessuno abbia suggerito un nuovo sovraccarico del metodo con un argomento di tipo T per semplificare l'API client.

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

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

Ora il tuo codice client può fare solo questo:

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

o con un elenco:

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

19
Ancora meglio, potresti avere il DoSomething<T>(params T[] items)che significa che il compilatore gestirà la conversione da un singolo elemento a un array. (Ciò consentirebbe anche di passare in più elementi separati e, di nuovo, il compilatore gestirà la conversione in un array per te.)
LukeH

7

O (come è stato precedentemente detto)

MyMethodThatExpectsAnIEnumerable(new[] { myObject });

o

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

Come nota a margine, l'ultima versione può anche essere utile se si desidera un elenco vuoto di un oggetto anonimo, ad es

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

Grazie, sebbene Enumerable.Repeat sia nuovo in .Net 3.5. Sembra comportarsi in modo simile al metodo di supporto sopra.
Groo,

7

Come ho appena scoperto, e visto anche l'utente suggerito da LukeH, un modo semplice per farlo è il seguente:

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

Questo modello ti permetterà di chiamare la stessa funzionalità in una moltitudine di modi: un singolo oggetto; più elementi (separati da virgola); un array; una lista; un'enumerazione, ecc.

Non sono sicuro al 100% sull'efficienza dell'utilizzo del metodo AsEnumerable, ma funziona a meraviglia.

Aggiornamento: la funzione AsEnumerable sembra abbastanza efficiente! ( riferimento )


In realtà, non è necessario .AsEnumerable()affatto. La matrice YourType[]implementa già IEnumerable<YourType>. Ma la mia domanda si riferiva al caso in cui è disponibile solo il secondo metodo (nel tuo esempio) e stai usando .NET 2.0 e vuoi passare un singolo elemento.
Groo

2
Sì, sì, ma scoprirai che potresti avere un overflow dello stack ;-)
teppicymon,

E per rispondere effettivamente alla tua vera domanda (non quella a cui stavo davvero cercando di rispondere da solo!), Sì, devi praticamente convertirlo in un array secondo la risposta di Mario
teppicymon,

2
Che ne dici di definire un IEnumerable<T> MakeEnumerable<T>(params T[] items) {return items;}metodo? Si potrebbe quindi usarlo con qualsiasi cosa ci si aspettasse IEnumerable<T>, per un numero qualsiasi di elementi discreti. Il codice dovrebbe essere essenzialmente efficiente quanto la definizione di una classe speciale per restituire un singolo elemento.
supercat,

6

Sebbene sia eccessivo per un metodo, credo che alcune persone possano trovare utili le estensioni interattive.

Interactive Extensions (Ix) di Microsoft include il seguente metodo.

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

Che può essere utilizzato in questo modo:

var result = EnumerableEx.Return(0);

Ix aggiunge nuove funzionalità non presenti nei metodi di estensione Linq originali ed è il risultato diretto della creazione delle estensioni reattive (Rx).

Pensa, Linq Extension Methods+ Ix= Rxper IEnumerable.

Puoi trovare sia Rx che Ix su CodePlex .


5

È più veloce del 30% rispetto yieldo Enumerable.Repeatquando utilizzato a foreachcausa dell'ottimizzazione del compilatore C # e delle stesse prestazioni in altri casi.

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

Grazie, ha senso creare una struttura per questo caso per ridurre l'onere del GC nei circuiti stretti.
Groo

1
Sia l'enumeratore che l'enumerabile verranno inscatolati quando restituiti ....
Stephen Drew,

2
Non confrontare utilizzando il cronometro. Usa Benchmark.NET github.com/dotnet/BenchmarkDotNet
NN_

1
L'ho appena misurato e l' new [] { i }opzione è circa 3,2 volte più veloce dell'opzione. Inoltre la tua opzione è circa 1,2 volte più veloce dell'estensione con yield return.
lorond

Puoi accelerarlo usando un'altra ottimizzazione del compilatore C #: aggiungi un metodo SingleEnumerator GetEnumerator()che restituisca la tua struttura. Il compilatore C # utilizzerà questo metodo (lo cerca tramite la digitazione duck) invece di quelli dell'interfaccia e quindi evita il box. Molte raccolte integrate utilizzano questo trucco, come Elenco . Nota: questo funzionerà solo quando hai un riferimento diretto SingleSequence, come nel tuo esempio. Se lo memorizzi in una IEnumerable<>variabile, il trucco non funzionerà (verrà utilizzato il metodo dell'interfaccia)
enzi


4

Questo potrebbe non essere migliore, ma è abbastanza bello:

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

Non è. È diverso.
nawfal,

Ewww. Questo è un sacco di dettagli, solo per trasformare un oggetto in un enumerabile.
ToolmakerSteve

1
Questo è l'equivalente dell'uso di una macchina Rube Goldberg per fare colazione. Sì, funziona, ma salta attraverso 10 cerchi per arrivarci. Una cosa semplice come questa può essere il collo di bottiglia delle prestazioni quando eseguito in un ciclo stretto eseguito milioni di volte. In pratica, l'aspetto delle prestazioni non ha importanza nel 99% dei casi, ma personalmente continuo a pensare che si tratti di un eccessivo eccesso eccessivo.
Enzi,

4

Sono d'accordo con i commenti di @ EarthEngine al post originale, che è che "AsSingleton" è un nome migliore. Vedi questa voce di Wikipedia . Quindi dalla definizione di singleton deriva che se un valore null viene passato come argomento, "AsSingleton" dovrebbe restituire un IEnumerable con un singolo valore null invece di un IEnumerable vuoto che risolverebbe il if (item == null) yield break;dibattito. Penso che la soluzione migliore sia avere due metodi: 'AsSingleton' e 'AsSingletonOrEmpty'; dove, nel caso in cui un null venga passato come argomento, "AsSingleton" restituirà un singolo valore null e "AsSingletonOrEmpty" restituirà un IEnumerable vuoto. Come questo:

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

Quindi, questi, più o meno, sarebbero analoghi ai metodi di estensione "First" e "FirstOrDefault" su IEnumerable che sembra giusto.


2

Il modo più semplice che direi sarebbe new T[]{item};; non c'è sintassi per farlo. L'equivalente più vicino a cui riesco a pensare è la paramsparola chiave, ma ovviamente ciò richiede che tu abbia accesso alla definizione del metodo ed è utilizzabile solo con le matrici.


2
Enumerable.Range(1,1).Select(_ => {
    //Do some stuff... side effects...
    return item;
});

Il codice sopra è utile quando si utilizza like

var existingOrNewObject = MyData.Where(myCondition)
       .Concat(Enumerable.Range(1,1).Select(_ => {
           //Create my object...
           return item;
       })).Take(1).First();

Nello snippet di codice sopra riportato non è presente alcun controllo vuoto / null ed è garantito che venga restituito un solo oggetto senza timore di eccezioni. Inoltre, poiché è pigro, la chiusura non verrà eseguita fino a quando non verrà dimostrato che non esistono dati adeguati ai criteri.


Questa risposta è stata automaticamente taggata "bassa qualità". Come spiegato nel aiuto ( "La brevità è accettabile, ma le spiegazioni più piene sono meglio."), Si prega di modificare a dire il PO quello che sta facendo male, che cosa il vostro codice è circa.
Sandra Rossi,

Vedo che la maggior parte di questa risposta, inclusa la parte a cui sto rispondendo, è stata modificata in seguito da qualcun altro. ma se l'intento del secondo frammento è quello di fornire un valore predefinito se MyData.Where(myCondition)è vuota, questa è già possibile (e più semplice) con DefaultIfEmpty(): var existingOrNewObject = MyData.Where(myCondition).DefaultIfEmpty(defaultValue).First();. Ciò può essere ulteriormente semplificatovar existingOrNewObject = MyData.FirstOrDefault(myCondition); se si desidera default(T)e non un valore personalizzato.
BACON

0

Sono un po 'in ritardo alla festa ma condividerò comunque la mia strada. Il mio problema era che volevo associare ItemSource o un TreeView WPF a un singolo oggetto. La gerarchia si presenta così:

Progetto> Trama (i)> Stanza (e)

Ci sarebbe sempre stato un solo progetto, ma volevo comunque mostrare il progetto nella struttura ad albero, senza dover passare una raccolta contenente solo quell'oggetto come suggerito.
Dato che puoi solo passare oggetti IEnumerable come ItemSource, ho deciso di rendere la mia classe IEnumerable:

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

    ... 

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

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

E crea il mio enumeratore di conseguenza:

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

Questa probabilmente non è la soluzione "più pulita" ma ha funzionato per me.

EDIT
Per sostenere il principio della responsabilità singola, come sottolineato da @Groo, ho creato una nuova classe 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();
}

Che ho usato così

TreeView.ItemSource = new SingleItemWrapper(itemToWrap);

EDIT 2
Ho corretto un errore con il MoveNext()metodo.


La SingleItemEnumerator<T>classe ha senso, ma fare di una classe un "singolo elemento IEnumerabledi se stesso" sembra una violazione del principio della responsabilità singola. Forse rende più pratico passarlo in giro, ma preferirei comunque avvolgerlo secondo necessità.
Groo,

0

Per essere archiviato in "Non necessariamente una buona soluzione, ma comunque ... una soluzione" o "Stupidi trucchi LINQ", è possibile combinare Enumerable.Empty<>()con Enumerable.Append<>()...

IEnumerable<string> singleElementEnumerable = Enumerable.Empty<string>().Append("Hello, World!");

... o Enumerable.Prepend<>()...

IEnumerable<string> singleElementEnumerable = Enumerable.Empty<string>().Prepend("Hello, World!");

Questi ultimi due metodi sono disponibili da .NET Framework 4.7.1 e .NET Core 1.0.

Questa è una soluzione praticabile se si fosse davvero intenzionati a usare metodi esistenti invece di scriverne uno proprio, anche se sono indeciso se questo è più o meno chiaro della Enumerable.Repeat<>()soluzione . Questo codice è sicuramente più lungo (in parte a causa della deduzione dei parametri di tipo per cui non è possibile Empty<>()) e crea comunque il doppio degli oggetti enumeratore.

A completare questo "Sapevi che esistono questi metodi?" risposta, Array.Empty<>()potrebbe essere sostituito Enumerable.Empty<>(), ma è difficile discutere che renda la situazione ... migliore.


Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.