为什么在构建将运算符 == 提升为可为空的表达式时,对泛型和非泛型结构的处理方式不同
本文关键字:泛型 结构 方式不 处理 构建 表达式 为什么 运算符 | 更新日期: 2023-09-27 18:15:39
这看起来像是在泛型结构上提升到操作数为空的错误。
考虑以下覆盖operator==
的虚拟结构:
struct MyStruct
{
private readonly int _value;
public MyStruct(int val) { this._value = val; }
public override bool Equals(object obj) { return false; }
public override int GetHashCode() { return base.GetHashCode(); }
public static bool operator ==(MyStruct a, MyStruct b) { return false; }
public static bool operator !=(MyStruct a, MyStruct b) { return false; }
}
现在考虑以下表达式:
Expression<Func<MyStruct, MyStruct, bool>> exprA =
(valueA, valueB) => valueA == valueB;
Expression<Func<MyStruct?, MyStruct?, bool>> exprB =
(nullableValueA, nullableValueB) => nullableValueA == nullableValueB;
Expression<Func<MyStruct?, MyStruct, bool>> exprC =
(nullableValueA, valueB) => nullableValueA == valueB;
所有三个都按预期编译和运行。
编译它们(使用 .Compile()
(时,它们生成以下代码(从 IL 转述为英语(:
第一个只接受
MyStruct
(不可为空(参数的表达式,简单地调用op_Equality
(我们对operator ==
的实现(第二个表达式在编译时会生成代码,用于检查每个参数以查看其是否
HasValue
。如果两者都不(都等于null
(,则返回true
。如果只有一个值,则返回false
。否则,对这两个值调用op_Equality
。第三个表达式检查可为 null 的参数以查看它是否有值 - 如果没有,则返回 false。否则,调用
op_Equality
。
目前为止,一切都好。
下一步:对泛型类型执行完全相同的操作 - 将MyStruct
更改为在类型定义中的所有位置MyStruct<T>
,并将其更改为表达式中的MyStruct<int>
。
现在,第三个表达式编译,但抛出运行时异常InvalidOperationException
,并显示以下消息:
我希望泛型结构运算符"等于"的操作数与方法 "op_Equality" 的参数不匹配。
的行为与非泛型结构完全相同,具有上述所有可为空的提升。
所以我的问题是:
- 为什么泛型结构和非泛型结构之间有区别?
- 这个例外的含义是什么?
- 这是 C#/.NET 中的错误吗?
重现此内容的完整代码可在此要点中找到。
简短的回答是:是的,这是一个错误。我在下面放了一个最小的重现和一个简短的分析。
我道歉。我写了很多这样的代码,所以这可能是我的坏事。
我已经向 Roslyn 开发、测试和项目管理团队发送了一份重现。我怀疑这在 Roslyn 中重现,但他们会验证它没有,并决定这是否成为 C# 5 Service Pack 的标准。
如果您也希望在那里跟踪问题,也可以随意在 connect.microsoft.com 上输入问题。
最少的重现:
using System;
using System.Linq.Expressions;
struct S<T>
{
public static bool operator ==(S<T> a, S<T> b) { return false; }
public static bool operator !=(S<T> a, S<T> b) { return false; }
}
class Program
{
static void Main()
{
Expression<Func<S<int>?, S<int>, bool>> x = (a, b) => a == b;
}
}
在最小重现中生成的代码等效于
ParameterExpression pa = Expression.Parameter(typeof(S<int>?), "a");
ParameterExpression pb = Expression.Parameter(typeof(S<int>), "b");
Expression.Lambda<Func<S<int>?, S<int>, bool>>(
Expression.Equal(pa, pb, false, infoof(S<int>.op_Equality)
new ParameterExpression[2] { pa, pb } );
其中infoof
是一个假运算符,它为给定方法获取MethodInfo
。
正确的代码是:
ParameterExpression pa = Expression.Parameter(typeof(S<int>?), "a");
ParameterExpression pb = Expression.Parameter(typeof(S<int>), "b");
Expression.Lambda<Func<S<int>?, S<int>, bool>>(
Expression.Equal(pa, Expression.Convert(pb, typeof(S<int>?), false, infoof(S<int>.op_Equality)
new ParameterExpression[2] { pa, pb } );
Equal
方法不能处理一个可为空的操作数,一个不可为空的操作数。它要求两者都可为空,或者两者都不可为空。
(请注意,false
是正确的。此布尔值控制提升相等的结果是否为提升的布尔值;在 C# 中不是,在 VB 中是。
是的,这个错误在 Roslyn(正在开发的编译器(中消失了。 我们将看到现有产品。