集合与 LINQ
LINQ(Language Integrated Query)是 C# 最具特色的功能之一,也是处理 AutoCAD 图形数据库和 Revit 元素集的核心工具。
常用集合类型
列表与字典
// List<T> —— 最常用的动态数组
var entities = new List<Entity>();
entities.Add(new Line());
entities.AddRange(new[] { new Circle(), new Arc() });
Entity first = entities[0]; // 索引访问
bool hasItems = entities.Count > 0;
entities.RemoveAt(0);
entities.Clear();
// Dictionary<TKey, TValue> —— 键值对
var layers = new Dictionary<string, Layer>
{
["0"] = new Layer("0"),
["WALL"] = new Layer("WALL")
};
layers["DOOR"] = new Layer("DOOR"); // 添加或覆盖
bool exists = layers.ContainsKey("WALL"); // true
layers.TryGetValue("WALL", out Layer? wallLayer); // 安全获取
// HashSet<T> —— 不重复集合,高速查找
var ids = new HashSet<string> { "A", "B", "C" };
ids.Add("A"); // 忽略重复
bool has = ids.Contains("B"); // O(1) 查找其他常用集合
// Queue<T> —— FIFO
var queue = new Queue<string>();
queue.Enqueue("first");
queue.Enqueue("second");
string item = queue.Dequeue(); // "first"
// Stack<T> —— LIFO
var stack = new Stack<int>();
stack.Push(1);
stack.Push(2);
int top = stack.Pop(); // 2
// SortedDictionary / SortedList —— 按键排序
var sorted = new SortedDictionary<int, string>
{
[3] = "three",
[1] = "one" // 自动按 key 排序
};
// LinkedList<T> —— 双向链表
var linked = new LinkedList<string>();
linked.AddLast("a");
linked.AddFirst("b");| 集合 | 适用场景 | 索引 | 添加 | 查找 | 内存 |
|---|---|---|---|---|---|
List<T> |
通用动态数组 | O(1) | O(1)* | O(n) | 紧凑 |
Dictionary<K,V> |
键值映射 | O(1) | O(1)* | O(1) | 较高 |
HashSet<T> |
去重、Contains | — | O(1)* | O(1) | 较高 |
LinkedList<T> |
频繁插入/删除 | O(n) | O(1) | O(n) | 每个节点有额外开销 |
Queue<T> |
FIFO 队列 | — | O(1) | — | 紧凑 |
Stack<T> |
LIFO 栈 | — | O(1) | — | 紧凑 |
IEnumerable 与延迟执行
// IEnumerable<T> 是 LINQ 的基础
IEnumerable<int> GetNumbers()
{
for (int i = 0; i < 5; i++)
{
Console.WriteLine($"生成 {i}");
yield return i; // 迭代器,每次只生成一个
}
}
// 延迟执行:此时 GetNumbers 还未执行
var numbers = GetNumbers().Where(n => n > 2);
// 真正执行在迭代时
foreach (var n in numbers)
Console.WriteLine(n);
// 输出:
// 生成 0
// 生成 1
// 生成 2
// 生成 3
// 3
// 生成 4
// 4🚨 延迟执行陷阱:LINQ 查询直到迭代时才执行。这意味着如果你在查询中捕获了变量,其值可能是迭代时的值而非定义时的值。
// 🚨 陷阱示例
var filters = new List<string> { "A", "B" };
var query = filters.Where(f => f.Length > 0); // 未执行
filters.Add("C"); // 修改了源
Console.WriteLine(query.Count()); // 3!因为执行时才求值LINQ 核心操作
筛选与投影
// Where —— 筛选
var longLines = entities
.Where(e => e is Line && ((Line)e).Length > 100);
// OfType —— 按类型筛选 + 转换(AutoCAD 开发中极常用)
var allLines = entities.OfType<Line>(); // 等价于 entities.Where(e => e is Line).Cast<Line>()
// Select —— 投影/转换
var layerNames = entities.Select(e => e.Layer).Distinct();
// SelectMany —— 展平
var allPoints = polylines.SelectMany(pl => pl.Vertices);
// 匿名类型 —— 中间产物
var summaries = entities.Select(e => new
{
e.Id,
e.Layer,
Type = e.GetType().Name
});排序与分区
// OrderBy / ThenBy
var sorted = entities
.OrderBy(e => e.Layer)
.ThenByDescending(e => e.Id);
// Take / Skip —— 分页
var page2 = entities.OrderBy(e => e.Id).Skip(20).Take(10); // 第 21~30 条
// First / Last / Single
Entity first = entities.First(); // 无元素会抛异常
Entity? firstOrNull = entities.FirstOrDefault(); // 无元素返回 default
Entity only = entities.Single(); // 多于一个会抛异常聚合与分组
// Count, Sum, Average, Min, Max
int count = entities.Count();
double maxLen = entities.OfType<Line>().Max(l => l.Length);
double avgLen = entities.OfType<Line>().Average(l => l.Length);
// GroupBy
var groups = entities.GroupBy(e => e.Layer);
foreach (var group in groups)
{
Console.WriteLine($"图层 {group.Key}:{group.Count()} 个实体");
}
// ToDictionary / ToLookup
var dict = entities.ToDictionary(e => e.Id); // 一对一映射
var lookup = entities.ToLookup(e => e.Layer); // 一对多映射量化操作
bool allOnLayer0 = entities.All(e => e.Layer == "0");
bool anyDoor = entities.Any(e => e is Door);
bool contains = entities.Select(e => e.Id).Contains("ABC");LINQ 方法语法 vs 查询语法
// 方法语法(推荐,一致、可链式)
var result = entities
.Where(e => e.Layer == "WALL")
.OrderBy(e => e.Id)
.Select(e => new { e.Id, e.Layer });
// 查询语法(类似 SQL,适合 join 场景)
var result = from e in entities
where e.Layer == "WALL"
orderby e.Id
select new { e.Id, e.Layer };
// JOIN —— 查询语法更清晰
var joined = from e in entities
join l in layers on e.Layer equals l.Name
select new { Entity = e, Layer = l };💡 方法语法与查询语法完全等价,编译后生成相同的 IL。选一种风格并在项目中保持一致。
立即执行 vs 延迟执行
// 延迟执行(迭代时才执行):
// Where, Select, Take, Skip, OrderBy, GroupBy, Join, Cast, OfType
var query = entities.Where(e => e.Layer == "0"); // 不执行
// 立即执行(返回具体值):
// ToList, ToArray, ToDictionary, Count, First, Single, Sum, Average
var list = entities.Where(e => e.Layer == "0").ToList(); // 立即执行🚨 在没有
ToList()之前,LINQ 查询只是一个"查询定义"。如果你在foreach时修改了源集合,将抛出InvalidOperationException。当你需要多次遍历时,务必先.ToList()物化结果。
// 正确做法
var filtered = entities.Where(e => e.Layer == "WALL").ToList();
// 现在 filtered 是独立副本,可以安全地多次遍历和修改Revit / AutoCAD 中的 FILTERED 操作
LINQ 是内存操作,但 AutoCAD 和 Revit 有各自的过滤机制:
// AutoCAD:选择集过滤
var filter = new SelectionFilter(
new TypedValue[] { new TypedValue((int)DxfCode.Start, "LINE") }
);
var selection = ed.SelectAll(filter);
// Revit:元素收集器 + 过滤(类似 LINQ 的链式风格)
var walls = new FilteredElementCollector(doc)
.OfClass(typeof(Wall))
.WhereElementIsNotElementType()
.Cast<Wall>();💡 这些 API 的风格与 LINQ 一致(链式调用),但底层是数据库级别的过滤,而非内存迭代。在大型项目中,尽量使用这些原生过滤而非把全部元素加载到内存再 LINQ。