IEnumerable i Rekurencja za pomocą zwrotu plonu
Mam metodę IEnumerable<T>
, której używam do znajdowania kontrolek na stronie formularzy internetowych.
Metoda jest rekurencyjna i mam pewne problemy ze zwróceniem typu, który chcę, 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 błąd "nie można przekonwertować typu wyrażenia". Jeśli jednak ta metoda zwraca typ IEnumerable<Object>
, kod jest budowany, ale na wyjściu zwracany jest niewłaściwy typ.
Czy istnieje sposób użycia yield return
Podczas używania rekurencji?
8 answers
Wewnątrz metody, która zwraca IEnumerable<T>
, yield return
musi zwrócić T
, a nie IEnumerable<T>
.
Zastąp
yield return c.GetDeepControlsByType<T>();
Z:
foreach (var x in c.GetDeepControlsByType<T>())
{
yield return x;
}
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-01-13 10:29:49
Musisz oddać każdy z elementów wywołanych wywołaniem rekurencyjnym:
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)
{
foreach (Control control in c.GetDeepControlsByType<T>())
{
yield return control;
}
}
}
}
Zauważ, że rekursowanie w ten sposób jest kosztowne - 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ąć, w zasadzie musisz wykonać rekursję samodzielnie w metodzie, aby upewnić się, że istnieje tylko jeden iterator (maszyna stanowa). Zobacz to pytanie aby uzyskać więcej szczegółów i próbkę implementacja - ale to oczywiście dodaje pewną złożoność zbyt.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:10:45
Jak Jon Skeet i Pułkownik Panic zauważają w swoich odpowiedziach, używanie yield return
w metodach rekurencyjnych może powodować problemy z wydajnością, jeśli drzewo jest bardzo głębokie.
Oto ogólna nie-rekurencyjna metoda rozszerzenia, która wykonuje trawersację w pierwszej głębokości sekwencji drzew:
public static IEnumerable<TSource> RecursiveSelect<TSource>(
this IEnumerable<TSource> source, Func<TSource, IEnumerable<TSource>> childSelector)
{
var stack = new Stack<IEnumerator<TSource>>();
var enumerator = source.GetEnumerator();
try
{
while (true)
{
if (enumerator.MoveNext())
{
TSource element = enumerator.Current;
yield return element;
stack.Push(enumerator);
enumerator = childSelector(element).GetEnumerator();
}
else if (stack.Count > 0)
{
enumerator.Dispose();
enumerator = stack.Pop();
}
else
{
yield break;
}
}
}
finally
{
enumerator.Dispose();
while (stack.Count > 0) // Clean up in case of an exception.
{
enumerator = stack.Pop();
enumerator.Dispose();
}
}
}
W Przeciwieństwie Do rozwiązania Erica Lipperta , RecursiveSelect działa bezpośrednio z enumeratorami, dzięki czemu nie musi wywoływać odwrotności (co buforuje całą sekwencję w pamięci).
Za pomocą RecursiveSelect, oryginalną metodę OP można przepisać w następujący sposób:
public static IEnumerable<Control> GetDeepControlsByType<T>(this Control control)
{
return control.Controls.RecursiveSelect(c => c.Controls).Where(c => c is T);
}
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-04-13 12:40:36
Inni dostarczyli Ci poprawną odpowiedź, ale nie sądzę, że Twoja sprawa przyniesie korzyści z poddania się.
Oto fragment, który osiąga to samo bez ulegania.
public static IEnumerable<Control> GetDeepControlsByType<T>(this Control control)
{
return control.Controls
.Where(c => c is T)
.Concat(control.Controls
.SelectMany(c =>c.GetDeepControlsByType<T>()));
}
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-04-15 09:43:03
Musisz zwrócić pozycje z wyliczenia, a nie samego wyliczenia, w drugim yield return
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)
{
foreach (Control ctrl in c.GetDeepControlsByType<T>())
{
yield return ctrl;
}
}
}
}
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-01-13 10:30:39
/ Align = "left" /
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)
{
foreach (Control childControl in c.GetDeepControlsByType<T>())
{
yield return childControl;
}
}
}
}
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-01-13 10:32:02
Składnia Seredyńskiego jest poprawna, ale należy uważać, aby unikać yield return
w funkcjach rekurencyjnych, ponieważ jest to katastrofa dla wykorzystania pamięci. Zobacz https://stackoverflow.com/a/3970171/284795 skaluje się wybuchowo z głębią (podobną funkcją było wykorzystanie 10% pamięci w mojej aplikacji).
Prostym rozwiązaniem jest użycie jednej listy i podanie jej z rekurencją https://codereview.stackexchange.com/a/5651/754
/// <summary>
/// Append the descendents of tree to the given list.
/// </summary>
private void AppendDescendents(Tree tree, List<Tree> descendents)
{
foreach (var child in tree.Children)
{
descendents.Add(child);
AppendDescendents(child, descendents);
}
}
Alternatywnie możesz użyć stosu i pętla while eliminująca wywołania rekurencyjne https://codereview.stackexchange.com/a/5661/754
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 11:47:34
Chociaż istnieje wiele dobrych odpowiedzi, to jednak dodam, że możliwe jest użycie metod LINQ, aby osiągnąć to samo,.
Na przykład oryginalny kod OP można przepisać jako:
public static IEnumerable<Control>
GetDeepControlsByType<T>(this Control control)
{
return control.Controls.OfType<T>()
.Union(control.Controls.SelectMany(c => c.GetDeepControlsByType<T>()));
}
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-06-21 15:07:20