记录类型与集合
本文某种程度上记录了我在选举站点中使用记录类型和集合时遇到的各种摩擦点。
记录类型回顾
这可能是本系列中最具普适性的博客文章。尽管记录类型自C# 10就已存在,但我个人使用不多(虽然我期待这个功能已有十多年,不过那是另一回事)。
决定将所有数据模型设为不可变后,在C#中使用记录类型(我始终使用密封记录)来实现这些模型几乎是理所当然的选择。只需用主构造函数的语法指定所需属性,编译器就会自动生成大量样板代码。
简单示例如下:
|
|
这生成的代码大致等效于:
|
|
(为简洁起见省略了完整代码,实际还包含EqualityContract等细节)
记录类型的相等性比较
默认情况下,记录类型为每个属性使用EqualityComparer<T>.Default
。当属性类型的默认相等比较器符合需求时这很完美——但并非总是如此。在我们的选举数据模型中,大多数类型没问题,但ImmutableList<T>
不适用,而我们大量使用了这种类型。
ImmutableList<T>
本身没有重写Equals
和GetHashCode
——因此具有引用相等语义。我真正需要的是使用元素类型的相等比较器,判断两个不可变列表是否具有相同数量的元素,且元素按顺序成对相等。
遗憾的是,C#记录类型目前无法为单个属性指定自定义相等比较器。如果手动实现Equals
和GetHashCode
方法,就需要为所有属性实现比较逻辑——这意味着添加新属性时必须记得更新这些方法(我就至少忘记过一次)。
我期望的解决方案是能够通过特性指示编译器使用指定的相等比较器提供程序。例如:
|
|
引用相等性
在我的数据模型文章中提过,在单个ElectionContext
中,我们只需要引用相等性。创建ImmutableDictionary<Constituency, Result>
时,我希望提供仅执行引用比较的IEqualityComparer<Constituency>
。虽然这看似简单,但发现它显著影响了上下文重载时构建视图模型的时间。
.NET 5+实际上已经提供了System.Collections.Generic.ReferenceEqualityComparer
,但我最初错误地忽略了它——因为它实现了非泛型的IEqualityComparer<object>
。后来才意识到IEqualityComparer<T>
是逆变的,完全可以安全使用。
字符串序数比较
字符串比较总是让我紧张。虽然默认字符串比较对于Equals
和GetHashCode
是序数的,但对于CompareTo
是文化敏感的。由于我几乎总是想要序数比较,因此创建了一系列扩展方法使意图更明确,包括:
OrderByOrdinal
OrderByOrdinalDescending
ToImmutableOrdinalDictionary
(4个重载)ToOrdinalLookup
(2个重载)
Visual Studio工具链问题
在VS中使用"调用层次结构"(Ctrl+K, Ctrl+T)时:
- 主构造函数和记录参数的"调用层次结构"不工作(尽管"查找引用"可以)
- 无法查看"调用构造函数"的层次结构
功能需求总结
- 支持按属性控制生成的相等比较逻辑
- 为不可变集合提供内置相等比较器
- 提供泛型引用相等比较器实现
- 增强VS对记录类型的工具支持
结论
尽管存在这些摩擦点,记录类型在站点中表现良好,不可变集合的天然支持也很棒——只是在比较操作方面还需要更多支持。