You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 15:15:10