Why are generic and non-generic structs treated differently when building expression that lifts operator == to nullable?

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.

Strange exception thrown when defining an expression with == between a generic struct type to its nullable counterpart

Final update: See this new question that narrows the problem down to generic structs. I have some code that is building an Expression<Func<..>> that compares a value type to the nullable o

Why use generic and nongeneric function variants?

I’ve been studying code of some different libraries, and notice that some will provide equivalent generic and non-generic functions in the same class. One example is the IServiceLocator interface of t

Is “fn ()” treated differently to “fn()” when invoking a function?

There are some conventions when it comes to using brackets in JavaScript, but do they actually get treated differently when the brackets are used to invoke. Is fn () different to fn() in any way, exce

Why can’t structs contain nullable circular references?

I understand why structs can’t contain circular references which lead to logical memory problems, but why doesn’t a nullable reference circumvent this limitation? For example: struct Foo { Foo? bar; }

Why are empty Arrays and Hashes treated differently when cast to string and then to symbol?

In Ruby, why are these two operations different for empty Arrays and Hashes? Empty Array: [].to_s.to_sym => :[] Empty Hash: {}.to_s.to_sym => :{}

Why conditional operator will be treated as bool when passed in as a parameter?

I have two overloaded function void foo(std::string value); void foo(bool value); when I call it with foo(true ? a : b); why function takes a boolean will be called instead of string?

Autofac: register generic type with nongeneric interface

I’m trying to register (as Instance per Request) the generic type and non-generic interface. On internet I have found a lot of opposite examples, but none for this. Thus, I have a class like this: pub

C# conditional operator ?: has problems with nullable int [duplicate]

Possible Duplicate: Conditional operator assignment with Nullable<value> types? Why does the conditional operator “?:” not work here when my function is returning a nullable integer “int?”? “r

Entity framework Generic query in Nongeneric Property

In Entity framework I have objectsets like public partial class Building { public int BuildingID { get; set; } public string BuildingName { get; set; } } public partial class Town { public int TownID

Why does the == operator work for Nullable when == is not defined?

I was just looking at this answer, which contains the code for Nullable<T> from .NET Reflector, and I noticed two things: An explicit conversion is required when going from Nullable<T> to

Answers

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.)

Yes, this bug is gone in Roslyn (the compiler under development). We’ll see about the existing product.