IEnumerable<T>
WebForms 페이지에서 컨트롤을 찾는 데 사용 하는 방법이 있습니다.
이 방법은 재귀 적이며 yield return
재귀 호출의 값을 returnig 할 때 원하는 유형을 반환하는 데 문제 가 있습니다.
내 코드는 다음과 같습니다.
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>();
}
}
}
현재 “표현 유형을 변환 할 수 없습니다”오류가 발생합니다. 그러나이 메소드가 type을 리턴 IEnumerable<Object>
하면 코드가 빌드되지만 출력에 잘못된 유형이 리턴됩니다.
yield return
재귀도 사용 하는 방법이 있습니까?
답변
반환하는 방법 내부 IEnumerable<T>
, yield return
반환하는 T
,을하지 IEnumerable<T>
.
바꾸다
yield return c.GetDeepControlsByType<T>();
와:
foreach (var x in c.GetDeepControlsByType<T>())
{
yield return x;
}
답변
재귀 호출로 산출 된 각 항목을 산출 해야합니다 .
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;
}
}
}
}
이런 식으로 되풀이하는 비용이 발생합니다. 많은 반복자를 생성하게되므로 제어 트리가 매우 깊은 경우 성능 문제가 발생할 수 있습니다. 이를 피하려면 기본적으로 메소드 내에서 재귀를 수행하여 하나의 반복자 (상태 머신) 만 작성되도록해야합니다. 자세한 내용과 샘플 구현에 대해서는 이 질문 을 참조하십시오. 그러나 이것은 분명히 어느 정도의 복잡성을 추가합니다.
답변
Jon Skeet과 Panic 대령이 답에 언급했듯이, yield return
재귀 방법을 사용 하면 나무가 매우 깊으면 성능 문제가 발생할 수 있습니다.
다음 은 일련의 트리에 대해 깊이 우선 순회를 수행 하는 일반적인 비 재귀 확장 방법입니다.
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();
}
}
}
Eric Lippert의 솔루션 과 달리 RecursiveSelect는 열거 자와 직접 작동하므로 Reverse (메모리의 전체 시퀀스를 버퍼링)를 호출 할 필요가 없습니다.
RecursiveSelect를 사용하면 다음과 같이 OP의 원래 방법을 간단히 다시 작성할 수 있습니다.
public static IEnumerable<Control> GetDeepControlsByType<T>(this Control control)
{
return control.Controls.RecursiveSelect(c => c.Controls).Where(c => c is T);
}
답변
다른 사람들이 당신에게 정답을 제공했지만 귀하의 사례가 양보로부터 이익을 얻지 못한다고 생각합니다.
다음은 양보하지 않고 동일한 것을 달성하는 스 니펫입니다.
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>()));
}
답변
두 번째로 열거 자 자체가 아닌 열거 자 에서 항목 을 반환해야합니다.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;
}
}
}
}
답변
열거 형의 각 컨트롤을 반환해야한다고 생각합니다.
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;
}
}
}
}
답변
Seredynski의 구문 은 정확하지만 yield return
재귀 함수는 메모리 사용에있어 재난이므로 피해야 합니다. https://stackoverflow.com/a/3970171/284795를 참조 하십시오. 이 기능은 깊이에 따라 폭발적으로 확장됩니다 (유사한 기능은 내 응용 프로그램에서 메모리의 10 %를 사용했습니다).
간단한 해결책은 하나의 목록을 사용하고 재귀와 함께 전달하는 것입니다 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);
}
}
또는 스택과 while 루프를 사용하여 재귀 호출을 제거 할 수 있습니다 https://codereview.stackexchange.com/a/5661/754