Раньше как-то не задумывался, что это можно реализовать на Linq'е. А ведь оказывается, простейший Extension Method эту проблему решает. Например, потребовалось подписать на событие MouseDown все вложенные контролы некой формы, код в таком случае будет выглядеть примерно так:
foreach(Control c in this.Controls.WithDescendants(c => c.Controls)) { c.MouseDown += new EventHandler<MouseEventArgs>(Controls_MouseDown); }Код для метода Descendants я взял с mutable.net, он выглядит следующим образом:
static public class LinqExtensions { static public IEnumerable<T> WithDescendants<T>(this IEnumerable<T> source, Func<T, IEnumerable<T>> DescendBy) { foreach (T value in source) { yield return value; foreach (T child in DescendBy(value).Descendants<T>(DescendBy)) { yield return child; } } } }Выглядит всё очень красиво, и пользоваться тоже очень удобно. Тема оказалась довольно старинной, но повторюсь, иногда подобные решения просто не приходят в голову, особенно когда в голове уже много лет сидит прочное: "рекурсия реализуется так-то и так-то". Надеюсь, кому-то этот пост поможет, как и мне, открыть вот такой вот великолепный велосипед :)
Update:
Когда важна производительность, лучше использовать распрямление рекурсии в цикл. Думаю, это всем итак понятно, но уж коли мы имеем уникальную возможность сделать некий реюзабельный метод расширения, то о производительности стоит побеспокоиться сразу.
Я нашел пост Алексея Шинкарева LINQ-ом по деревьям (часть 2) с уже готовыми замерами различных алгоритмов. Ну вкратце, рекурсию, конечно же, следует распрямлять. Иначе в случае глубокого дерева получим банальный StackOverflowException.
Распрямление более-менее интуитивное, для быстроты привожу код-победитель по результатам Алексея, немного измененный мной под вызов для IEnumerable:
class ListNode<T> { public T Value { get; private set; } public ListNode<T> Next { get; private set; } public ListNode(T value, ListNode<T> next) { Value = value; Next = next; } } public static IEnumerable<T> WithDescendants<T>(this IEnumerable<T> rootNodes, Func<T, IEnumerable<T>> next) { if (rootNodes == null) yield break; ListNode<T> list = null; foreach (T listNode in rootNodes) { list = new ListNode<T>(listNode, list); } while (list != null) { var currentNode = list; var nextNode = list.Next; yield return currentNode.Value; ListNode<T> tmpList = null; IEnumerable<T> nextNodes = next(currentNode.Value); if (nextNodes != null) { foreach (var v in nextNodes) { tmpList = new ListNode<T>(v, tmpList); } } if (tmpList != null) { for (; tmpList != null; tmpList = tmpList.Next) { nextNode = new ListNode<T>(tmpList.Value, nextNode); } } list = nextNode; } }
Update:
Обновил немного код, в процессе опытной эксплуатации выяснилось что там требуется несколько дополнительных проверок.
Комментариев нет:
Отправить комментарий
Внимание! Реклама и прочий спам будут беспощадно удаляться.
Примечание. Отправлять комментарии могут только участники этого блога.