Reputation: 18563
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
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