c# - 值类型中的运算符解析问题(未添加参考)
问题描述
我正在做一个项目,我发现我使用的运算符和我声明的运算符不相等。
我做了一个最小可重现的例子:
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.
为什么这些看起来相同的运算符不表明它们是相等的?
解决方案
我修改了您的代码,并使用反射和查看 Roslyn 源代码,发现usedSymbol
并declaredSymbol
最终成为两种不同的符号类型。
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);
}
推荐阅读
- c# - c#在嵌套的对象列表中插入项目
- c# - 如何通过仅传递 Resources.MyProperty 属性来获取资源键字符串值
- javascript - Fabric.js:重新缩放组时保持对象位置固定
- javascript - 在单个语句中重新导出 ES6 导入命名空间?
- php - 尝试完成自定义帖子和简码以连接外部数据库
- ruby-on-rails - Rails - 通过属性在活动记录关联中查找对象而不发出更多查询
- mysql - 错误代码:1136 列数与值 SQL 过程不匹配
- amazon-web-services - Docker 的 EXPOSE 不会破坏所谓的沙盒吗?
- javascript - 如何只允许逗号作为最后一个字符
- android - 使用 ffmpeg 为视频上的叠加 gif 添加透明度