Perché implementare IEnumerable (T) se posso solo definire uno GetEnumerator?
-
03-10-2019 - |
Domanda
Aggiorna : Apprezzo tutti i commenti, che sono in sostanza comprese unanime opposizione. Mentre ogni obiezione era valido, ritengo che il chiodo finale nella bara era condizioni astuta di Ani che, in ultima analisi, anche il una minuscolo beneficio che questa idea apparentemente offerto - l'eliminazione del boilerplate codice -. è stato negato dal fatto che l'idea stessa richiederebbe la sua proprio codice standard
Quindi sì, in considerazione mi ha convinto:. Sarebbe una cattiva idea
E proprio al tipo di salvataggio mia dignità un po ': forse avrei giocato per amor di discussione, ma non sono mai stato realmente venduti su questa idea per cominciare - semplicemente curioso di sentire ciò che gli altri hanno da dire in proposito. Onesto.
Prima di respingere questa domanda assurda, vi chiedo di prendere in considerazione quanto segue:
- eredita
IEnumerable<T>
da *IEnumerable
, che significa che qualsiasi tipo che implementaIEnumerable<T>
generalmente deve implementare siaIEnumerable<T>.GetEnumerator
e (esplicitamente)IEnumerable.GetEnumerator
. Ciò equivale in sostanza a codice standard. - È possibile
foreach
su qualsiasi tipo che ha un metodoGetEnumerator
, fintanto che il metodo restituisce un oggetto di un certo tipo con un metodo e una proprietàMoveNext
Current
. Quindi, se il tipo definisce una con il metodopublic IEnumerator<T> GetEnumerator()
firma, è legale enumerare su di esso utilizzandoforeach
. - Chiaramente, c'è un sacco di codice là fuori che richiede l'interfaccia
IEnumerable<T>
- per esempio, in pratica tutti i metodi di estensione LINQ. Per fortuna, per passare da un tipo che si puòforeach
a unIEnumerable<T>
è banale mediante la generazione automatica iteratore che C # forniture tramite la parola chiaveyield
.
Quindi, mettendo insieme tutto questo, ho avuto questa pazza idea: e se ho appena definisco la mia propria interfaccia che assomiglia a questo:
public interface IForEachable<T>
{
IEnumerator<T> GetEnumerator();
}
Poi ogni volta che mi definisco un tipo che voglio essere enumerabile, implemento questo interfaccia invece di IEnumerable<T>
, eliminando la necessità di implementare due metodi GetEnumerator
(uno esplicito) . Ad esempio:
class NaturalNumbers : IForEachable<int>
{
public IEnumerator<int> GetEnumerator()
{
int i = 1;
while (i < int.MaxValue)
{
yield return (i++);
}
}
// Notice how I don't have to define a method like
// IEnumerator IEnumerable.GetEnumerator().
}
Infine , al fine di rendere questo tipo compatibile con il codice che fa si aspettano l'interfaccia IEnumerable<T>
, posso solo definire un metodo di estensione per andare da qualsiasi IForEachable<T>
a un IEnumerable<T>
in questo modo:
public static class ForEachableExtensions
{
public static IEnumerable<T> AsEnumerable<T>(this IForEachable<T> source)
{
foreach (T item in source)
{
yield return item;
}
}
}
Mi sembra che facendo questo mi permette di progettare i tipi che sono utilizzabili in ogni modo come implementazioni di IEnumerable<T>
, ma senza che l'attuazione IEnumerable.GetEnumerator
esplicita fastidiosi in ciascuno di essi.
Ad esempio:
var numbers = new NaturalNumbers();
// I can foreach myself...
foreach (int x in numbers)
{
if (x > 100)
break;
if (x % 2 != 0)
continue;
Console.WriteLine(x);
}
// Or I can treat this object as an IEnumerable<T> implementation
// if I want to...
var evenNumbers = from x in numbers.AsEnumerable()
where x % 2 == 0
select x;
foreach (int x in evenNumbers.TakeWhile(i => i <= 100))
{
Console.WriteLine(x);
}
Che ne pensate voi ragazzi di questa idea? Mi sto perdendo qualche motivo per cui questo sarebbe un errore?
Mi rendo conto che probabilmente sembra una soluzione troppo complessa per ciò che non quello grande di un accordo per iniziare è (dubito chiunque davvero cure che molto di dover definire esplicitamente l'interfaccia IEnumerable
) ; ma appena spuntato nella mia testa e non sto vedendo alcun problema evidente che questo approccio porrebbe.
In generale, se posso scrivere una moderata quantità di codice una volta ??em> per salvare me stesso la fatica di dover scrivere una piccola quantità di codice un sacco di volte , per me , ne vale la pena.
* è che la terminologia diritto di utilizzo? Sono sempre riluttanti a dire un'interfaccia "eredita da" un altro, in quanto questo non sembra di cogliere correttamente il rapporto tra di loro. Ma forse è proprio su.
Soluzione
Non sei solo spostando il testo standard da qualche altra parte - dalla scrittura della IEnumerable.GetEnumerator
method su ogni classe per chiamare il proprio interno AsEnumerable
ogni volta che si prevede un IEnumerable<T>
? In genere, mi sarei aspettato un tipo enumerabile da utilizzare per l'interrogazione di gran lunga più volte di quanto è scritto (che è esattamente una volta). Ciò significa che questo modello porterà a più boilerplate, in media.
Altri suggerimenti
Ti manca una cosa enorme -
Se si implementa la propria interfaccia, invece di IEnumerable<T>
, la classe non funzionerà con i metodi quadro aspettano IEnumerable<T>
- principalmente, si sarà completamente in grado di utilizzare LINQ, o utilizzare la classe per costruire un List<T>
, o di molte altre astrazioni utili .
È possibile raggiungere questo obiettivo, come si parla, attraverso un metodo di estensione separata - tuttavia, questo ha un costo. Utilizzando un metodo di estensione per convertire a un IEnumerable<T>
, si aggiunge un ulteriore livello di astrazione necessario per utilizzare la classe (che si farà molto più spesso di quanto la creazione della classe), e ridurre le prestazioni (il vostro metodo di estensione sarà , in effetti, generare una nuova implementazione della classe internamente, che è davvero inutile). Ancora più importante, qualsiasi altro utente della classe (o più tardi) dovrà imparare una nuova API che compie nulla - si sta facendo la classe più difficile da usare, non utilizzando interfacce standard, in quanto viola le aspettative degli utenti
Hai ragione:. It non sembra una soluzione troppo complessa per un problema abbastanza facile
Si introduce inoltre un ulteriore livello di indirezione per ogni passo dell'iterazione. Probabilmente , non un problema di prestazioni, ma ancora un po 'inutile, quando non credo che siete veramente guadagnando nulla di molto significativo.
Inoltre, anche se il vostro metodo di estensione consente di convertire qualsiasi IForEachable<T>
in un IEnumerable<T>
, significa che il tipo stesso non sarà di soddisfare un vincolo generico come questo:
public void Foo<T>(T collection) where T : IEnumerable
o simili. In sostanza, dal dover effettuare la vostra conversione, si sta perdendo la capacità di trattare un singolo oggetto come sia un'implementazione IEnumerable<T>
e il vero tipo concreto.
Inoltre, non attuando IEnumerable
, si sta contando te di inizializzatori raccolta. (A volte mi attuare IEnumerable
esplicitamente solo di optare per l'inizializzazione di raccolta, un'eccezione da GetEnumerator()
.)
Oh, e hai anche introdotto un pizzico di infrastrutture che è sconosciuto a tutti gli altri nel mondo, rispetto alle vaste orde di C # sviluppatori che già conoscono IEnumerable<T>
.
Nel nostro codice di base c'è probabilmente più di 100 istanze di questo frammento esatto:
IEnumerator IEnumerable.GetEnumerator()
{
return this.GetEnumerator();
}
E sono davvero d'accordo con questo. E 'un piccolo prezzo da pagare per la piena compatibilità con ogni altro pezzo di codice .NET mai scritto;)
La soluzione proposta richiede essenzialmente ogni consumatore / visitatore della vostra nuova interfaccia per ricordare anche per chiamare un metodo di estensione speciale su di esso prima che sia utile .
E 'molto più facile da usare IEnumerable
di riflessione. Cercando di richiamare interfacce generiche attraverso la riflessione è tale dolore a. La pena della boxe attraverso IEnumerable
si perde per il sovraccarico di riflessione per sé quindi perché preoccuparsi utilizzando un'interfaccia generica? Per quanto riguarda, ad esempio, la serializzazione viene in mente.
Credo che ognuno di noi ha già menzionato le ragioni tecniche per non fare questo, quindi mi aggiungi a nel mix: che richiede l'utente a chiamare AsEnumerable () sulla vostra collezione di essere in grado di utilizzare le estensioni enumerabili violerebbe il principio di minima sorpresa.