Функция рекурсии LINQ?
Возьмем эту глубинную структуру n-уровня, например:
public class SomeItem
{
public Guid ID { get;set; }
public string Name { get; set; }
public bool HasChildren { get;set; }
public IEnumerable<SomeItem> Children { get; set; }
}
Если я ищу, чтобы получить конкретный элемент по идентификатору (в любом месте структуры), есть ли какая-то LINQ доброта, которую я могу использовать, чтобы легко получить его в одном выражении или мне нужно использовать некоторую рекурсивную функцию, как показано ниже:
private SomeItem GetSomeItem(IEnumerable<SomeItem> items, Guid ID)
{
foreach (var item in items)
{
if (item.ID == ID)
{
return item;
}
else if (item.HasChildren)
{
return GetSomeItem(item.Children, ID);
}
}
return null;
}
Ответы
Ответ 1
LINQ не очень хорошо выполняет рекурсию. Ваше решение кажется подходящим - хотя я не уверен, что HasChildren действительно требуется... почему бы просто не использовать пустой список для элемента без детей?
Альтернативой является написать метод DescendantsAndSelf
, который вернет все потомки (включая сам элемент), что-то вроде этого:
// Warning: potentially expensive!
public IEnumerable<SomeItem> DescendantsAndSelf()
{
yield return this;
foreach (var item in Children.SelectMany(x => x.DescendantsAndSelf()))
{
yield return item;
}
}
Однако, если дерево глубокое, что заканчивается очень неэффективным, потому что каждый элемент должен "пройти" все итераторы его родословной. Wes Dyer рассказал об этом, демонстрируя более эффективную реализацию.
В любом случае, если у вас есть такой метод (однако он реализован), вы можете просто использовать обычное предложение "where", чтобы найти элемент (или First/FirstOrDefault и т.д.).
Ответ 2
надеюсь, что это поможет
public static IEnumerable<Control> GetAllChildControls(this Control parent)
{
foreach (Control child in parent.Controls)
{
yield return child;
if (child.HasChildren)
{
foreach (Control grandChild in child.GetAllChildControls())
yield return grandChild;
}
}
}
Ответ 3
Здесь один без рекурсии. Это позволяет избежать затрат на прохождение через несколько уровней итераторов, поэтому я думаю, что это так же эффективно, как и они.
public static IEnumerable<T> IterateTree<T>(this T root, Func<T, IEnumerable<T>> childrenF)
{
var q = new List<T>() { root };
while (q.Any())
{
var c = q[0];
q.RemoveAt(0);
q.AddRange(childrenF(c) ?? Enumerable.Empty<T>());
yield return c;
}
}
Вызвать так:
var subtree = root.IterateTree(x => x. Children).ToList();
Ответ 4
Важно помнить, что вам не нужно делать все с помощью LINQ или по умолчанию для рекурсии. При использовании структур данных есть интересные варианты. Ниже приведена простая функция сглаживания в случае, если кто-либо заинтересован.
public static IEnumerable<SomeItem> Flatten(IEnumerable<SomeItem> items)
{
if (items == null || items.Count() == 0) return new List<SomeItem>();
var result = new List<SomeItem>();
var q = new Queue<SomeItem>(collection: items);
while (q.Count > 0)
{
var item = q.Dequeue();
result.Add(item);
if (item?.Children?.Count() > 0)
foreach (var child in item.Children)
q.Enqueue(child);
}
return result;
}
Ответ 5
Хотя существуют методы расширения, которые позволяют рекурсию в LINQ (и, вероятно, похожи на вашу функцию), ни один из них не предоставляется из коробки.
Примеры этих методов расширения можно найти здесь или здесь.
Я бы сказал, что ваша функция в порядке.