#if NETFRAMEWORK using System; using System.Collections.Generic; using System.Linq; #endif using System.Linq.Expressions; using Masuit.Tools.Systems; namespace Masuit.Tools.Models; /// /// 树形数据扩展 /// public static class TreeExtensions { /// /// 过滤 /// /// /// /// /// public static IEnumerable Filter(this IEnumerable items, Func func) where T : class, ITreeChildren => Flatten(items).Where(func); /// /// 过滤 /// /// /// /// /// public static IEnumerable> Filter(this IEnumerable> items, Func, bool> func) where T : class => Flatten(items).Where(func); /// /// 过滤 /// /// /// /// /// public static IEnumerable Filter(this T item, Func func) where T : class, ITreeChildren => Flatten(item).Where(func); /// /// 过滤 /// /// /// /// /// public static IEnumerable> Filter(this Tree item, Func, bool> func) where T : class => Flatten(item).Where(func); /// /// 平铺开 /// /// /// /// 平铺时子级需要做的操作,参数1:子级对象,参数2:父级对象 /// public static IEnumerable Flatten(this IEnumerable items, Action optionAction = null) where T : class, ITreeChildren => items.Flatten(i => i.Children ?? [], optionAction); /// /// 平铺开 /// /// /// /// 平铺时子级需要做的操作,参数1:子级对象,参数2:父级对象 /// public static IEnumerable Flatten(this T p, Action optionAction = null) where T : class, ITreeChildren => p != null ? Flatten([p], t => t.Children, optionAction) : []; /// /// 平铺开任意树形结构数据 /// /// /// /// 子节点获取方法 /// 平铺时子级需要做的操作,参数1:子级对象,参数2:父级对象 /// public static IEnumerable Flatten(this IEnumerable items, Func> selector, Action optionAction = null) { if (items == null) yield break; // 使用一个队列来存储待处理的节点 var queue = new Queue(); // 首先将所有项加入队列 foreach (var item in items) queue.Enqueue(item); // 循环直到队列为空 while (queue.Count > 0) { // 从队列中取出当前节点 var currentItem = queue.Dequeue(); // 将当前节点返回 yield return currentItem; // 获取当前节点的所有子节点 var children = selector(currentItem) ?? []; // 将所有子节点加入队列 foreach (var child in children) { // 执行平铺时的操作(如果有的话) optionAction?.Invoke(child, currentItem); queue.Enqueue(child); } } } /// /// 平铺开 /// /// /// /// 平铺时子级需要做的操作,参数1:子级对象,参数2:父级对象 /// public static IEnumerable> Flatten(this IEnumerable> items, Action, Tree> optionAction = null) where T : class => items != null ? Flatten(items, t => t.Children, optionAction) : []; /// /// 平铺开 /// /// /// /// 平铺时子级需要做的操作,参数1:子级对象,参数2:父级对象 /// public static IEnumerable> Flatten(this Tree p, Action, Tree> optionAction = null) where T : class { if (p == null) { return []; } return Flatten([p], t => t.Children, optionAction); } /// /// 平行集合转换成树形结构 /// /// /// /// /// /// 根对象parentId的值 /// public static List ToTree(this IEnumerable source, Expression> idSelector, Expression> pidSelector, string topValue = null) where T : ITreeParent, ITreeChildren => ToTree(source, idSelector, pidSelector, topValue); /// /// 平行集合转换成树形结构 /// /// /// /// /// /// 根对象parentId的值 /// public static List ToTree(this IEnumerable source, Expression> idSelector, Expression> pidSelector, int topValue = 0) where T : ITreeParent, ITreeChildren => ToTree(source, idSelector, pidSelector, topValue); /// /// 平行集合转换成树形结构 /// /// /// /// /// /// 根对象parentId的值 /// public static List ToTree(this IEnumerable source, Expression> idSelector, Expression> pidSelector, long topValue = 0) where T : ITreeParent, ITreeChildren => ToTree(source, idSelector, pidSelector, topValue); /// /// 平行集合转换成树形结构 /// /// /// /// /// /// 根对象parentId的值 /// public static List ToTree(this IEnumerable source, Expression> idSelector, Expression> pidSelector, Guid topValue = default) where T : ITreeParent, ITreeChildren => ToTree(source, idSelector, pidSelector, topValue); /// /// 平行集合转换成树形结构 /// /// /// /// /// /// /// 根对象parentId的值 /// public static List ToTree(this IEnumerable source, Expression> idSelector, Expression> pidSelector, TKey topValue = default) where T : ITreeParent, ITreeChildren where TKey : IComparable => idSelector.Body.ToString() != pidSelector.Body.ToString() ? BuildTree(source.Enumerable2NonNullList(), idSelector.Compile(), pidSelector.Compile(), topValue).ToList() : throw new ArgumentException("idSelector和pidSelector不应该为同一字段!"); /// /// 平行集合转换成树形结构 /// /// /// /// public static List ToTree(this IEnumerable source) where T : class, ITreeEntity => ToTree(source); /// /// 平行集合转换成树形结构 /// /// /// /// /// /// public static List ToTree(this IEnumerable source, TKey? topValue = null) where T : class, ITreeEntity where TKey : struct, IComparable => BuildTree(source.Enumerable2NonNullList(), topValue).ToList(); /// /// 平行集合转换成树形结构 /// /// /// /// /// /// /// 根对象parentId的值 /// public static List ToTree(this IEnumerable source, Expression> idSelector, Expression> pidSelector, TKey? topValue = null) where T : ITreeChildren where TKey : struct => idSelector.Body.ToString() != pidSelector.Body.ToString() ? BuildTree(source.Enumerable2NonNullList(), idSelector.Compile(), pidSelector.Compile(), topValue).ToList() : throw new ArgumentException("idSelector和pidSelector不应该为同一字段!"); private static IEnumerable BuildTree(IEnumerable source, Func idSelector, Func pidSelector, TKey topValue = default) where T : ITreeChildren where TKey : IComparable { // 创建一个字典,用于快速查找节点的子节点 var childrenLookup = new Dictionary>(); var list = source as ICollection ?? source.ToList(); foreach (var item in list.Where(item => !childrenLookup.ContainsKey(idSelector(item)))) { childrenLookup[idSelector(item)] = []; } // 构建树结构 foreach (var item in list.Where(item => !Equals(pidSelector(item), default(TKey)) && childrenLookup.ContainsKey(pidSelector(item)))) { childrenLookup[pidSelector(item)].Add(item); } // 找到根节点,即没有父节点的节点 foreach (var root in list.Where(x => Equals(pidSelector(x), topValue))) { // 为根节点和所有子节点设置Children属性 // 使用队列来模拟递归过程 var queue = new Queue(); queue.Enqueue(root); while (queue.Count > 0) { // 出队当前节点 var current = queue.Dequeue(); // 为当前节点设置子节点 if (childrenLookup.TryGetValue(idSelector(current), out var children)) { current.Children = children; foreach (var child in children) { // 如果子节点实现了ITreeParent接口,则设置其Parent属性 if (child is ITreeParent tree) { tree.Parent = current; } // 将子节点入队以继续处理 queue.Enqueue(child); } } } yield return root; } } internal static IEnumerable BuildTree(IEnumerable source, TKey? topValue = null) where T : ITreeEntity where TKey : struct, IComparable => BuildTree(source, t => t.Id, t => t.ParentId, topValue); internal static IEnumerable BuildTree(IEnumerable source, string topValue = null) where T : ITreeEntity => BuildTree(source, t => t.Id, t => t.ParentId, topValue); internal static IEnumerable BuildTree(IEnumerable source, T parent) where T : ITreeEntity { // 创建一个字典,用于快速查找节点的子节点 var childrenLookup = new NullableDictionary>(); var list = source as ICollection ?? source.ToList(); foreach (var item in list.Where(item => !childrenLookup.ContainsKey(item.Id))) childrenLookup[item.Id] = []; // 构建树结构 foreach (var item in list.Where(item => !string.IsNullOrEmpty(item.ParentId) && childrenLookup.ContainsKey(item.ParentId))) childrenLookup[item.ParentId].Add(item); // 找到根节点,即没有父节点的节点 foreach (var root in list.Where(x => x.Id == parent.Id)) { // 为根节点和所有子节点设置Children属性 // 使用队列来模拟递归过程 var queue = new Queue(); queue.Enqueue(root); while (queue.Count > 0) { // 出队当前节点 var current = queue.Dequeue(); // 为当前节点设置子节点 if (childrenLookup.TryGetValue(current.Id, out var children)) { current.Children = children; foreach (var child in children) { // 如果子节点实现了ITreeParent接口,则设置其Parent属性 if (child is ITreeParent tree) tree.Parent = current; // 将子节点入队以继续处理 queue.Enqueue(child); } } } yield return root; } } private static IEnumerable BuildTree(IEnumerable source, Func idSelector, Func pidSelector, TKey? topValue = null) where T : ITreeChildren where TKey : struct { // 创建一个字典,用于快速查找节点的子节点 var childrenLookup = new NullableDictionary>(); var list = source as ICollection ?? source.ToList(); foreach (var item in list.Where(item => !childrenLookup.ContainsKey(idSelector(item)))) { childrenLookup[idSelector(item)] = []; } // 构建树结构 foreach (var item in list.Where(item => !Equals(pidSelector(item), default(TKey)) && childrenLookup.ContainsKey(pidSelector(item) ?? default))) { childrenLookup[pidSelector(item) ?? default].Add(item); } // 找到根节点,即没有父节点的节点 foreach (var root in list.Where(x => Equals(pidSelector(x), topValue))) { // 为根节点和所有子节点设置Children属性 // 使用队列来模拟递归过程 var queue = new Queue(); queue.Enqueue(root); while (queue.Count > 0) { // 出队当前节点 var current = queue.Dequeue(); // 为当前节点设置子节点 if (childrenLookup.TryGetValue(idSelector(current), out var children)) { current.Children = children; foreach (var child in children) { // 如果子节点实现了ITreeParent接口,则设置其Parent属性 if (child is ITreeParent tree) { tree.Parent = current; } // 将子节点入队以继续处理 queue.Enqueue(child); } } } yield return root; } } /// /// 平行集合转换成树形结构 /// /// /// /// /// /// /// 根对象parentId的值 /// public static List> ToTreeGeneral(this IEnumerable source, Expression> idSelector, Expression> pidSelector, TKey topValue = default) where TKey : IComparable => idSelector.Body.ToString() != pidSelector.Body.ToString() ? BuildTreeGeneral(source.Where(t => t != null).ToList(), idSelector.Compile(), pidSelector.Compile(), topValue).ToList() : throw new ArgumentException("idSelector和pidSelector不应该为同一字段!"); private static IEnumerable> BuildTreeGeneral(List list, Func idSelector, Func pidSelector, TKey parent) where TKey : IComparable { // 创建一个字典,用于快速查找节点的子节点 var lookup = new Dictionary>>(); foreach (var item in list.Where(item => !lookup.ContainsKey(idSelector(item)))) { lookup[idSelector(item)] = []; } // 构建树结构 foreach (var item in list.Where(item => !Equals(pidSelector(item), default(TKey)) && lookup.ContainsKey(pidSelector(item)))) { lookup[pidSelector(item)].Add(new Tree(item)); } // 找到根节点,即没有父节点的节点 foreach (var root in list.Where(x => Equals(pidSelector(x), parent))) { // 为根节点和所有子节点设置Children属性 var queue = new Queue>(); var item = new Tree(root); queue.Enqueue(item); while (queue.Count > 0) { // 出队当前节点 var current = queue.Dequeue(); // 为当前节点设置子节点 if (lookup.TryGetValue(idSelector(current.Value), out var children)) { current.Children = children; foreach (var child in children) { child.Parent = current.Parent; // 将子节点入队以继续处理 queue.Enqueue(child); } } } yield return item; } } /// /// 所有子级 /// public static ICollection AllChildren(this T tree) where T : ITreeChildren => GetChildren(tree, c => c.Children); /// /// 所有子级 /// public static ICollection AllChildren(this T tree, Func> selector) where T : ITreeChildren => GetChildren(tree, selector); /// /// 所有子级 /// public static ICollection> AllChildren(this Tree tree) => GetChildren(tree, c => c.Children); /// /// 所有子级 /// public static ICollection> AllChildren(this Tree tree, Func, IEnumerable>> selector) => GetChildren(tree, selector); /// /// 所有父级 /// public static List AllParent(this T tree) where T : class, ITreeParent => GetParents(tree, c => c.Parent); /// /// 所有父级 /// public static List AllParent(this T tree, Func selector) where T : class, ITreeParent => GetParents(tree, selector); /// /// 所有父级 /// public static List> AllParent(this Tree tree, Func, Tree> selector) => GetParents(tree, selector); /// /// 是否是根节点 /// public static bool IsRoot(this ITreeParent tree) where T : ITreeParent => tree.Parent == null; /// /// 是否是叶子节点 /// public static bool IsLeaf(this ITreeChildren tree) where T : ITreeChildren => tree.Children?.Count == 0; /// /// 是否是根节点 /// public static bool IsRoot(this Tree tree) => tree.Parent == null; /// /// 是否是叶子节点 /// public static bool IsLeaf(this Tree tree) => tree.Children?.Count == 0; /// /// 深度层级 /// public static int Level(this T tree) where T : class, ITreeParent { if (tree == null) throw new ArgumentNullException(nameof(tree), "当前节点不能为null"); // 使用一个队列来存储待处理的节点 var queue = new Queue(); queue.Enqueue(tree); int level = 1; // 循环直到队列为空 while (queue.Count > 0) { int nodeCount = queue.Count; // 当前层级的节点数 for (int i = 0; i < nodeCount; i++) { // 从队列中出队一个节点 T currentNode = queue.Dequeue(); // 如果当前节点是根节点,则返回当前层级 if (currentNode.Parent == null) return level; // 将当前节点的父节点入队 if (currentNode.Parent != null) queue.Enqueue(currentNode.Parent); } // 完成当前层级的遍历,准备进入下一层级 level++; } // 如果执行到这里,说明没有找到根节点 throw new InvalidOperationException("未找到根节点"); } /// /// 节点路径(UNIX路径格式,以“/”分隔) /// public static string Path(this T tree) where T : class, ITree => GetFullPath(tree, t => t.Name); /// /// 节点路径(UNIX路径格式,以“/”分隔) /// public static string Path(this T tree, Func selector) where T : class, ITreeParent => GetFullPath(tree, selector); /// /// 节点路径 /// /// /// 分隔符 public static string Path(this T tree, string separator) where T : class, ITree => GetFullPath(tree, t => t.Name, separator); /// /// 节点路径 /// /// /// /// 选择字段 /// 分隔符 /// public static string Path(this T tree, Func selector, string separator) where T : class, ITreeParent => GetFullPath(tree, selector, separator); /// /// 根节点 /// public static T Root(this T tree) where T : class, ITreeParent => GetRoot(tree, t => t.Parent); private static string GetFullPath(T c, Func selector, string separator = "/") where T : class, ITreeParent { if (c == null || selector == null) { throw new ArgumentNullException(c == null ? nameof(c) : nameof(selector), "当前节点或选择器不能为null"); } // 使用一个栈来存储节点,栈将逆序存储路径中的节点 var stack = new Stack(); var currentNode = c; while (currentNode != null) { stack.Push(currentNode); currentNode = currentNode.Parent; } // 构建路径字符串 return stack.Select(selector).Join(separator); } /// /// 根节点 /// public static T GetRoot(T c, Func selector) where T : class, ITreeParent { if (c == null || selector == null) { throw new ArgumentNullException(c == null ? nameof(c) : nameof(selector), "当前节点或父级选择器不能为null"); } // 使用一个集合来存储已访问的节点,以避免无限循环 var visited = new HashSet(); var currentNode = c; // 向上遍历直到找到根节点 while (true) { // 如果当前节点已被访问,说明存在循环引用,抛出异常 if (!visited.Add(currentNode)) throw new InvalidOperationException("节点存在循环引用"); var parent = selector(currentNode); if (parent == null) { return currentNode; // 找到了根节点 } currentNode = parent; } } /// /// 递归取出所有下级 /// /// /// /// private static List GetChildren(T t, Func> selector) { if (t == null || selector == null) return []; // 如果t或selector为null,则返回空列表 var list = new List(); var queue = new Queue(); queue.Enqueue(t); while (queue.Count > 0) { var current = queue.Dequeue(); list.Add(current); // 将当前节点添加到结果列表 var children = selector(current) ?? new List(); foreach (var child in children.Where(child => selector(child).Any())) { queue.Enqueue(child); } } return list; } /// /// 递归取出所有上级 /// /// /// /// private static List GetParents(T t, Func selector) where T : class { if (t == null || selector == null) return []; // 如果t或selector为null,则返回空列表 var parents = new List(); var current = t; while (current != null) { // 添加当前节点到父节点列表 parents.Add(current); // 通过selector函数获取当前节点的父节点 current = selector(current); // 如果selector返回了已经遍历过的节点,则停止遍历以避免无限循环 if (parents.Contains(current)) { break; } } // 由于是逆序添加的,所以需要反转列表 parents.Reverse(); return parents; } } /// /// 树形数据扩展 long /// public static class TreeExtensionLong { /// /// 平行集合转换成树形结构 /// /// /// /// public static List ToTree(this IEnumerable source) where T : class, ITreeEntity => source.ToTree(); } /// /// 树形数据扩展 guid /// public static class TreeExtensionGuid { /// /// 平行集合转换成树形结构 /// /// /// /// public static List ToTree(this IEnumerable source) where T : class, ITreeEntity => source.ToTree(); } /// /// 树形数据扩展 string /// public static class TreeExtensionString { /// /// 平行集合转换成树形结构 /// /// /// /// /// public static List ToTree(this IEnumerable source, string topValue) where T : class, ITreeEntity => TreeExtensions.BuildTree(source.Enumerable2NonNullList(), topValue).ToList(); /// /// 平行集合转换成树形结构 /// /// /// /// public static List ToTree(this IEnumerable source) where T : class, ITreeEntity => source.Enumerable2NonNullList().Where(t => string.IsNullOrEmpty(t.ParentId)).SelectMany(parent => TreeExtensions.BuildTree(source, parent)).ToList(); } internal static class TreeExtensionCommon { /// /// 将IEnumerable转换成不含null的List,若T为IQueryable,则将数据读取到内存 /// /// /// /// public static List Enumerable2NonNullList(this IEnumerable source) { source = source is IQueryable queryable ? queryable.AsEnumerable() : source; return (typeof(T).IsValueType ? source : source.Where(t => t != null)).ToList(); } }