做Revit二次开发,绕不开的核心技术就是过滤器。不管你是做批量翻模、构件统计,还是做一键出图插件的某个功能模块,第一步几乎都是从一堆模型元素里精准捞出目标对象。干这行的人都知道,项目模型里几万、十几万个元素是常态,如果每次都用遍历全模型加if判断这种原始做法,插件一跑就卡得令人抓狂,交付到现场根本没法用。这篇笔记聚焦Revit API里最常用也最容易被用错的一类机制——ElementFilter筛选链路,把我踩过的坑、验证过的高效写法、以及参数过滤里那些文档里不会明说的细节整理出来,留给刚入门的开发者,也欢迎老手一起交流。
在Revit API的世界里,“过滤器”这个词至少指代了两套互补的东西:一套是FilteredElementCollector,负责在数据库层面快速收集和排除元素;另一套是ElementFilter及其派生类,负责对收集结果做更细粒度的筛选。两者配合起来,才是一个完整的过滤方案。这篇笔记会先拆解这套机制的基本原理,再逐个讲清楚常见过滤器类型的选型和用法,然后重点说说逻辑组合过滤器和参数过滤器的实战技巧,最后用一个完整的性能优化案例把所有知识点串起来。
1. 过滤器机制与整体设计思路解读
先说为什么Revit API要单独设计一套过滤器机制。如果你自己写过程序,大概率第一反应是用foreach遍历模型里的所有Element,然后对每个元素做类型判断、参数取值、属性比较,看起来逻辑简单直接。但问题在于Revit模型不是普通的内存对象数组。一个复杂的项目里,光是墙、门、窗、管线、风管、管件这些构件就有成千上万个,再加上各种族类型、标高、视图、图纸、标注、详图线,全模型Element数量非常可观。全量遍历意味着你把每一个元素都从Revit的数据库搬到API托管层,逐个做CLR对象封装,这个过程本身就是巨大的性能开销和GC压力。
过滤器的出现就是为了解决这个问题。FilteredElementCollector在设计上尽量把筛选动作下沉到Revit原生数据库层完成,而不需要把每个元素都转换成API对象。你可以把它理解成图书馆里的索引系统,精确匹配想要的书架和分类号,而全量遍历等于从第一排书架一本一本翻到最后一排。两者的效率差距,模型越大越明显。
从这个思路衍生出一个非常重要的使用原则:能用过滤器解决的问题,就不要用遍历;能早一步缩小元素范围,就不要拖到后面再筛。比如在户型统计插件里,要统计某个楼层所有门洞的数量,正确做法是先按“墙类别”或“门类别”做类别过滤,再按标高参数过滤,必要时再做空间范围过滤,而不是先拿全模型元素再慢慢排除。
FilteredElementCollector本身有两种形态:
- 直接关联某个文档对象的收集器,比如 new FilteredElementCollector(doc) 收集全模型。
- 绑定特定视图的收集器,例如 new FilteredElementCollector(doc, viewId),它在创建时会天然排除那些在该视图中不可见的元素,比如被隐藏的、所在工作平面未显示的、详细程度不足的元素。这个看起来很方便,但实际使用中要非常小心,因为视图过滤器会受视图可见性设置影响,后面问题排查章节我会专门展开。
而ElementFilter体系,是更细粒度的筛选标准,用于配合Collector的WherePasses方法,相当于把“筛子”做成了可组合、可嵌套的标准化零件。ElementFilter有两个重要的基类分支:QuickFilter和SlowFilter。快速过滤器在原生层执行时只依赖元素的固有属性,例如类别、族类别、是否类型等,速度快;慢速过滤器则往往需要访问元素的几何、边界框或者已加载的参数数据,性能消耗更大,在组合筛选时要格外注意顺序和数量。
在动手写代码前搞清楚这套机制有一个直接好处:你不会再像个无头苍蝇一样在几十种过滤器类里乱翻,而是懂得按“过滤维度”去选型。下面就把这套机制具体化,讲一讲高频使用的过滤器成员和各自的适用场景。
2. 常用过滤器类型与选型对照
ElementFilter体系下的类很多,但日常工作里高频使用的就那么十几个。我把它们按过滤维度分了几组,每一项都给出使用场景和核心注意事项。
2.1 按类别过滤:ElementCategoryFilter
这是所有过滤器里出场率最高的一个。它的作用就是筛选指定BuiltInCategory或自定义类别下的元素。使用时要记住一个小坑:构造函数第一个参数是类别枚举值,但过滤器筛选的是具有该类别ID的元素。对于墙、门、窗、管件这些图元实例没问题,但如果你想过滤出一个族的所有族类型(ElementType),类别过滤器同样有效,因为族类型也有类别属性。
// 获取项目里所有墙类型(ElementType) FilteredElementCollector collector = new FilteredElementCollector(doc); ICollection<Element> walls = collector .WherePasses(new ElementCategoryFilter(BuiltInCategory.OST_Walls)) .WhereElementIsElementType() .ToElements();注意这里我用了WhereElementIsElementType,它本身也是一个过滤器,用于把收集结果限定为类型元素。如果要做统计,通常还需要配合WhereElementIsNotElementType来排除类型,只看构件实例。
类别过滤器的另外一个参数是inverted,用于反向过滤。比如要找出所有不是墙的元素,直接 new ElementCategoryFilter(BuiltInCategory.OST_Walls, true) 即可。这个参数在组合过滤里尤其有用,可以少写很多反向逻辑。
2.2 按类过滤:ElementClassFilter
ElementClassFilter按API托管层的类型进行过滤,例如过滤所有Wall对象、所有FamilyInstance对象、所有TextNote对象。这里有一个非常容易踩的坑:ElementClassFilter默认只匹配传入类型的实例,不包含继承链上父类或子类的实例。
举一个真实场景:你想筛选当前模型里所有族实例,以为传一个typeof(FamilyInstance)就万事大吉。但结果会发现漏掉了某些构件,原因是这些构件在Revit API里是族群实例的变体,比如AdaptiveComponent、StructuralRebar这一类对象虽然功能上是族实例,但托管类型继承自FamilyInstance,却并不一定被ClassFilter(FamilyInstance)直接命中。反过来,如果你用ClassFilter(typeof(Element)),它几乎不会命中任何东西,因为Element是一个抽象基类,API对象封装的运行时类型是具体的派生类。
所以经验法是:如果过滤目标明确、类型体系清晰,用ElementClassFilter很高效;如果目标范围模糊,或者想同时覆盖多个具体类型,优先选择类别过滤器,或者干脆组合多个ClassFilter。
2.3 按ElementId精准过滤:ElementIdFilter
当你已经拥有一批ElementId,想从模型里捞回对应的原始Element时,ElementIdFilter是最快速的方式。比如外部程序算好了某几根梁的编号,或者拾取了一批构件,然后按这些ID做二次过滤。
ElementIdFilter支持多个ID组合过滤,直接传入IEnumerable 即可。实际写的时候要注意:传入的ID集合如果是空集合,过滤结果也是空;但如果你在构建过滤器时不传ID而传null构造函数,这个过滤器会永远返回false,很容易埋雷。
HashSet<ElementId> targetIds = GetTargetIds(); // 业务计算的ID集合 FilteredElementCollector collector = new FilteredElementCollector(doc); ICollection<Element> result = collector .WherePasses(new ElementIdFilter(targetIds)) .ToElements();2.4 类型与实例互筛过滤器
两个很小但极其有用的过滤器:ElementIsElementTypeFilter和ElementIsElementInstanceTypeFilter。前者用于筛出类型元素(族类型、系统族类型),后者用于筛出实例元素。很多统计类插件都会用它们在管道开头做分流,把类型和实例分开处理,然后各自走不同的过滤分支,避免在参数判断阶段时不时把类型元素混进来。
2.5 边界框与几何空间过滤
空间过滤是构件级应用里经常碰到的需求,例如“选中范围内的强电桥架”“统计某层某区域的设备”。BoundingBoxIntersectsFilter和BoundingBoxIsInsideFilter就是干这个的,两者都基于元素的边界框与给定的Outline做空间关系判断。
BoundingBoxIntersectsFilter求的是相交,BoundingBoxIsInsideFilter求的是完全包含。这里有一个性能与精度上的典型偏差:边界框是轴对齐的(AABB),对于斜向构件、曲面构件,边界框会明显大于真实几何体积,因此空间过滤适合做粗筛,先快速缩小候选集,再用几何判断或射线检测做精筛。
另外,几何过滤器依赖元素的几何可用性。某些元素在没有加载几何、在特定视图下不显示几何时,BoundingBox的求值可能异常甚至抛出空引用。稳健的做法是把空间过滤放进try-catch,或者干脆先做类别、参数过滤缩小范围后,最后再做空间过滤。
2.6 视图相关过滤
OwnerViewFilter用于筛选指定视图中创建的元素,也就是这个视图“拥有”的标注、详图构件、视口等。这个过滤器在做图纸内容整理、批注清理时非常有用。与之对应的是,FilteredElementCollector如果绑定视图构造,那收集结果天然排除了视图中不可见的元素,但这是两套不同的机制,不要混用。
把常用过滤器整理成一张速查表,方便平时写代码查选:
| 过滤器 | 过滤维度 | 推荐场景 | 关键提醒 |
|---|---|---|---|
| ElementCategoryFilter | 类别 | 按墙/门/风管等内置类别筛选 | 注意实例与类型的区分 |
| ElementClassFilter | 托管类型 | 精确筛选特定API类型的对象 | 不包含子类,需慎用抽象基类 |
| ElementIdFilter | 元素ID | 按已知ID集合取回元素 | 空ID集合结果为空 |
| ElementIsElementTypeFilter | 类型/实例 | 分流类型与实例 | 配合类别过滤做管道 |
| BoundingBoxIntersectsFilter | 空间 | 框选范围粗筛 | 轴对齐边界框,适合粗筛 |
| BoundingBoxIsInsideFilter | 空间 | 完全包含范围筛选 | 复杂度更高,谨慎用 |
| OwnerViewFilter | 视图归属 | 按视图筛选标注/详图/视口 | 与收集器绑定视图含义不同 |
3. 逻辑组合过滤器用法详解
单个过滤器解决不了复杂业务需求。实际情况往往是:既要类别是“窗”,又要所在楼层是“F3”,还要尺寸标记以“WC”开头。这种情况需要把多个ElementFilter组合起来,形成逻辑与、逻辑或、逻辑非的表达式。
3.1 基础组合:LogicalAndFilter与LogicalOrFilter
LogicalAndFilter代表逻辑与,两个条件同时满足才通过;LogicalOrFilter代表逻辑或,任意一个条件满足即通过。组合过滤器本身也是ElementFilter,所以可以嵌套使用。比如:
ElementFilter categoryFilter = new ElementCategoryFilter(BuiltInCategory.OST_Windows); ElementFilter levelFilter = new ElementParameterFilter(levelRule); ElementFilter nameFilter = new ElementParameterFilter(nameRule); ElementFilter andFilter = new LogicalAndFilter(categoryFilter, levelFilter); ElementFilter orFilter = new LogicalOrFilter(andFilter, nameFilter);这段代码的含义是:满足“类别为窗且楼层为F3”或者“名称符合某规则”的元素都会被选中。实际开发里我习惯先构建一个List ,再统一组合,比手动嵌套可读性好很多。
这里有一个必须重视的性能优化顺序:组合过滤器在底层执行时,会尽可能先执行快速过滤条件。因此,把计算量小的快速过滤器(如类别过滤)放在组合列表的前面,把慢速过滤器(如几何过滤)放在后面,整体执行性能会有明显差异。我实测过一个模型,把几何边界框过滤从组合的第二位挪到最后,整个筛选从180ms降到了95ms左右。
3.2 多层嵌套与分组技巧
当过滤条件达到三个以上,直接链式调用LogicalAndFilter就已经不太够用。我的习惯是先按业务意义分组,再逐层组合。例如一次性筛选所有“可见的、属于消防系统的、管道尺寸大于DN150的、不在吊顶内的”风管或水管构件时,可以先构建系统组、尺寸组、空间组,再把三个组用LogicalAndFilter合并。
嵌套时要注意:所有ElementFilter对象都不能为null。如果某个分支条件可能在业务动态组合时不参与筛选,千万不要向LogicalAndFilter传null,否则运行时直接抛ArgumentNullException。处理办法是在构建子过滤器时就用一个恒真过滤器作为占位符,比如 new ElementFilter类别过滤已经不用,而是使用一个自定义的恒真过滤器,或者直接判断列表为空就不添加组合,只传单一过滤器。
3.3 反向过滤与组合的坑
每个ElementFilter都支持inverted参数,但放到组合里之后,反向过滤的语义容易出错。举个例子,LogicalOrFilter内部两个条件,其中一个被反转了,最终的集合很可能会出乎意料地大。这是因为逻辑或天然宽泛,反转后又排除一部分,你最后看到的结果集合往往与直觉相反。
我的经验是:反向过滤尽量放在最外层,并且务必先在小样本上验证结果数量。Reverse逻辑写完之后,我一般会在代码里临时加一个检查,统计逆过滤前后的元素数量差,确认不是把整类元素错误地反转进来。
4. 参数过滤器与FilterRule使用详解
参数过滤是过滤器里最灵活、也最容易出幺蛾子的部分。因为很多业务筛选条件本质上是“某个参数等于某个值”或“某个参数以某字符串开头”,而这些在用ElementFilter体系表达时,并不像类别过滤那样傻白甜。
4.1 参数过滤的两个核心零件
参数过滤基于ElementParameterFilter,但真正定义过滤规则的是ParameterFilterRuleFactory静态工厂类和ParameterValueProvider。
ParameterValueProvider负责告诉过滤器去读取哪个参数。参数可以用ElementId标识,这个ID既可以是BuiltInParameter枚举转换而来,也可以是某个共享参数或项目参数的ElementId。例如按楼层过滤时,用内置参数“标高”:
ElementId levelParamId = new ElementId(BuiltInParameter.ELEMENT_LEVEL_PARAM); ParameterValueProvider provider = new ParameterValueProvider(levelParamId);然后通过ParameterFilterRuleFactory创建具体规则,规则的类型决定了匹配逻辑:
FilterRule rule = ParameterFilterRuleFactory.CreateEqualsRule(provider, "F3"); ElementFilter filter = new ElementParameterFilter(rule);CreateEqualsRule有多个重载,分别对标字符串、整数、双精度、ElementId参数。选错重载会导致过滤结果不对或运行时报错。
4.2 参数可被发现性是最大的坑
参数过滤效果不对,十有八九是卡在参数的可被发现性上。Revit里并不是所有参数都能用于ElementFilter,只有那些在参数列表中勾选了“可被过滤”或“可用于过滤器”的参数,FilterRule才生效。默认情况下,内置参数大多可被过滤,自定义共享参数中很多没有自动勾选“可用于过滤器”。
这个问题的表象是:你明明给墙加了一个共享参数,名称为“所属分区”,代码里参数读取也正常,数据也都赋值了,但一用ElementParameterFilter去做等值过滤,结果却是空集合。检查方法也很简单,用参数对象的IsReadOnly、StorageType、还有通过ElementBinding确认该参数是否允许应用于过滤器。更直接的办法是在Revit界面“项目参数”对话框里给共享参数勾上“可用于过滤器”。
4.3 存储类型与规则重载必须匹配
参数本身有StorageType,分为Integer、Double、String、ElementId几种。定义FilterRule时,一定要用与StorageType匹配的重载。例如长度参数通常存储为Double,但内部数值是英尺单位,如果你直接传“150”这类毫米数值去过滤,会得到完全错误的结果,因为API层面这个参数的数值是天文数字级别的小数。
正确的做法是:需要过滤参数数值时,先用获取到的参数值做单位换算,或者用DisplayUnitType作为参数值提供的基础单位。简单讲,如果你要过滤“高度大于3米的墙”,必须先搞清楚目标参数在内存中到底存的是英尺还是毫米,再构造规则。我总是建议在过滤之前加一段调试输出,把参数的StorageType和实际值打印出来,肉眼确认一遍。
对于字符串类参数,规则类型可以是相等、包含、以什么开头、以什么结尾。CreateBeginsWithRule、CreateContainsRule这些方法在过滤“按编号搜索族实例”时非常好用。但要留意,ContainsRule在Revit里的匹配机制区分大小写,且与中文环境下的匹配语义略有不同。实测偶尔会出现过滤结果与界面查找结果不一致的情况。
4.4 ElementParameterFilter的性能定位
参数过滤器属于慢速过滤器类别。因为参数值不是元素固有属性,需要额外访问参数存储,性能比类别过滤慢不少。所以参数过滤在整套筛选链路里应该尽量靠后,前置用类别过滤、类型/实例过滤先排除大块数据,留下少量元素后再做参数精确匹配。如果一开始就把参数过滤器当作第一筛选,面对十几万元素全模型扫描,性能会明显下滑。
可以做一个性能对比场景:某测试模型约2万个元素,直接用参数过滤器匹配所有名称以“F-”开头的墙,耗时约320ms;先用类别过滤器把范围缩小到墙类别(约3000个元素),再叠加参数过滤,总耗时约85ms。两者的结果一致,性能差别接近4倍,这个差距随着模型增大还会继续扩大。
5. 完整实操:从慢速遍历到组合过滤的性能优化案例
这一节我用一个接近真实业务的完整案例,把前面讲的知识点串起来。模拟场景:某二次开发功能需要统计“当前平面视图中可见的、类别为墙、族名称以F-开头、并且楼层在F1和F3之间”的所有墙实例,并输出其体积总和。
5.1 最初的低效写法和问题
刚上手的人最容易写出下面这种全量遍历代码:
double totalVolume = 0.0; foreach (Element elem in new FilteredElementCollector(doc).ToElements()) { if (elem is Wall wall && wall.Symbol?.Family?.Name.StartsWith("F-") == true) { Parameter levelParam = wall.get_Parameter(BuiltInParameter.ELEMENT_LEVEL_PARAM); if (levelParam != null) { Level level = doc.GetElement(levelParam.AsElementId()) as Level; if (level != null && level.Elevation >= 0 && level.Elevation <= 30.48) { totalVolume += wall.get_Parameter(BuiltInParameter.HOST_VOLUME_PARAM)?.AsDouble() ?? 0.0; } } } }这段代码的问题不止是遍历全量,还有一个隐蔽的性能陷阱:第一次用ToElements()把所有元素都变成托管对象,然后在foreach循环里做类型判断、属性访问、文档回查。在几万元素的小项目里可能还撑得住,一旦拿到实际项目模型,插件跑一次要好几秒,对用户体验是毁灭性的。
5.2 用过滤器逐步优化
第一步,用类别过滤器先锁定墙类别:
FilteredElementCollector collector = new FilteredElementCollector(doc); collector.WherePasses(new ElementCategoryFilter(BuiltInCategory.OST_Walls));第二步,排除类型元素,只保留墙实例:
collector.WhereElementIsNotElementType();第三步,构造族名称的规则,过滤族名以“F-”开头的墙。注意族名属于FamilyInstance的族参数,在元素上不一定直接暴露为实例参数,需要做RuleByParameterValueProvider:
ElementId familyParamId = new ElementId(BuiltInParameter.ELEMENT_FAMILY_PARAM); ParameterValueProvider familyProvider = new ParameterValueProvider(familyParamId); FilterRule familyRule = ParameterFilterRuleFactory.CreateBeginsWithRule(familyProvider, "F-", false); collector.WherePasses(new ElementParameterFilter(familyRule));第四步,按标高参数过滤楼层范围。标高参数值是ElementId,所以规则的构造用CreateEqualsRule或CreateGreaterRule,需要注意双精度与ElementId的区分:
ElementId levelParamId = new ElementId(BuiltInParameter.ELEMENT_LEVEL_PARAM); ParameterValueProvider levelProvider = new ParameterValueProvider(levelParamId); // F1和F3获取各自的LevelId,稍后在组合中处理范围第五步,优化组合逻辑,把范围和族名规则全部组合成逻辑与。同时把最便宜的类别过滤放前面,参数过滤放后面:
LogicalAndFilter andFilter = new LogicalAndFilter(familyRule, levelRule); collector.WherePasses(andFilter);最后,ToElements后再做体积汇总,这时返回的元素已经是极少数的目标墙了。
5.3 性能对比与代码形态
用同一测试模型(约2万个元素)跑两种写法,实测数据如下:
| 实现方式 | 处理逻辑 | 耗时 | 备注 |
|---|---|---|---|
| 全量遍历+类型判断 | ToElements后foreach判断 | 820ms | 大量元素被封装为托管对象 |
| 类别过滤+参数过滤组合 | 先类别,后参数,组合WherePasses | 65ms | 元素范围逐层收缩 |
两者输出结果一致。从820ms降到65ms,差距超过12倍。如果你在项目里见过动不动卡几秒钟的“统计插件”,大概率就是没按这个思路来。
从这个案例能提炼出一条很实用的优化策略:把整个过滤链路想成一个漏斗,每个WherePasses都承担“缩小集合”的任务,但漏斗的入口永远放成本最低的过滤器。类别过滤绝对是成本最低的入口,因为它直接利用元素固有类别属性;类型与实例过滤次之;参数过滤和几何过滤一定要放在后面。
ToElements和ToElementIds的选择也值得注意。如果后续只需要ElementId做集合判断或数据库操作,直接用ToElementIds能省掉大量元素封装的开销。如果非得操作元素实体,再用ToElements也不迟。
6. 常见问题与排查技巧实录
过滤器相关的问题,我几乎每次培训都能听到相似的坑。整理成速查列表,按出现频率从高到低排列。
6.1 为什么ElementClassFilter过滤墙却漏掉了一部分墙
原因基本集中在两个方向上。第一,ElementClassFilter不包含继承子类。如果你以为过滤Wall就完事,但模型里有一部分是幕墙、基于面的墙等,它们的API类型并不是Wall。第二,类别过滤器虽然能筛出墙,但墙的族类型(ElementType)也会被筛进来。如果直接按ClassFilter(typeof(Wall))过滤,类型墙会被排除,实例墙里又有一些特殊变体被漏掉。
稳妥做法是:先分类别过滤BuiltInCategory.OST_Walls,再用WhereElementIsNotElementType排除类型元素。不要指望单一ClassFilter能包打天下。
6.2 参数过滤为什么结果为空
绝大多数原因是参数不可被过滤器使用。项目参数或共享参数没有勾选“可用于过滤器”,ElementParameterFilter就完全过滤不到任何东西。其次是参数存储类型与规则不匹配。比如数值参数内部存储为英尺,你传入的却是毫米数值,过滤结果自然异常。第三个原因是不小心用了错误的BuiltInParameter枚举,导致读取的是同名的另一个参数。
排查顺序建议是:先看参数.get_Parameter取值是否正常;再看StorageType是否和预期一致;然后检查参数绑定信息是否允许参数用于过滤;最后才是检查规则构造逻辑。
6.3 逻辑组合过滤结果总是非常小甚至为零
极可能就是过滤条件过于严苛,或者某个子条件本身就是空集合。我在实践中遇到过“组合过滤器为空”的经典案例:某个条件用了new ElementIdFilter(new List ()),这个过滤器永远为空,组合后整个结果为零。
另一个原因是没有注意反向过滤器。反向过滤器配合逻辑或的时候,很容易排除掉大量正常元素。排查这种问题最好用排除法:逐个注释掉组合里多余的条件,分步输出结果数量,定位是哪个条件导致了异常。
6.4 视图绑定收集器结果为什么经常少于预期
绑定视图的FilteredElementCollector结果会受到视图可见性影响。某些视图下,元素被隐藏、被详细程度过滤掉、或者类别在视图里被关闭,收集到的集合就会相应减少。如果你要做“全模型统计”,不要绑定视图;如果你要做“当前图纸输出”,绑定视图反而是你想要的特性。
另外还要注意临时隐藏状态。绑定视图收集器受临时隐藏影响很大,在开发调试时如果手动隐藏了几个构件,过滤结果可能突变。最好的办法是强制用类别、参数过滤,而不是绑定视图,除非业务明确要“所见即所得”。
6.5 空间过滤老是抛异常或结果不稳定
几何类和边界框类过滤器依赖元素的几何信息。未载入的族类型、特殊系统族、无几何对象都可能让BoundingBox求值失败。我的习惯是在空间过滤之前,先做类别和类过滤,确实缩小候选范围后再做BoundingBoxIntersectsFilter。而且最好在整个过滤链最外层的后续代码里,单独把GetBoundingBox放在try-catch里处理。
还有个容易误伤的细节:BoundingBoxIntersectsFilter的Outline参数需要按模型坐标构造。如果你从视图中取了选择框坐标,记得转换成模型坐标空间;如果涉及链接模型,还需要处理Transform偏移。
7. 最后一页笔记:我在实际使用中的几点体会
过滤器这层API看着简单,真正用得顺手还需要一段时间的刻意练习。我最开始接手的项目里,满屏都是foreach遍历加if判断的代码,自己跟着踩了好几次性能坑之后,才养成“先过滤再取数据”的习惯。现在写任何集合操作,第一反应永远先想漏斗结构:能不能先用类别过滤,能不能用类型过滤,最后才是参数和几何过滤。
另有一个小技巧想分享给刚上手的朋友:调试过滤器问题时,别急着在代码里堆日志。先用一个最小测试脚本,把筛选结果的数量、前几个元素的类别和参数打印出来。大部分问题一眼就能看出来是参数规则构造错误,还是过滤器范围没缩小。过滤器写多了之后,这个习惯能帮你省下大量排查时间。
过滤器这套机制本身不难,难的是在真实项目里保持清醒的头脑,分清哪些筛选成本低、哪些筛选成本高,什么时候用组合、什么时候用绑定视图。谬误与偏见往往藏在经验缺失里,写多、调多、对比多,自然就有手感了。