Roslyn IIncrementalGenerator比较器失效问题排查
问题根源分析
问题出在record类型的默认相等性判断逻辑上:
ClassInformation的字段都是值类型或字符串(不可变引用类型),record的默认值相等性可以正常工作。InterfaceInformation包含List<ConstructorInformation>、IDictionary<string, ConstructorParameters>这类可变引用类型集合,还有递归的InterfaceInformation? Inherited引用。record的默认相等性对这些类型会使用引用相等(判断是否是同一个实例),而不是内容相等(判断集合里的元素是否完全一致)。
你看到JSON序列化后内容一致,是因为序列化会遍历集合的所有元素,但record的Equals方法不会自动做这种深层内容比较——只要两个集合是不同的实例,哪怕元素完全一样,默认Equals也会返回false。
解决方案
需要在GeneratorInformationComparer中手动实现InterfaceInformation的深层内容比较,替代record的默认Equals逻辑。以下是修改后的核心代码:
public bool Equals(GeneratorInformation x, GeneratorInformation y) { if (x == null || y == null) return x == y; // ClassInformation默认相等性正常工作 if (!x.ClassInformation.Equals(y.ClassInformation)) return false; var xInfo = x.InterfaceInformation; var yInfo = y.InterfaceInformation; // 比较基础字段 if (xInfo.Namespace != yInfo.Namespace || xInfo.ClassName != yInfo.ClassName || xInfo.GenerateSample != yInfo.GenerateSample) return false; // 比较集合类字段 if (!ListsAreEqual(xInfo.Constructors, yInfo.Constructors, ConstructorEquals)) return false; if (!ListsAreEqual(xInfo.Properties, yInfo.Properties, ClassMemberEquals)) return false; if (!ListsAreEqual(xInfo.Fields, yInfo.Fields, ClassMemberEquals)) return false; // 递归比较继承的接口信息 if (!InterfaceInfoEquals(xInfo.Inherited, yInfo.Inherited)) return false; return true; } // 辅助方法:比较两个List的内容 private bool ListsAreEqual<T>(List<T> x, List<T> y, Func<T, T, bool> elementEquals) { if (x.Count != y.Count) return false; for (int i = 0; i < x.Count; i++) { if (!elementEquals(x[i], y[i])) return false; } return true; } // 比较ConstructorInformation内容 private bool ConstructorEquals(ConstructorInformation x, ConstructorInformation y) { if (x.Accessibility != y.Accessibility) return false; if (x.ConstructorParameters.Count != y.ConstructorParameters.Count) return false; foreach (var kvp in x.ConstructorParameters) { if (!y.ConstructorParameters.TryGetValue(kvp.Key, out var yParam) || !kvp.Value.Equals(yParam)) return false; } return true; } // 比较ClassMember内容(依赖record默认值相等) private bool ClassMemberEquals(ClassMember x, ClassMember y) { return x.Equals(y); } // 递归比较InterfaceInformation private bool InterfaceInfoEquals(InterfaceInformation? x, InterfaceInformation? y) { if (x == null && y == null) return true; if (x == null || y == null) return false; return Equals(new GeneratorInformation(default!, x), new GeneratorInformation(default!, y)); } // 同步修改GetHashCode,基于内容生成哈希值 public int GetHashCode(GeneratorInformation obj) { unchecked { int hash = obj.ClassInformation.GetHashCode(); var info = obj.InterfaceInformation; hash = hash * 397 ^ info.Namespace.GetHashCode(); hash = hash * 397 ^ info.ClassName.GetHashCode(); hash = hash * 397 ^ info.GenerateSample.GetHashCode(); hash = hash * 397 ^ GetListHashCode(info.Constructors, c => c.GetHashCode()); hash = hash * 397 ^ GetListHashCode(info.Properties, p => p.GetHashCode()); hash = hash * 397 ^ GetListHashCode(info.Fields, f => f.GetHashCode()); hash = hash * 397 ^ (info.Inherited?.GetHashCode() ?? 0); return hash; } } private int GetListHashCode<T>(List<T> list, Func<T, int> getElementHash) { unchecked { int hash = 17; foreach (var item in list) { hash = hash * 397 ^ getElementHash(item); } return hash; } }
关键注意点
- 对于List、Dictionary这类集合类型,必须手动比较内容,不能依赖record的默认Equals。
- 递归的
Inherited字段需要递归处理相等性,避免因引用不同导致判断失败。 GetHashCode必须同步修改为基于内容生成,否则哈希值不一致会导致相等性判断逻辑失效(IEqualityComparer规范要求:Equals返回true时,GetHashCode必须返回相同值)。
内容的提问来源于stack exchange,提问作者Webbert
相关产品推荐
相关产品推荐

