8

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.

Arthur Rey
  • 2,990
  • 3
  • 19
  • 42
  • Is it causing a *problem* that the `Convert`s have been added? – Damien_The_Unbeliever Oct 13 '15 at 10:47
  • @Damien_The_Unbeliever Indeed it does as I can't access properties with this faulty name. – Arthur Rey Oct 13 '15 at 10:48
  • @ArthurRey There's no faulty name, unless you're parting the string representation of the expression, which you shouldn't be doing anyway. –  Oct 13 '15 at 10:49
  • 1
    If you're writing code that is pulling apart the lambda expressions for extracting e.g. property names then, almost certainly, the fix is to be made within the code currently shown in your question as `/* Some stuff */`. – Damien_The_Unbeliever Oct 13 '15 at 10:50
  • 2
    What do you actually want to achieve? What is the problem? – usr Oct 13 '15 at 11:01
  • You haven't actually asked a question. Do you want an explanation for why I added the convert node in there, or help on removing it, or what? – Eric Lippert Oct 16 '15 at 14:51

3 Answers3

5

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.

OP said...

Isn't there any way to force the Expression to remain untouched?

No. You should handle both scenarios: accessing properties with and without casts.

Community
  • 1
  • 1
Matías Fidemraizer
  • 63,804
  • 18
  • 124
  • 206
  • Isn't there any way to force the Expression to remain untouched? – Arthur Rey Oct 13 '15 at 10:56
  • @ArthurRey - you mean, is there a way to get the C# compiler to emit invalid code here? No, not so far as I'm aware. – Damien_The_Unbeliever Oct 13 '15 at 10:58
  • 1
    @ArthurRey No. You should handle both scenarios: accessing properties with and without casts. – Matías Fidemraizer Oct 13 '15 at 11:00
  • In what way does this answer the question? Not sure how he can resolve the problem given this information. – usr Oct 13 '15 at 11:05
  • _In what way does this answer the question?_ telling the reason why its not possible. and probably OP having xy problem. @usr – M.kazem Akhgary Oct 13 '15 at 11:15
  • @M.kazemAkhgary yeah, XY is true. I should have just stepped away. Turns out the answer he needed was to just strip the Convert node away. You never know with these kinds of questions. – usr Oct 13 '15 at 11:24
  • @usr OP had to take my answer and understand that the conversion can't be avoided, thus, it should cover both cases (accessing properties holding reference and value types). BTW, other author has answered this directly and OP has choosen it as the right answer for him/her. – Matías Fidemraizer Oct 13 '15 at 11:58
  • 1
    @usr In my case, I don't like giving the 100% of the work to get to a definitive solution, but provide enough background to work on it. Other reader may find my answer more useful than "do this", because it's more interesting understanding why the conversion is unavoidable than just solving the issue like a workaround when it can be solved because the OP understood what was behind the issue... – Matías Fidemraizer Oct 13 '15 at 12:00
3

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.

qxg
  • 6,955
  • 1
  • 28
  • 36
  • 3
    It is exactly what I was looking for. Solved it by doing `(property.Body.NodeType == ExpressionType.Convert ? (property.Body as UnaryExpression).Operand : property.Body) as MemberExpression;` – Arthur Rey Oct 13 '15 at 11:28
  • @ArthurRey I suggested you this too... See a comment I answered you: **No. You should handle both scenarios: accessing properties with and without casts.**. BTW, if you find this answer more useful, I'm glad you solved the issue – Matías Fidemraizer Oct 13 '15 at 11:57
  • @MatíasFidemraizer I just realised what you meant. This answer was more straightforward resolving my issue, hence the acceptance. You helped me though. – Arthur Rey Oct 13 '15 at 12:02
  • 1
    @ArthurRey The important part is you solved the issue. At the end of the day, I don't care if I get more or less points for this... I'm not here to get fame but to learn from others and contribute with useful content :) – Matías Fidemraizer Oct 13 '15 at 12:04
1

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.

usr
  • 168,620
  • 35
  • 240
  • 369