horgh
horgh

Reputation: 18563

Why is type parameter treated as object in expression?

Learning LINQ expressions I found out a strange behaviour. Please have a look at the following class. It may seem to be not very handy, but it's only an example.

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

Just to run a test one may use:

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

Then I tried to make this class generic:

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

So when I try to run similar code:

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

it throws an exception on line initializing x1 variable in constructor, saying

Expression of type 'System.Int32' cannot be used for parameter of type 'System.Object' of method 'Boolean Equals(System.Object)'

So it appears that equals1 (containing MethodInfo of the Equals method) in the TestClass<T> constructor is Boolean Equals(System.Object)

But in IntTestClass constructor equals1 is Boolean Equals(Int32).

Why is that so? In the generic class in runtime T is of type System.Int32. Why does the expression in e1 variable use Equals from the System.Object class, but not from System.Int32?

Upvotes: 1

Views: 289

Answers (1)

Dennis
Dennis

Reputation: 37800

Well, this is because manual building of expression trees needs a knowledge of tricks, that compiler does for you when you write regular code.

You can solve this particular problem in two ways:

1) Convert the int-expression into object-expression:

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

This will add conversion code for Object.Equals method argument, which, in turn, will provide boxing, when you'll call compiled lambda.

2) Put a constraint to your generic:

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

and leave this line:

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

unchanged.

The constraint here helps to tell, that you're going to use IEquatable<T>.Equals(T) instead of Object.Equals. IEquatable<T>.Equals(T) doesn't need any argument conversions, because it is generic itself.

Upvotes: 2

Related Questions