2009-11-05 16 views
5

Robię co w mojej mocy, aby kodować na interfejsy, gdy tylko jest to możliwe, ale mam pewne problemy, jeśli chodzi o kolekcje. Na przykład tutaj mam kilka interfejsów, których chciałbym użyć.Wyraźna implementacja interfejsu GetEnumerator interfejsu powoduje przepełnienie stosu

public interface IThing {} 

public interface IThings : IEnumerable<IThing> {} 

Oto przykłady wdrożeń. Aby wdrożyć IEnumerable <IThing> muszę jawnie implementować IEnumerable <IThing> .GetEnumerator() w Things.

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

Problem polega na tym, że implementacja GetEnumerator powoduje przepełnienie stosu. Powtarza się w kółko. Nie mogę dowiedzieć się, dlaczego zdecydowałby się nazwać tę implementację GetEnumerator zamiast implementacji dostarczonej przez wynik tego .Cast <IThing>(). Jakieś pomysły, co robię źle? Jestem gotów się założyć, że to coś bardzo głupie ...

Oto niektóre proste kodu testu dla powyższych klas:

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

Odpowiedz

2

Użyj tego zamiast:

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

Albo można pracować z powstrzymywania zamiast.

+0

Oooh, dobrze. To czyni oczywistym, że base.GetEnumerator() powinien działać, jeśli zrozumiał kowariancję i contravaraince jak Frank powiedział. Ale ponieważ tak nie jest, robi to całkiem dobrze! – Joe

+0

tak, C# 4 będzie wielki:^p również, byłem zaskoczony, że "((Lista ) to) .Cast () .GetEnumerator();" nie działało: s – Stormenet

+0

Po obejrzeniu reflektora oczywiste jest, dlaczego Cast nie działa. Zasadniczo rzuca każdy z elementów pojedynczo, jeśli musi. W tym przypadku widzi, że "to" jest niezliczone , więc po prostu to zwraca. To zbyt mądre dla własnego dobra. :) – Joe

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

Jest to wywołanie rekurencyjne bez przerwy warunkiem, idziesz aby uzyskać przepełnienie stosu, ponieważ bardzo szybko zapełni stos.

Przesyłasz do interfejsu, który nie działa tak, jak myślisz. W końcu właśnie wywołujesz to.GetEnumerator(); która jest funkcją, w której już się znajdujesz, a więc recursuje. Być może masz na myśli base.GetEnumerator();

+0

base.GetEnumerator() nie załatwi, bo zwraca IEnumerator , a nie IEnumerator . – Joe

0

Wydaje się dość oczywiste, że this.Cast<IThing>() zwraca wartość IEnumerable. Lub przynajmniej nie wymaga wykonania Cast.

+0

Tak, myślę, że to oczywiste. Nie jestem pewien, jak to pomaga. Muszę zwrócić IEnumerator , a nie IEnumerable. – Joe

+0

Zgadnij, że nie było jasne ... Nie dostarczyłeś wersji Cast, więc nie mogę ci powiedzieć w 100%, na czym polega problem, ale mogę prawie zagwarantować, że Cast zwróci wartość "IEnumerable " (w przeciwnym razie robi coś, co powoduje przepełnienie stosu). Twoja jawna implementacja interfejsu oznacza, że ​​metoda zostanie wywołana, ilekroć 'ten' jest rzutowany jako' IEnumerable '. Dlatego jeśli wywołanie metody powoduje przepełnienie stosu, Cast musi przesłać bieżącą instancję do interfejsu IE, co spowoduje niekończącą się pętlę. – Will

1

Ogólnie rzecz biorąc, prawdopodobnie chciałbyś odłączyć implementację kolekcji Rzeczy od implementacji obiektu Thing. Klasa rzeczy jest w stanie pracować z każdą implementacją IThing, a nie tylko klasą Thing.

Konkretnie:

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

W tym przypadku nie trzeba zastąpić realizację domyślnej GetEnumerator(), realizacja bazowa jest już wpisany poprawnie dla Ciebie. Pozwoli to uniknąć przepełnienia, którego obecnie doświadczasz i spełnia podane przez ciebie przypadki testowe.

+0

Tak, to zdecydowanie, jak bym to zrobił, biorąc pod uwagę wybór. Mój prawdziwy przykład życia używa CSLA, co nie pozwoli ci tego zrobić. Wyglądałoby to na przykład "class Things: ReadOnlyListBase , IThings" – Joe

0

Nie wiem, czy pytanie kwalifikuje się jako odpowiedź, ale należy do mnie Jestem konsultantem; P Dlaczego chcesz wdrożyć GetEnumerator()?

Pochodzi od List, jeśli zmienisz to na Listę, otrzymasz GetEnumerator za darmo.

Należy również pamiętać, że wywodzenie się z List jest ogólnie złym pomysłem. powinieneś rozważyć wyprowadzenie z IEnumerable <() ITingi> (jak już robisz, co jest atm nadwyrężające, ponieważ List także implementuje ten interfejs) lub jeśli potrzebujesz interfejsu List, a nie tylko IEnumerable IList narzędzia i zachować prywatny obiekt List.W ten sposób można uzyskać pełną kontrolę na temat realizacji i tylko wystawiać umowę Design (przez zaimplementowanych interfejsów)

1

Jest to dobry przykład potrzeby, aby język i środowisko wykonawcze zrozumiały kowariancję i kontrrawariancje.

W języku C# 4, można po prostu użyć

IEnumerator<IThing> IEnumerable<IThing>.GetEnumerator() 
{ 
    return base.GetEnumerator(); 
} 
Powiązane problemy