【问题标题】:Issues with operator resolution in value types (no references added)值类型中的运算符解析问题(未添加参考)
【发布时间】:2021-11-11 18:54:34
【问题描述】:

我正在做一个项目,我发现我使用的运算符和我声明的运算符不相等。

我做了一个最小可重现的例子:

var tree = CSharpSyntaxTree.ParseText(@"
bool a = 3 > 5;
namespace System{
    public struct Int32
    {
        public static extern bool operator > (int a, int b);
    }
    public struct Boolean { }
}");
var compilation = CSharpCompilation.Create("bla").AddSyntaxTrees(tree);
var model = compilation.GetSemanticModel(tree);

var usedSymbol     = model.GetSymbolInfo(tree.GetRoot().DescendantNodes().OfType<BinaryExpressionSyntax>().Single()).Symbol;
var declaredSymbol = model.GetDeclaredSymbol(tree.GetRoot().DescendantNodes().OfType<OperatorDeclarationSyntax>().Single());

Console.WriteLine(
    $"{declaredSymbol} and {usedSymbol} are {(declaredSymbol.Equals(usedSymbol) ? "" : "not ")}equal.");

// int.operator >(int, int) and int.operator >(int, int) are not equal.

See on .NET Fiddle!

为什么这些看起来相同的运算符不表明它们是相等的?

【问题讨论】:

  • From Object.Equals docs: "如果当前实例是引用类型,Equals(Object)方法测试引用相等,调用Equals(Object)方法相当于调用 ReferenceEquals 方法。”因此,这些类型很可能没有覆盖它们的 Object.Equals 方法,因此您的代码正在检查 reference 相等性而不是真正的对象相等性。
  • @SeanSkelly,不,我确信ISymbol 上的Equals 方法已被覆盖。 dotnetfiddle.net/iE0aRs

标签: c# roslyn microsoft.codeanalysis


【解决方案1】:

我修改了您的代码,并结合使用反射并查看 Roslyn 源代码,发现 usedSymboldeclaredSymbol 最终成为两种不同的符号类型。

        var tree = CSharpSyntaxTree.ParseText(@"
bool a = 3 > 5;
namespace System{
    public struct Int32
    {
        public static extern bool operator > (int a, int b);
    }
    public struct Boolean { }
}");
        var compilation = CSharpCompilation.Create("bla").AddSyntaxTrees(tree);
        var model = compilation.GetSemanticModel(tree);

        var usedSymbol     = model.GetSymbolInfo(tree.GetRoot().DescendantNodes().OfType<BinaryExpressionSyntax>().Single()).Symbol;
        var declaredSymbol = model.GetDeclaredSymbol(tree.GetRoot().DescendantNodes().OfType<OperatorDeclarationSyntax>().Single());

        Type used = usedSymbol.GetType();
        Type declared = declaredSymbol.GetType();

        var usedUnderlying = used.GetField("_underlying", BindingFlags.NonPublic | BindingFlags.Instance);
        var usedUnderlyingValue = usedUnderlying.GetValue(usedSymbol);
        var declaredUnderlying = declared.GetField("_underlying", BindingFlags.NonPublic | BindingFlags.Instance);
        var declaredUnderlyingValue = declaredUnderlying.GetValue(declaredSymbol);

        Type usedSymbolType = usedUnderlyingValue.GetType(); //SynthesizedIntrinsicOperatorSymbol
        Type declaredSymbolType = declaredUnderlyingValue.GetType(); //SourceUserDefinedOperatorSymbol

        Console.WriteLine(usedSymbolType.ToString());
        Console.WriteLine(declaredSymbolType.ToString());

        Console.WriteLine(
            $"{declaredSymbol} and {usedSymbol} are {(declaredSymbol.Equals(usedSymbol) ? "" : "not ")}equal.");

符号的两种表示的类型不匹配。一种是 SynthesizedIntrinsicOperatorSymbol,另一种是 SourceUserDefinedOperatorSymbol。归根结底,这就是为什么平等不起作用的原因——这两种类型似乎都没有实现。

例如,SynthesizedIntrinsicOperatorSymbol 的相等性会进行类型检查,在此用例中会失败:

    public override bool Equals(Symbol obj, TypeCompareKind compareKind)
    {
        if (obj == (object)this)
        {
            return true;
        }

        var other = obj as SynthesizedIntrinsicOperatorSymbol;

        if ((object)other == null)
        {
            return false;
        }

        if (_isCheckedBuiltin == other._isCheckedBuiltin &&
            _parameters.Length == other._parameters.Length &&
            string.Equals(_name, other._name, StringComparison.Ordinal) &&
            TypeSymbol.Equals(_containingType, other._containingType, compareKind) &&
            TypeSymbol.Equals(_returnType, other._returnType, compareKind))
        {
            for (int i = 0; i < _parameters.Length; i++)
            {
                if (!TypeSymbol.Equals(_parameters[i].Type, other._parameters[i].Type, compareKind))
                {
                    return false;
                }
            }

            return true;
        }

        return false;
    }

查看另一种类型,SourceUserDefinedOperatorSymbol,发现相等是在许多层深的基类上实现的:Symbols.MethodSymbol。 SourceUserDefinedOperatorSymbol 的继承链中没有任何内容会覆盖相等性并实现特殊的相等性检查。

在查看source for MethodSymbol 时,它不会覆盖Object.Equals(object)。 (它确实覆盖了一个相关的方法;稍后会详细介绍。)

MethodSymbol 派生自 Symbolsource of Symbol 表明它确实覆盖了 Object.Equals(object),而 Object.Equals(object) 又调用了另一个 Equals 函数。注意实现和 cmets:

    public sealed override bool Equals(object obj)
    {
        return this.Equals(obj as Symbol, SymbolEqualityComparer.Default.CompareKind);
    }

    // By default we don't consider the compareKind, and do reference equality. This can be overridden.
    public virtual bool Equals(Symbol other, TypeCompareKind compareKind)
    {
        return (object)this == other;
    }

所以看来这个类只是返回引用相等设计

Equals(Symbol, TypeCompareKind) 方法是virtualMethodSymbol 类覆盖它,但仅用于检查特定类型。因为这种类型 (SourceUserDefinedOperatorSymbol) 的继承链中没有任何内容会覆盖相等方法,所以您的代码最终仍会调用使用引用相等的 base 版本:

    public override bool Equals(Symbol other, TypeCompareKind compareKind)
    {
        if (other is SubstitutedMethodSymbol sms)
        {
            return sms.Equals(this, compareKind);
        }

        if (other is NativeIntegerMethodSymbol nms)
        {
            return nms.Equals(this, compareKind);
        }

        return base.Equals(other, compareKind);
    }

【讨论】:

  • 是的,它作为一个内在运算符似乎是在绊倒它。
  • 我创建了一个新问题,询问如何将此用户定义的运算符类型转换为代码中的内在函数。 stackoverflow.com/questions/69239551/…
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-09-17
  • 2014-02-09
  • 1970-01-01
  • 2015-11-12
  • 2023-03-23
  • 1970-01-01
相关资源
最近更新 更多