Skip to content
C#
集合与 LINQ

集合与 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。