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

记录类型与集合

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

记录类型回顾

这可能是本系列中最具普适价值的博客文章。虽然记录类型自C# 10就已存在,但我个人使用不多(尽管我期待使用它们已有十余年,这是另一回事了)。

决定将所有数据模型设为不可变后,在C#中使用记录类型(我全部使用密封记录)来实现这些模型几乎是理所当然的选择。只需用主构造函数相同的格式指定所需属性,编译器就会自动生成大量样板代码。

以简单示例为例,考虑以下记录声明:

csharp 复制代码
public sealed record Candidate(int Id, string Name, int? MySocietyId, int? ParliamentId);

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

csharp 复制代码
public sealed class Candidate : IEquatable<Candidate>
{
    // 属性声明、构造函数、Equals、GetHashCode等完整实现
    // 包含解构方法和with表达式支持
}

(此处保留了完整的类结构说明但压缩了具体实现细节)

记录类型的相等性比较

如上所示,记录类型默认使用EqualityComparer<T>.Default对每个属性进行比较。当属性类型的默认比较器符合需求时这很完美------但并非总是如此。在我们的选举数据模型中,大多数类型没问题,但ImmutableList<T>不符合要求,而我们大量使用了它。

ImmutableList<T>本身没有重写Equals和GetHashCode方法------因此具有引用相等语义。我真正需要的是使用元素类型的相等比较器,判断两个不可变列表在元素数量相同且元素成对相等时视为相等。这很容易实现------连同合适的GetHashCode方法。虽然可以包装成实现IEqualityComparer<ImmutableList<T>>的类型,但我目前尚未这样做。

遗憾的是,C#记录类型的工作方式无法为特定属性指定相等比较器。如果直接实现Equals和GetHashCode方法,这些自定义版本会替代生成版本,但意味着需要为所有属性实现比较逻辑。添加新属性时必须记得修改这两个方法(我至少忘记过一次)------而如果使用默认生成实现,添加新属性就非常简单。

引用相等比较

根据我关于数据模型的文章,在单个ElectionContext中,我们只需要引用相等。网站永远不需要通过从一个上下文指定另一个上下文的Constituency来获取2024年选举的选区结果。实际上,如果发现有这样的代码,很可能意味着存在bug:任何给定Web请求中的所有内容都应引用相同的ElectionContext。

因此,当创建ImmutableDictionary<Constituency, Result>时,我希望提供仅执行引用比较的IEqualityComparer<Constituency>。虽然这看起来简单,但我发现当上下文重新加载时,这对构建视图模型的时间有显著影响。

我原以为在框架中很容易找到引用相等比较器------但如果真有,我错过了。

更新 :感谢Michael Damatov指出,框架中确实存在System.Collections.Generic.ReferenceEqualityComparer------但它实现的是非泛型的IEqualityComparer<object>。我愚蠢地忽略了IEqualityComparer<T>的逆变特性:存在从IEqualityComparer<object>到任何类类型X的IEqualityComparer<X>的隐式引用转换。

字符串序数比较

字符串比较让我紧张。虽然默认字符串比较对Equals和GetHashCode使用序数比较,但对CompareTo使用文化敏感比较。由于我几乎总是想要序数比较,因此喜欢明确指定。为此我创建了一系列扩展方法:

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

Visual Studio中的主构造函数和记录"调用层次结构"问题

在Visual Studio中,对主构造函数和记录参数,"查找引用"(Ctrl-K, Ctrl-R)有效但"调用层次结构"(Ctrl-K, Ctrl-T)无效。虽然可以理解主构造函数参数不支持,但记录参数最终成为属性,我期望能像其他属性一样查看调用层次结构。

更令人沮丧的是无法查看"调用构造函数"的层次结构。由于类/记录声明某种程度上也充当构造函数的声明,我原以为将光标放在类/记录声明上(在名称上)会起作用。

功能需求总结

总结来说,虽然我喜欢记录类型和不可变集合,但可以通过引入以下内容减少摩擦:

  1. 控制生成代码中每个属性使用的相等比较器的方式
  2. 不可变集合的相等比较器,能指定元素比较方式
  3. 执行引用比较的IEqualityComparer<T>实现
  4. 显示主构造函数和记录构造函数调用的"调用层次结构"

结论

我在记录类型和集合中发现的一些问题至少某种程度上特定于我的选举网站,尽管我强烈怀疑我不是唯一在记录中使用不可变集合并希望在相等比较中使用它们的开发者。

总体而言,记录类型在网站中表现良好,我很高兴它们可用,即使仍有改进空间。同样,能自然使用不可变集合也很棒------但在执行比较时提供更多帮助会更好。

相关推荐
星云低代码开发平台9 小时前
MES 延误归因的技术路径:计划、物料、报工与质量记录
人工智能·权限管理·系统集成
邵奈一9 小时前
HyperFrames上手:可编辑视频工程
aigc·视频制作·hyperframes
一木 之林9 小时前
Agent智能体学习总结:从一次调用到模型自主规划、工具调用与 ReAct 循环的项目实战-6/6
前端·人工智能·学习·react.js
小蒋观天下9 小时前
【安防AI工程实战二】AI摄像头帧率抖动根治:ISP曝光时序与AI推理时序冲突深度定位
人工智能·深度学习·算法·安全·机器学习·ai大模型
前端.火鸡9 小时前
大模型概念地图:所有术语分别站在哪一层
人工智能
打工仔折腾 AI10 小时前
鸿蒙平板远控电脑实测:ToDesk蓝牙键鼠、双模式鼠标与跨端剪贴板落地记录
人工智能·后端·python·性能优化·计算机外设·电脑·harmonyos
云和数据.ChenGuang10 小时前
trace 是RAG检索链路的**全链路追踪日志对象
人工智能·深度学习·算法·排序算法
鼓掌MVP10 小时前
决定全案的智能Agent
大数据·人工智能·机器学习
lank_M10 小时前
Word裁剪图片转HTML走样,该在渲染层修还是入口修
前端·图像处理·人工智能·计算机视觉·html·word
IT_陈寒10 小时前
深入理解Redis:核心原理与最佳实践
前端·人工智能·后端