2009-11-05 6 views
5

Sto facendo del mio meglio per codificare le interfacce quando possibile, ma sto riscontrando alcuni problemi quando si tratta di raccolte. Ad esempio, ecco un paio di interfacce che vorrei usare.L'implementazione esplicita di GetEnumerator dell'interfaccia causa l'overflow dello stack

public interface IThing {} 

public interface IThings : IEnumerable<IThing> {} 

Ecco le implementazioni. Per implementare IEnumerable <IThing>, è necessario implementare esplicitamente IEnumerable <IThing> .GetEnumerator() in Oggetti.

public class Thing : IThing {} 

public class Things : List<Thing>, IThings 
{ 
    IEnumerator<IThing> IEnumerable<IThing>.GetEnumerator() 
    { 
     // This calls itself over and over 
     return this.Cast<IThing>().GetEnumerator(); 
    } 
} 

Il problema è che l'implementazione di GetEnumerator causa uno stack overflow. Si chiama più e più volte. Non riesco a capire perché avrebbe deciso di chiamare quell'implementazione di GetEnumerator invece dell'implementazione fornita dal risultato di questo.Cast <IThing>(). Qualche idea su cosa sto facendo male? Sono pronto a scommettere che sia qualcosa di estremamente stupido ...

Ecco qualche semplice codice di prova per le classi di cui sopra:

static void Enumerate(IThings things) 
{ 
    foreach (IThing thing in things) 
    { 
     Console.WriteLine("You'll never get here."); 
    } 
} 

static void Main() 
{ 
    Things things = new Things(); 
    things.Add(new Thing()); 

    Enumerate(things); 
} 

risposta

2

Utilizzare questo invece:

public class Things : List<Thing>, IThings 
    { 
     IEnumerator<IThing> IEnumerable<IThing>.GetEnumerator() 
     { 
      foreach (Thing t in this) 
      { 
       yield return t; 
      } 
     } 
    } 

Oppure si potrebbe lavorare con contenimento, invece.

+0

Oooh, buona scelta. Questo rende ovvio che base.GetEnumerator() dovrebbe funzionare, se comprendeva covarianza e contravaraince come diceva Frank. Ma dal momento che non lo fa, questo fa abbastanza bene il trucco! – Joe

+0

sì, C# 4 sarà fantastico:^p inoltre, sono rimasto sorpreso che "((Lista ) questo). Rapido () .GetEnumerator();" non ha funzionato neanche: s – Stormenet

+0

Dopo aver guardato Reflector, è ovvio perché Cast non funziona. Fondamentalmente, lancia solo ciascuno degli elementi individualmente, se necessario. In questo caso vede che "questo" è un oggetto IEnumerable , quindi restituisce solo questo. È troppo intelligente per il suo bene. :) – Joe

1
IEnumerator<IThing> IEnumerable<IThing>.GetEnumerator() 
{ 
    // This calls itself over and over 
    return this.Cast<IThing>().GetEnumerator(); 
} 

E 'una chiamata ricorsiva senza condizione di interruzione, si sta andando per ottenere uno stack overflow, perché sta per riempire molto velocemente lo stack.

Stai eseguendo il casting su un'interfaccia, che non fa ciò che pensi che faccia. Alla fine stai chiamando this.GetEnumerator(); qual è la funzione in cui ti trovi già, quindi ricorsero. Forse intendi base.GetEnumerator();

+0

base.GetEnumerator() non farà il trucco, perché restituisce un IEnumerator , non un IEnumerator . – Joe

0

Sembra abbastanza ovvio che this.Cast<IThing>() restituisce un oggetto IEnumerable. O almeno lo fa senza l'implementazione di Cast.

+0

Sì, credo sia ovvio. Non sono sicuro di come possa essere d'aiuto. Devo restituire un IEnumerator , non un IEnumerable. – Joe

+0

Indovina che non ero chiaro ... Non hai fornito l'implementazione di Cast, quindi non posso dirti al 100% quale sia il problema, ma posso quasi garantirti che Cast restituisce un 'IEnumerable ' (altrimenti fa qualcosa che provoca l'overflow dello stack). La tua esplicita implementazione dell'interfaccia significa che il metodo verrà chiamato ogni volta che 'this' viene lanciato come' IEnumerable '. Pertanto, se la chiamata al metodo risulta in un overflow dello stack, Cast deve trasmettere l'istanza corrente all'interfaccia IE, generando un ciclo infinito. – Will

1

In genere, è probabile che si desideri disaccoppiare l'implementazione della raccolta Things dall'implementazione dell'oggetto Thing. La classe Things è in grado di funzionare con qualsiasi implementazione di IThing, non solo la classe Thing.

In particolare:

public class Things : List<IThing>, IThings 
{ 
} 

In questo caso, non c'è bisogno di sostituire l'implementazione predefinita di GetEnumerator(), l'implementazione di base è già digitato correttamente per voi. Ciò eviterà l'overflow che stai attualmente riscontrando e soddisfa il caso di test che hai fornito.

+0

Sì, questo è sicuramente come lo farei, data la scelta. Il mio esempio di vita reale usa comunque CSLA, che non ti consente di farlo. Sembrerebbe davvero qualcosa di "classe Things: ReadOnlyListBase , IThings" – Joe

0

Non so se una domanda si qualifica come risposta, ma portami con me Sono un consulente; P Perché vuoi implementare il GetEnumerator()?

Si sta derivando dalla lista se si cambia in Elenco si ottiene il GetEnumerator gratuitamente.

Attenzione anche che derivare da List è generalmente una cattiva idea. dovresti considerare la derivazione da IEnumerable <() IThings> (come già stai facendo, che è atm superflua poiché List implementa anche quell'interfaccia) o se hai bisogno dell'interfaccia List e non solo dell'Iistumer implementato IList e mantieni un oggetto List privato.In questo modo si ottiene il pieno controllo circa l'attuazione ed esporre solo un contratto di progettazione (attraverso le interfacce implementate)

1

Questo è un bell'esempio della necessità del linguaggio e del runtime di comprendere covarianza e controvarianza.

In C# 4, si può semplicemente utilizzare

IEnumerator<IThing> IEnumerable<IThing>.GetEnumerator() 
{ 
    return base.GetEnumerator(); 
} 
Problemi correlati