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

sin*_*law 24 c# generics nullable compiler-bug

这看起来像是在通用结构上提升为操作数的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; }
}
Run Code Online (Sandbox Code Playgroud)

现在考虑以下表达式:

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;
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

  3. 第三个表达式检查nullable参数以查看它是否有值 - 如果不是,则返回false.否则,打电话op_Equality.

到现在为止还挺好.

下一步:使用泛型类型执行完全相同的操作 - 更改MyStructMyStruct<T>类型定义中的任何位置,并将其更改MyStruct<int>为表达式.

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

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

我希望通用结构的行为与非通用结构完全相同,并且上面描述了所有可空的结构.

所以我的问题是:

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

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

Eri*_*ert 22

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

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

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

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


最小的repro:

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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 } );
Run Code Online (Sandbox Code Playgroud)

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 } );
Run Code Online (Sandbox Code Playgroud)

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

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

  • 请允许我发表评论,如果没有像这样的快速回复,我们的团队过去会等待数月甚至数年才能对Connect进行适当的澄清,在此期间我们实施了可能会或可能不正确的变通方法.我希望仍然在微软的人能够快速回复官方政策. (10认同)

Nea*_*ter 5

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