这看起来像是将通用结构上的操作数提升为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转发给英语):
仅采用MyStruct
(不可为空)args的第一个表达式,只需调用op_Equality
(我们对operator ==
的实现)
第二个表达式在编译时会生成代码,用于检查每个参数以查看它是否为HasValue
。如果两者都不相等(均为null
),则返回true
。如果只有一个值,则返回false
。否则,请在两个值上调用op_Equality
。
第三个表达式检查nullable参数以查看它是否有值 - 如果不是,则返回false。否则,请致电op_Equality
。
到目前为止一切顺利。
下一步:使用泛型类型执行完全相同的操作 - 在类型定义中的任何位置将MyStruct
更改为MyStruct<T>
,并将其更改为表达式中的MyStruct<int>
。 / p>
现在第三个表达式编译但抛出运行时异常InvalidOperationException
,并带有以下消息:
运算符'Equal'的操作数与方法'op_Equality'的参数不匹配。
我希望通用结构的行为与非通用结构完全相同,并且上面描述了所有可空的提升。
所以我的问题是:
复制此内容的完整代码为available on this gist。
答案 0 :(得分:22)
简短的回答是:是的,这是一个错误。我在下面写了一个最小的repro和一个简短的分析。
道歉。我写了很多代码,所以很可能是我的不好。
我已经向Roslyn开发,测试和项目管理团队发送了一份副本。我怀疑这会在Roslyn中重现,但他们会验证它没有,并确定这是否成为C#5服务包的标准。
如果您希望在connect.microsoft.com上进行跟踪,请随时在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;
}
}
最小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 } );
其中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中它是。)
答案 1 :(得分:5)
是的,这个错误在Roslyn(开发中的编译器)中消失了。我们将看到现有产品。