Linq和等号运算符:类型为'System.Int32'的表达式不能用于类型为'System.Object'的参数。

10
我试图在C#中重载等值(==)运算符,以便处理将任何类型与自定义类型(自定义类型实际上是围绕null的包装器/盒子)进行比较。因此,我有以下代码:
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;
    }
   ...
}

现在如果我进行以下调用:

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

它完全正常地工作。然而,如果我尝试通过一个Linq表达式树来做同样的事情:

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

它抛出异常:

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)
任何关于为什么基础系统可以将Int32转换为对象,但Linq不能,或者如何解决这个问题的想法吗? 这整件事的起因是因为Linq本身就无法比较Int32和Object:
exp = Expression.Equal(
    Expression.Constant(10), 
    Expression.Constant(null)
);

抛出异常,指出“System.Int32”和“System.Object”之间没有比较运算符。


快速跟进:

以下内容可以正常工作:

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

所以具体来说,将所有东西都转换为对象。那么 Linq 内部是否不处理继承?这真的很烦人...


跟进 #2:

我还尝试使用自定义比较方法:

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;
    }

这也会抛出异常:

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)

但是,将所有内容直接转换为对象也可以起作用:

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

所以我猜我有了解决方法……将所有东西都转换为对象并使用自定义比较方法。我仍然很惊讶 Linq 不能像普通的C#一样自动转换。


6
所以Linq内部不处理继承关系吗? 这真的很烦人...是的,这很烦人,但有一个很好的原因。表达式树库使用来自C#和VB的表达式,以及任何具有这些类型表达式的其他语言。如果我们将C#转换规则嵌入到处理解析相等性的代码中,那么对于来自VB的表达式,我们可能会做错事情。所以我们两者都不做--您需要传递明确的表达式,以便解析是与语言无关的。 - Eric Lippert
(针对您的评论)添加了一些想法。 - Marc Gravell
你在这个领域里找不到比Eric Lippert更权威的人了! - Marc Gravell
@eric:感谢您的确认和对Linq工作方式的一些见解。 - CodingWithSpike
2个回答

9

空值有什么问题吗?关于缺失的intnull,请尝试使用int?

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

我的问题是我真的不知道这些类型。Expression.Constant 是通过从 Dictionary<string, object> 中的值动态构建的。当字典中不存在某个值时,它返回 null,这是问题所在。 - CodingWithSpike
为了进一步澄清我上面的评论,有人可以尝试比较“x”和“y”,代码将从字典中抓取“x”和“y”,并创建Expression.Constant(dict [“x”])和Expression.Constant(dict [“y”])并尝试Equal()它们。 - CodingWithSpike
1
使用“对象”在表达式中有些冒险;它需要证明类型以使用正确的重载…… 但是,您可以可能制定一些特殊规则,以便如果一个操作数为 null,则使用另一个操作数的类型。 - Marc Gravell

0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接