C#记录类型与集合的深度解析:从默认行为到自定义比较器

本文深入探讨了C#记录类型与不可变集合在实际应用中的痛点,包括默认相等比较的局限性、自定义比较器的实现技巧,以及如何通过扩展方法优化不可变字典的构建性能。作者分享了在选举网站开发中积累的实战经验,并提出了对C#语言和IDE的功能改进建议。

记录类型与集合

本文某种程度上是我在选举网站开发中使用记录类型和集合时遇到的摩擦点汇总。

记录类型回顾

这可能是本系列中最具普适价值的博文。虽然C#从10版本就引入了记录类型,但我个人使用经验有限。(尽管我期待这个特性已有十余年,不过这是题外话了。)

决定将所有数据模型设为不可变后,使用密封记录类型来实现这些C#模型几乎是顺理成章的选择。只需用主构造函数的语法指定所需属性,编译器就会自动生成大量样板代码。

以这个简单记录声明为例:

1
public sealed record Candidate(int Id, string Name, int? MySocietyId, int? ParliamentId);

其生成的代码大致等效于:

1
2
3
4
5
public sealed class Candidate : IEquatable<Candidate>
{
    // 属性声明、构造函数、Equals、GetHashCode等完整实现
    // 包含解构方法和with表达式支持
}

(为简洁起见使用传统C#语法展示,实际生成代码可能使用主构造函数)

更妙的是,编译器还支持with表达式基于现有实例创建新实例:

1
var updated = original with { Id = 40, Name = "Jonathan" };

这一切都很美好——直到遇到特殊情况…

记录类型的相等性比较

如上所示,记录类型默认使用EqualityComparer<T>.Default比较各属性。当属性类型的默认比较器符合需求时没问题,但ImmutableList<T>等类型就不适用——我们大量使用了这类不可变集合。

ImmutableList<T>未重写EqualsGetHashCode,因此具有引用相等语义。我实际需要的是基于元素类型的比较器,当两个不可变列表长度相同且元素按顺序对应相等时视为相等。虽然实现这种比较器很容易,但C#记录机制目前无法为单个属性指定特定比较器。

理想情况下,希望能通过特性标注指定比较器:

1
2
3
4
public sealed record Ballot(
    Constituency Constituency,
    [IEqualityComparerProvider(typeof(CollectionEqualityProvider))] 
    ImmutableList<Candidacy> Candidacies);

引用相等比较

根据数据模型设计,在单个ElectionContext内我们只需要引用相等。创建ImmutableDictionary<Constituency, Result>时,我希望使用仅进行引用比较的IEqualityComparer<Constituency>,这能显著提升上下文重载时视图模型的构建效率。

.NET 5+其实提供了System.Collections.Generic.ReferenceEqualityComparer,但由于其实现的是非泛型IEqualityComparer<object>接口,我最初错误地忽略了它——忘了IEqualityComparer<T>的逆变特性。

字符串序数比较扩展方法

由于通常需要使用序数比较,我创建了一系列扩展方法使意图更明确:

  • OrderByOrdinal/OrderByOrdinalDescending
  • ToImmutableOrdinalDictionary(4个重载)
  • ToOrdinalDictionary(4个重载)
  • ToOrdinalLookup(2个重载)

Visual Studio对记录类型的支持问题

使用"调用层次结构"(Ctrl-K, Ctrl-T)时:

  1. 主构造函数参数无法查看调用链
  2. 记录参数转换的属性也无法查看
  3. 查看构造函数调用需要特殊操作

功能改进建议

总结记录类型和不可变集合的使用体验,建议引入:

  1. 按属性控制相等比较器的机制
  2. 不可变集合的专用比较器
  3. 泛型引用相等比较器
  4. 完善IDE对记录类型的工具支持

结论

虽然存在这些痛点,记录类型在网站开发中表现优异,不可变集合的内置支持也很棒。期待未来版本能进一步优化这些体验。

comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计