【问题标题】:Why is type parameter treated as object in expression?为什么类型参数在表达式中被视为对象?
【发布时间】:2013-11-12 05:31:03
【问题描述】:

学习 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”类型参数

所以看起来TestClass&lt;T&gt;构造函数中的equals1(包含Equals方法的MethodInfo)是Boolean Equals(System.Object)

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

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

【问题讨论】:

    标签: c# linq generics expression


    【解决方案1】:

    嗯,这是因为手动构建表达式树需要技巧知识,编译器会在您编写常规代码时为您完成。

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

    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&lt;T&gt;.Equals(T) 而不是Object.EqualsIEquatable&lt;T&gt;.Equals(T) 不需要任何参数转换,因为它本身就是通用的。

    【讨论】:

    • 我使用了第二种方法......它完成了工作......请给我一些阅读该主题的链接吗?......因为这种行为仍然相当模糊。跨度>
    • @horgh:不幸的是,我不能给你任何链接......只是不知道在哪里可以检查这个特定方面。但是没有任何含糊之处:将表达式树视为原始代码,没有任何编译器技巧和语法糖。在编写代码(数据转换、方法组到委托实例的方法组转换)时,您没有观察到的事情应该明确地写在表达式树中。
    • 在您对IEquatable 进行编辑后,实际上一切都取而代之了...非常感谢
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-15
    • 2016-04-25
    • 1970-01-01
    相关资源
    最近更新 更多