Mam IEnumerable<T>
metodę, której używam do znajdowania formantów na stronie WebForms.
Ta metoda jest rekurencyjna i mam pewne problemy ze zwróceniem żądanego typu, gdy yield return
zwraca wartość wywołania rekurencyjnego.
Mój kod wygląda następująco:
public static IEnumerable<Control>
GetDeepControlsByType<T>(this Control control)
{
foreach(Control c in control.Controls)
{
if (c is T)
{
yield return c;
}
if(c.Controls.Count > 0)
{
yield return c.GetDeepControlsByType<T>();
}
}
}
To powoduje obecnie błąd „Nie można przekonwertować typu wyrażenia”. Jeśli jednak ta metoda zwraca typ IEnumerable<Object>
, kod jest budowany, ale w wyniku zwracany jest niewłaściwy typ.
Czy istnieje sposób korzystania z yield return
jednoczesnej rekurencji?
c#
generics
ienumerable
yield
Jamie Dixon
źródło
źródło
if(c.Controls.Count > 0)
->if(c.Controls.Any())
, szczególnie jeśli równieżyield
. Zobacz poniżej :) I to też jest jedna linijka :)yield return
w funkcjach rekurencyjnych uniknąć skalowania użycia pamięci. Zobacz stackoverflow.com/a/30300257/284795Odpowiedzi:
Wewnątrz metody, która zwraca
IEnumerable<T>
,yield return
musi zwrócićT
, a nie anIEnumerable<T>
.Zastąpić
z:
źródło
Musisz wydać każdy przedmiot uzyskany w wyniku rekurencyjnego połączenia:
Zauważ, że rekursja w ten sposób wiąże się z pewnymi kosztami - w końcu stworzysz wiele iteratorów, które mogą powodować problemy z wydajnością, jeśli masz naprawdę głębokie drzewo kontroli. Jeśli chcesz tego uniknąć, po prostu musisz wykonać rekursję w ramach metody, aby upewnić się, że utworzono tylko jeden iterator (maszynę stanu). Zobacz to pytanie, aby uzyskać więcej szczegółów i przykładową implementację - ale to oczywiście dodaje pewnej złożoności.
źródło
c.Controls.Count > 0
vs..Any()
:)Jak zauważają Jon Skeet i pułkownik Panic w swoich odpowiedziach, stosowanie
yield return
metod rekurencyjnych może powodować problemy z wydajnością, jeśli drzewo jest bardzo głębokie.Oto ogólna nierekurencyjna metoda przedłużania, która wykonuje najpierw głębokość przechodzenia przez sekwencję drzew:
W przeciwieństwie do rozwiązania Erica Lipperta RecursiveSelect współpracuje bezpośrednio z modułami wyliczającymi, dzięki czemu nie musi wywoływać funkcji Reverse (która buforuje całą sekwencję w pamięci).
Za pomocą RecursiveSelect oryginalną metodę OP można przepisać w następujący sposób:
źródło
Inni podali prawidłową odpowiedź, ale nie sądzę, aby twoja sprawa przyniosła korzyść z ustępstw.
Oto fragment kodu, który osiąga to samo bez ustępowania.
źródło
yield
? ;)foreach
pętla. Teraz mogę to zrobić za pomocą czysto funkcjonalnego programowania!Musisz zwrócić elementy z modułu wyliczającego, a nie z samego modułu wyliczającego, na sekundę
yield return
źródło
Myślę, że musisz dać zwrot każdej z formantów w wyliczeniach.
źródło
Składnia Seredynskiego jest poprawna, ale należy uważać, aby unikać
yield return
funkcji rekurencyjnych, ponieważ jest to katastrofa z powodu zużycia pamięci. Zobacz https://stackoverflow.com/a/3970171/284795 . Skaluje się gwałtownie z głębokością (podobna funkcja używała 10% pamięci w mojej aplikacji).Prostym rozwiązaniem jest użycie jednej listy i przekazanie jej z rekurencją https://codereview.stackexchange.com/a/5651/754
Alternatywnie możesz użyć stosu i pętli while, aby wyeliminować rekurencyjne połączenia https://codereview.stackexchange.com/a/5661/754
źródło
Chociaż istnieje wiele dobrych odpowiedzi, nadal dodam, że możliwe jest użycie metod LINQ do osiągnięcia tego samego,.
Na przykład oryginalny kod OP może zostać przepisany jako:
źródło
OfType
nie jest naprawdę miażdżący inny. Co najwyżej niewielka zmiana styalistyczna. Kontrolka nie może być dzieckiem wielu kontrolek, więc drzewo, które przechodzi, jest już niepotrzebne. UżywanieUnion
zamiastConcat
niepotrzebnie weryfikuje wyjątkowość sekwencji, która jest już gwarantowana jako wyjątkowa, a zatem jest obiektywnym obniżeniem poziomu.