为什么通用和非通用结构建设表达式升降机操作员==可空当区别对待? [英] Why are generic and non-generic structs treated differently when building expression that lifts operator == to nullable?
问题描述
这看起来像是在解除对通用的结构操作数为空的错误。
考虑下面的虚拟结构,重写运算符==
:
<预类=郎-CS prettyprint-覆盖>
结构MYSTRUCT
{
私人只读INT _value;
公共MYSTRUCT(INT VAL){this._value = VAL; }
公众覆盖布尔等于(obj对象){返回false; }
公共覆盖INT的GetHashCode(){返回base.GetHashCode(); }
公共静态布尔运算符==(MYSTRUCT一,MYSTRUCT B){返回false; }
公共静态布尔运算符=(MYSTRUCT一,MYSTRUCT B){返回false!; }
}
现在考虑下面的表达式:
<预类=郎-CS prettyprint-覆盖>
表达式来; Func键< MYSTRUCT,MYSTRUCT,布尔>> exprA =
(值a,valueB,则)=>值a == valueB,则;
表达式来; Func键< MYSTRUCT?MYSTRUCT?布尔>> exprB =
(nullableValueA,nullableValueB)=> nullableValueA == nullableValueB;
表达式来; Func键< MYSTRUCT?MYSTRUCT,布尔>> exprC =
(nullableValueA,valueB,则)=> nullableValueA == valueB,则;
这三个编译并运行正常。
当他们编译(使用 .Compile()
),它们所产生的下面的代码(从转述英语白细胞介素):
-
第一个表达式,只需要
MYSTRUCT
(不能为空)指定参数时,只需调用op_Equality
(我们的实现运算符==
) -
第二个表达式,编译时产生,检查每一个参数,看它是否
的HasValue
代码。如果这两个不(都等于空
),返回真正
。如果只有一个有值,返回假
。否则,调用op_Equality
对这两个值。 -
第三个表达式检查可空参数,看是否它有一个价值 - 如果不是,返回false。否则,调用
op_Equality
。
到目前为止好
下一步:做同样的事情用一个泛型类型 - 变 MYSTRUCT
到 MYSTRUCT< T>
无处不在的类型定义,并将其更改为 MYSTRUCT< INT方式>
在表达式现在第三个表达式编译,但会引发运行时例外出现InvalidOperationException
以下消息:
有关运营商的操作数'平等'做不匹配方法op_Equality的参数。
块引用>
我希望通用结构的行为完全一样的非普通的人,用。上述所有为空的升降
所以我的问题是:
- 为什么会出现通用和非通用结构之间的差异?
- 这是什么异常的含义是什么?
- 是在C#中的错误/ .NET?
再现这个完整的代码的这个要旨使用
解决方案短答案是:是的,这是一个错误。我已经把一个最小的摄制及以下短的分析。
我的道歉。我写了很多代码,所以它很可能是我不好。
我已经发出了摄制关闭的罗斯林开发,测试和项目管理团队。我怀疑这再现了罗斯林,但他们会验证它不并决定这是否使得一个C#5服务包了吧。
随意,如果你想跟踪它那里也进入上connect.microsoft.com的问题为好。
最小摄制:
使用系统;使用System.Linq.Expressions
;
结构S< T>
{
公共静态布尔运算符==(S< T> A,S< T> B){返回false; }
公共静态布尔运算符=(S< T> A,S< T> B)!{返回false; }
}
类节目
{
静态无效的主要()
{
表达式来; Func键< S< INT>?,S< INT>中布尔>> X =(A,B)=> A == B:
}
}
那就是在最小的摄制生成的代码等同于
ParameterExpression PA = Expression.Parameter(typeof运算(S< INT> ;? ), 一个);
ParameterExpression PB = Expression.Parameter(typeof运算(S< INT>),B);
Expression.Lambda< Func键< S< INT>?,S< INT>中布尔>>(
Expression.Equal(PA,PB,假的,infoof(S< INT> .op_Equality)
新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>中布尔>> (
Expression.Equal(PA,Expression.Convert(PB的typeof(S< INT>?),假的,infoof(S< INT> .op_Equality)
新ParameterExpression [2] {PA,PB });
的
等于
方法无法处理一个空的,一个非空的操作数。它要求两个可为空或两者都不是。
(请注意,
假
是正确的。这个布尔控件解除平等的结果是否是解除布尔;在C#不是,在VB它是。)
This looks like a bug in lifting to null of operands on generic structs.
Consider the following dummy struct, that overrides
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; } }
Now consider the following expressions:
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;
All three compile and run as expected.
When they're compiled (using
.Compile()
) they produce the following code (paraphrased to English from the IL):
The first expression that takes only
MyStruct
(not nullable) args, simply callsop_Equality
(our implementation ofoperator ==
)The second expression, when compiled, produces code that checks each argument to see if it
HasValue
. If both don't (both equalnull
), returnstrue
. If only one has a value, returnsfalse
. Otherwise, callsop_Equality
on the two values.The third expression checks the nullable argument to see if it has a value - if not, returns false. Otherwise, calls
op_Equality
.So far so good.
Next step: do the exact same thing with a generic type - change
MyStruct
toMyStruct<T>
everywhere in the definition of the type, and change it toMyStruct<int>
in the expressions.Now the third expression compiles but throws a runtime exception
InvalidOperationException
with the following message:The operands for operator 'Equal' do not match the parameters of method 'op_Equality'.
I would expect generic structs to behave exactly the same as non-generic ones, with all the nullable-lifting described above.
So my questions are:
- Why is there a difference between generic and non-generic structs?
- What is the meaning of this exception?
- Is this a bug in C#/.NET?
The full code for reproducing this is available on this gist.
解决方案The short answer is: yes, that's a bug. I've put a minimal repro and a short analysis below.
My apologies. I wrote a lot of that code and so it was likely my bad.
I have sent a repro off to the Roslyn development, test and program management teams. I doubt this reproduces in Roslyn, but they'll verify that it does not and decide whether this makes the bar for a C# 5 service pack.
Feel free to enter an issue on connect.microsoft.com as well if you want it tracked there as well.
Minimal 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; } }
The code that is generated in the minimal repro is equivalent to
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 } );
Where
infoof
is a fake operator that gets aMethodInfo
for the given method.The correct code would be:
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 } );
The
Equal
method cannot deal with one nullable, one non-nullable operands. It requires that either both are nullable or neither is.(Note that the
false
is correct. This Boolean controls whether the result of a lifted equality is a lifted Boolean; in C# it is not, in VB it is.)这篇关于为什么通用和非通用结构建设表达式升降机操作员==可空当区别对待?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!