views:

1476

answers:

1

I'm trying to override the equality (==) operator in C# to handle comparing any type to a custom type (the custom type is really a wrapper/box around null).

So I have this:

internal sealed class Nothing
{
 public override bool Equals(object obj)
 {
  if (obj == null || obj is Nothing)
   return true;
  else
   return false;
 }

 public static bool operator ==(object x, Nothing y)
 {
  if ((x == null || x is Nothing) && (y == null || y is Nothing))
   return true;
  return false;
 }
   ...
}

Now if I make a call like:

Nothing n = new Nothing();
bool equal = (10 == n);

It works perfectly fine. However, if I try to do this same thing through a Linq expression tree:

exp = Expression.Equal(
    Expression.Constant(10), 
    Expression.Constant(new Nothing(), typeof(Nothing))
);

It throws the exception:

System.ArgumentException : Expression of type 'System.Int32' cannot be used for parameter of type 'System.Object' of method 'Boolean op_Equality(System.Object, PARTSFinder.Rules.Runtime.RulesNothing)'
    at System.Linq.Expressions.Expression.ValidateArgumentTypes(MethodInfo method, ReadOnlyCollection`1& arguments)
    at System.Linq.Expressions.Expression.ValidateCallArgs(Expression instance, MethodInfo method, ReadOnlyCollection`1& arguments)
    at System.Linq.Expressions.Expression.Call(Expression instance, MethodInfo method, IEnumerable`1 arguments)
    at System.Linq.Expressions.Expression.Call(Expression instance, MethodInfo method, Expression[] arguments)
    at System.Linq.Expressions.ExpressionCompiler.GenerateBinaryMethod(ILGenerator gen, BinaryExpression b, StackType ask)

Any ideas on why the base system can convert Int32 to Object, but Linq can't, or how I can fix this?

This whole thing stared because Linq also can't compare Int32 to Object in the first place:

exp = Expression.Equal(
    Expression.Constant(10), 
    Expression.Constant(null)
);

Throws an exception stating that there is no comparison operator for "System.Int32" and "System.Object".


Quick followup:

The following do work without issue:

exp = Expression.Equal(
    Expression.Constant(10, typeof(object)), 
    Expression.Constant(new Nothing(), typeof(Nothing))
);

exp = Expression.Equal(
    Expression.Constant(10, typeof(object)), 
    Expression.Constant(null)
);

So specifically casting everything to object. So does Linq just not handle inheritance internally? Thats pretty annoying...


Followup #2:

I also tried using a custom comparison method:

exp = Expression.Equal(
    Expression.Constant(10),
    Expression.Constant(null),
    false,
    this.GetType().GetMethod("ValueEquals", BindingFlags.Public | BindingFlags.Static)
);

 public static bool ValueEquals(object x, object y)
 {
  if (x == null && y == null)
   return true;
  if (x.GetType() != y.GetType())
   return false;
  return x == y;
 }

This too throws an exception:

System.InvalidOperationException : The operands for operator 'Equal' do not match the parameters of method 'ValueEquals'.
    at System.Linq.Expressions.Expression.GetMethodBasedBinaryOperator(ExpressionType binaryType, Expression left, Expression right, MethodInfo method, Boolean liftToNull)

But again casting everything directly to object works:

exp = Expression.Equal(
    Expression.Constant(10, typeof(object)),
    Expression.Constant(null, typeof(object)),
    false,
    this.GetType().GetMethod("ValueEquals", BindingFlags.Public | BindingFlags.Static)
);

So I guess I have my workaround... cast everything to object and use a custom comparison method. I'm still surprised Linq doesn't do the conversion automatically as normal C# does.

+3  A: 

What is wrong with null? Re the missing int vs null, try int?:

exp = Expression.Equal(
    Expression.Constant(10, typeof(int?)), 
    Expression.Constant(null, typeof(int?))
);
Marc Gravell
My issue is that I really don't know the types. The Expression.Constant is being built dynamically from values in a Dictionary<string, object>. The issue is when something doesnt exist in the dictionary, it returns null.
rally25rs
To further clarify my above comment, someone can try to compare "x" and "y", and the code will grab "x" and "y" out of a Dictionary, and make an Expression.Constant(dict["x"]) and Expression.Constant(dict["y"]) and try to Equal() them.
rally25rs
Well, using "object" is a bit risky with Expression; it needs to prove the types to use the correct overloads... you could probably put in some special rules so that if one operand is null you use the type from the other operand, though.
Marc Gravell