为什么将类型参数视为表达式中的对象?

时间:2013-11-12 05:31:04

标签: c# linq generics expression

学习LINQ表达式我发现了一个奇怪的行为。请看下面的课程。它似乎不是很方便,但它只是一个例子。

class IntTestClass
{
    public int Id { get; private set; }

    Func<IntTestClass, bool> check;

    public IntTestClass(int _id)
    {
        Id = _id;

        Expression<Func<IntTestClass, int>> GetId = tc => tc.Id;

        Expression<Func<int, bool>> e1 = i => i.Equals(Id);
        var equals1 = (e1.Body as MethodCallExpression).Method;
        string s1 = equals1.ToString();
        Console.WriteLine(s1);

        var x1 = Expression.Call(Expression.Constant(Id), equals1, GetId.Body);
        var exp1 = (Expression<Func<IntTestClass, bool>>)
                             Expression.Lambda(x1, GetId.Parameters.ToArray());
        check = exp1.Compile();
    }

    public bool Check(IntTestClass t)
    {
        var result =  check(t);
        Console.WriteLine(result);
        return result;
    }
}

只是为了进行测试,可以使用:

var intTestClass= new IntTestClass(0);
intTestClass.Check(new IntTestClass(0)); //true
intTestClass.Check(new IntTestClass(1)); //false

然后我试着让这个类变得通用:

class TestClass<T>
{
    public T Id { get; private set; }

    public TestClass(T _Id)
    {
        Id = _Id;

        Expression<Func<TestClass<T>, T>> GetId = tc => tc.Id;

        Expression<Func<T, bool>> e1 = i => i.Equals(Id);
        var equals1 = (e1.Body as MethodCallExpression).Method;
        string s1 = equals1.ToString();
        Console.WriteLine(s1);

        var x1 = Expression.Call(Expression.Constant(Id), equals1, GetId.Body);
        ....
    }
    ....
}

所以当我尝试运行类似的代码时:

 var testClass = new TestClass<int>(0);

它在构造函数中初始化x1变量的行上抛出异常,说

  

'System.Int32'类型的表达式不能用于'Boolean Equals(System.Object)'方法'System.Object'类型的参数

equals1构造函数中的MethodInfo(包含Equals方法的TestClass<T>)似乎是Boolean Equals(System.Object)

但在IntTestClass构造函数equals1Boolean Equals(Int32)

为什么会这样?在运行时T中的泛型类的类型为System.Int32。为什么e1变量中的表达式使用Equals类中的System.Object,而不是来自System.Int32

1 个答案:

答案 0 :(得分:2)

嗯,这是因为表达式树的手动构建需要熟悉技巧,编译器会在您编写常规代码时为您做。

您可以通过两种方式解决此特定问题:

1)将int - 表达式转换为object - 表达式:

var x1 = Expression.Call(Expression.Constant(Id), equals1, Expression.Convert(GetId.Body, typeof(object)));

这将为Object.Equals方法参数添加转换代码,当您调用编译的lambda时,它将提供装箱。

2)对你的通用设置约束:

class TestClass<T>
    where T : IEquatable<T>
{
}

并留下这一行:

var x1 = Expression.Call(Expression.Constant(Id), equals1, GetId.Body);

不变。

此处的约束有助于说明您将使用IEquatable<T>.Equals(T)而不是Object.EqualsIEquatable<T>.Equals(T)不需要任何参数转换,因为它本身就是通用的。