У меня есть IEnumerable<T>
метод, который я использую, чтобы найти элементы управления на странице WebForms.
Метод рекурсивный, и у меня возникают некоторые проблемы с возвратом 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)
{
yield return c.GetDeepControlsByType<T>();
}
}
}
В настоящее время выдается ошибка «Не удается преобразовать тип выражения». Однако если этот метод возвращает тип IEnumerable<Object>
, код создается, но в выводе возвращается неправильный тип.
Есть ли способ использования, yield return
а также рекурсии?
c#
generics
ienumerable
yield
Джейми Диксон
источник
источник
if(c.Controls.Count > 0)
->if(c.Controls.Any())
, особенно если вы тоже уступаете :)yield
. Пожалуйста, смотрите ниже :) И это тоже однострочник :)yield return
в рекурсивных функциях, использование памяти взрывчато. См. Stackoverflow.com/a/30300257/284795Ответы:
Внутри метода, который возвращает
IEnumerable<T>
,yield return
должен возвращатьсяT
, а неIEnumerable<T>
.замещать
с участием:
источник
Вам нужно отдать каждый из предметов, полученных в результате рекурсивного вызова:
Обратите внимание, что повторение таким способом обходится дорого - в конечном итоге вы создадите много итераторов, которые могут создать проблему производительности, если у вас действительно глубокое дерево управления. Если вы хотите избежать этого, вам в основном необходимо выполнить рекурсию внутри метода, чтобы убедиться, что создан только один итератор (конечный автомат). Посмотрите этот вопрос для более подробной информации и примера реализации - но это, очевидно, также добавляет некоторую сложность.
источник
c.Controls.Count > 0
против.Any()
:)Как отмечают Джон Скит и полковник Паник в своих ответах, использование
yield return
рекурсивных методов может вызвать проблемы с производительностью, если дерево очень глубокое.Вот общий нерекурсивный метод расширения, который выполняет обход глубины последовательности деревьев:
В отличие от решения Эрика Липперта , RecursiveSelect работает напрямую с перечислителями, поэтому ему не нужно вызывать Reverse (который буферизует всю последовательность в памяти).
Используя RecursiveSelect, оригинальный метод OP можно переписать просто так:
источник
Другие дали вам правильный ответ, но я не думаю, что ваше дело выиграет от уступок.
Вот фрагмент, который достигает того же самого, не уступая.
источник
yield
? ;)foreach
цикл. Теперь я могу сделать это с помощью чисто функционального программирования!Вам нужно вернуть элементы из перечислителя, а не сам перечислитель, в ваш второй
yield return
источник
Я думаю, что вы должны возвращать каждый из элементов управления в перечислимых.
источник
Серединный синтаксис правильный, но вы должны быть осторожны, избегая
yield return
рекурсивных функций, потому что это катастрофа для использования памяти. См. Https://stackoverflow.com/a/3970171/284795, он масштабируется с большой глубиной (аналогичная функция использовала 10% памяти в моем приложении).Простое решение - использовать один список и передать его с помощью рекурсии https://codereview.stackexchange.com/a/5651/754.
В качестве альтернативы вы можете использовать стек и цикл while для устранения рекурсивных вызовов https://codereview.stackexchange.com/a/5661/754
источник
Хотя есть много хороших ответов, я все же добавил бы, что для достижения той же цели можно использовать методы LINQ.
Например, исходный код OP может быть переписан как:
источник
OfType
самом деле совсем не изящное. Максимум незначительных изменений в стиле. Элемент управления не может быть дочерним по отношению к нескольким элементам управления, поэтому пройденное дерево уже не является обязательным. ИспользованиеUnion
вместоConcat
ненужной проверки уникальности последовательности, которая уже гарантированно является уникальной, и, следовательно, является объективным понижением.