C# Struct 与Class区别 一句话因为它是**「值对象 高频分配的字典键」**struct 在这个位置同时赢在三件事上——相等语义、分配成本、非空性。1. 相等语义为什么必须是 record而不是普通 class/structGroupBy/Lookup内部用的是EqualityComparerTKey.Default。分组键的核心需求是结构相等两个字段全同的键必须被认成同一个组。普通class默认是引用相等 → 每行都是新实例每个料号一行都自成一组建不出组。要修就得手写Equals/GetHashCode/IEquatableT三件套。recordclass 或 struct编译器自动生成基于字段的Equals/GetHashCode还顺带给你ToString、Deconstruct、with正好是调试分组结果时想要的东西。所以「record」解决的是相等语义问题「struct」解决的是下面两个。2. 分配与 GC为什么是 struct 而不是 record class这个键的实例数量 ≫ 不同键的数量库存明细有多少行GroupBy就要为每行做一次键查找、构造键值。record class每行一个堆对象。几万行库存就是几万个短期对象全是 GC young-gen 垃圾。record structGroupBy的泛型EqualityComparerTKey是强类型的struct 键不会被装箱键值直接内联在迭代器/查找逻辑里零堆分配。另外record struct自动实现了IEquatableTEqualityComparerT.Default会走强类型路径而不做虚调用/装箱比较。3. 不可能为 null引用类型键存在null这个额外状态等于给「该维度未参与聚合」再叠一层「键本身可能为空」的二义性。struct 天然非空配合你注释里写的「未参与聚合的维度一律取 null」这套形状约定语义是干净的。4. 为什么还要readonlyGetHashCode的稳定性是字典键的硬要求。字段可变的 struct 一旦被放进字典后再被改写哈希桶就废了。readonly把不可变性交给编译器强制同时避免编译器在只读上下文里插入防御性拷贝。代价 / 边界唯一要留意的反方向成本struct 是按值拷贝的。这里 4 个long?≈ 64 字节拷贝一次比堆分配 GC 追踪更划算所以选 struct 是对的但如果键字段再多比如十几个拷贝成本会开始追平甚至超过分配成本那时就该考虑换回 class或者把字段压成非空long用 0 表 null瘦身。一句话总结record负责「和别的键相等该怎么判」struct负责「别为每行库存造一个堆对象、也别让键可能是 null」。两件事缺一个都别扭。