Reputation: 3058
I have a sample Data
class
public class Data
{
public int TestInt { get; set; }
public bool TestBool { get; set; }
public string TestString { get; set; }
public Data() { TestInt = 10; TestBool = true; TestString = "test"; }
}
And an extension method
public static void Method<T>(this T item, params Expression<Func<T, object>>[] properties)
{
/* Some stuff */
}
That I use like this
Data data = new Data();
data.Method(x => x.TestInt, x => x.TestBool, x => x.TestString);
My Method<T>
does receive 3 properties, but it has been slightly changed to:
properties[0] = x => Convert(x.TestId);
properties[1] = x => Convert(x.TestBool);
properties[2] = x => x.TestString;
As you can see, the TestString
part is unchanged. I tried changing my properties to params Expression<Func<T, bool>>[]
and params Expression<Func<T, int>>[]
and only pass the corresponding parameter and it works fine. I understand the problem comes from converting into an object
but I can't figure it out.
Upvotes: 8
Views: 1996
Reputation: 64931
Since both Int32
and Boolean
aren't reference types, the whole expression tree needs to explicitly cast them to object
.
There're some implicit operations that are available during compile-time using regular C# compiler while others require explicit actions while implementing expression trees.
Do you want to test yourself about this fact?
public struct A {}
public class B { }
public class C
{
public A A { get; set; }
public B B { get; set; }
}
C c = new C();
Expression<Func<C, object>> expr1 = some => some.A; // Convert(some.A)
Expression<Func<C, object>> expr2 = some => some.B; // some.B
At the end of the day, regular C# compiler implements some trickery to cast a value type to fit into object
(a reference type). Maybe this Q&A "How do ValueTypes derive from Object (ReferenceType) and still be ValueTypes?" where Eric Lippert answered it might be interesting for you.
Isn't there any way to force the Expression to remain untouched?
No. You should handle both scenarios: accessing properties with and without casts.
Upvotes: 5
Reputation: 7036
If you want to analyze original expression, one possible way is to remove Convert
expression manually.
In Method
, you may get a UnaryExpression
with NodeType
= Convert
. If so, just inspect this expression's Operand property.
Upvotes: 3
Reputation: 171188
I'm not sure what you want to achieve but here's a way not to have these conversions. The problem is that you are converting to object
which only happens because of the way you have declared the Expression
variable/argument.
When you say:
Expression<Func<int>> f1 = () => 1234;
This does not convert. So do this:
Expression<Func<int>> f1 = () => 1234;
Expression<Func<string>> f2 = () => "x";
LambdaExpression[] myFunctionExpressions = new LambdaExpression[] { f1, f2 };
Method(myFunctionExpressions);
And the argument to myFunctionExpressions
must be LambdaExpression[]
as well.
The caller has become more verbose now but the trees are clean.
Upvotes: 1