首页 > 解决方案 > 值类型中的运算符解析问题(未添加参考)

问题描述

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

在此处输入图像描述

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

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.

参见 .NET Fiddle!

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

标签: c#roslynmicrosoft.codeanalysis

解决方案


我修改了您的代码,并使用反射和查看 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 的继承链中的任何内容都不会覆盖相等性并实现特殊的相等性检查。

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

MethodSymbol源自Symbol。的来源Symbol表明它确实 override Object.Equals(object),这反过来又调用了另一个 Equals 函数。注意实现和评论:

    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)方法是virtual,并且MethodSymbol该类将覆盖它,但仅用于检查特定类型。因为这种类型 (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);
    }

推荐阅读