为什么通用和非通用结构建设表达式升降机操作员==可空当区别对待? [英] Why are generic and non-generic structs treated differently when building expression that lifts operator == to nullable?

查看:177
本文介绍了为什么通用和非通用结构建设表达式升降机操作员==可空当区别对待?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述



这看起来像是在解除对通用的结构操作数为空的错误。



考虑下面的虚拟结构,重写运算符==



<预类=郎-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()),它们所产生的下面的代码(从转述英语白细胞介素):




  1. 第一个表达式,只需要 MYSTRUCT (不能为空)指定参数时,只需调用 op_Equality (我们的实现运算符==


  2. 第二个表达式,编译时产生,检查每一个参数,看它是否的HasValue 代码。如果这两个不(都等于),返回真正。如果只有一个有值,返回。否则,调用 op_Equality 对这两个值。


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




到目前为止好



下一步:做同样的事情用一个泛型类型 - 变 MYSTRUCT MYSTRUCT< T> 无处不在的类型定义,并将其更改为 MYSTRUCT< INT方式>



在表达式现在第三个表达式编译,但会引发运行时例外出现InvalidOperationException 以下消息:




有关运营商的操作数'平等'做不匹配方法op_Equality的参数。




我希望通用结构的行为完全一样的非普通的人,用。上述所有为空的升降



所以我的问题是:




  1. 为什么会出现通用和非通用结构之间的差异?

  2. 这是什么异常的含义是什么?

  3. 是在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):

  1. The first expression that takes only MyStruct (not nullable) args, simply calls op_Equality (our implementation of operator ==)

  2. The second expression, when compiled, produces code that checks each argument to see if it HasValue. If both don't (both equal null), returns true. If only one has a value, returns false. Otherwise, calls op_Equality on the two values.

  3. 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 to MyStruct<T> everywhere in the definition of the type, and change it to MyStruct<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:

  1. Why is there a difference between generic and non-generic structs?
  2. What is the meaning of this exception?
  3. 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 a MethodInfo 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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆