24

这看起来像是将泛型结构上的操作数提升为 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<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 解释为英语):

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

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

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

到目前为止,一切都很好。

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

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

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

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

所以我的问题是:

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

此 gist 上提供了重现此内容的完整代码。

4

2 回答 2

22

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

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

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

如果您也想在那里跟踪它,请随时在 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获取 a 的假运算符在哪里。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 中是。)

于 2013-05-28T17:27:09.080 回答
5

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

于 2013-05-28T22:05:33.350 回答