在构建将operator ==提升为可空的表达式时,为什么generics和非generics结构的处理方式不同?

这看起来像是在通用结构上提升为操作数的null的错误。

考虑以下伪结构,它覆盖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> exprA = (valueA, valueB) => valueA == valueB; Expression<Func> exprB = (nullableValueA, nullableValueB) => nullableValueA == nullableValueB; Expression<Func> exprC = (nullableValueA, valueB) => nullableValueA == valueB; 

所有三个都按预期编译和运行。

当它们被编译时(使用.Compile() ),它们产生以下代码(从IL转述为英语):

  1. 第一个只MyStruct (不可为空)args的表达式,只调用op_Equality (我们的operator ==的实现)

  2. 第二个表达式在编译时会生成代码,该代码检查每个参数以查看它是否为HasValue 。 如果两者都不(都等于null ),则返回true 。 如果只有一个值,则返回false 。 否则,在两个值上调用op_Equality

  3. 第三个表达式检查nullable参数以查看它是否有值 – 如果不是,则返回false。 否则,调用op_Equality

到现在为止还挺好。

下一步:使用generics类型执行完全相同的操作 – 在类型定义MyStruct更改为MyStruct ,并将其更改为MyStruct

现在第三个表达式编译但抛出运行时exceptionInvalidOperationException并带有以下消息:

运算符’Equal’的操作数与方法’op_Equality’的参数不匹配。

我希望通用结构的行为与非通用结构完全相同,具有上述所有可空的提升。

所以我的问题是:

  1. 为什么通用结构和非通用结构之间存在差异?
  2. 这个例外是什么意思?
  3. 这是C#/ .NET中的错误吗?

有关复制此内容的完整代码, 请参阅此要点 。

简短的回答是:是的,这是一个错误。 我在下面放了一个最小的repro和一个简短的分析。

我很抱歉。 我写了很多代码,所以很可能是我的坏。

我已经向Roslyn开发,测试和项目管理团队发送了一份副本。 我怀疑这是在Roslyn中重现的,但他们会validation它不会,并确定这是否成为C#5服务包的标准。

如果您也希望在connect.microsoft.com上进行跟踪,请随时在connect.microsoft.com上输入问题。


最小的repro:

 using System; using System.Linq.Expressions; struct S { public static bool operator ==(S a, S b) { return false; } public static bool operator !=(S a, S b) { return false; } } class Program { static void Main() { Expression?, S, bool>> x = (a, b) => a == b; } } 

在最小repro中生成的代码相当于

 ParameterExpression pa = Expression.Parameter(typeof(S?), "a"); ParameterExpression pb = Expression.Parameter(typeof(S), "b"); Expression.Lambda?, S, bool>>( Expression.Equal(pa, pb, false, infoof(S.op_Equality) new ParameterExpression[2] { pa, pb } ); 

其中, infoof是一个伪运算符,它获取给定方法的MethodInfo

正确的代码是:

 ParameterExpression pa = Expression.Parameter(typeof(S?), "a"); ParameterExpression pb = Expression.Parameter(typeof(S), "b"); Expression.Lambda?, S, bool>>( Expression.Equal(pa, Expression.Convert(pb, typeof(S?), false, infoof(S.op_Equality) new ParameterExpression[2] { pa, pb } ); 

Equal方法不能处理一个可空的,一个不可为空的操作数。 它要求两者都可以为空或者两者都可以为空。

(注意, false是正确的。这个布尔值控制是否提升相等的结果是一个提升的布尔值;在C#中它不是,在VB中它是。)

是的,这个bug在Roslyn(开发中的编译器)中消失了。 我们将看到现有产品。