2

I have the following expression which is of type Expression<Func<TDocument, object>>

x => x.Name

Now, I don't know the type of x.Name at compile time, but I now it at runtime since it's stored in a Type.

How can I convert my expression to be of type Expression<Func<TDocument, TOutput>> where TOutput is a Type and not known at compile time?

Complexity
  • 5,682
  • 6
  • 41
  • 84

1 Answers1

3

You just need to wrap the original expression's Body in a Convert expression and then rebuild your lambda. Here's how I would do it if I could use generics:

Expression<Func<TInput, TReturn>> ConvertReturnValue<TInput, TReturn>(
    Expression<Func<TInput, object>> inputExpression)
{
    Expression convertedExpressionBody = Expression.Convert(
        inputExpression.Body, typeof(TReturn)
    );

    return Expression.Lambda<Func<TInput, TReturn>>(
        convertedExpressionBody, inputExpression.Parameters
    );
}

Usage:

Expression<Func<TDocument, object>> inputExpression = d => d.Name;

Expression<Func<TDocument, string>> convertedExpression
    = ConvertReturnValue<TDocument, string>(inputExpression);

// Test.
TDocument doc = new TDocument { Name = "Zzz" };
string name = convertedExpression.Compile().Invoke(doc);

Assert.Equal("Zzz", name);

No generics

If you cannot use generics because you don't know the return type at compile time, Expression.Lambda actually offers a non-generic overload, which you can use like this:

Expression ConvertReturnValue<TInput>(Expression<Func<TInput, object>> inputExpression, Type returnType)
{
    Expression convertedExpressionBody = Expression.Convert(inputExpression.Body, returnType);

    return Expression.Lambda(convertedExpressionBody, inputExpression.Parameters);
}

The above still returns an Expression<Func<TInput, TReturn>> (upcast to a non-generic Expression). You can downcast it later if you need to:

Expression<Func<TDocument, object>> inputExpression = d => d.Name;

Expression<Func<TDocument, string>> convertedExpression
    = (Expression<Func<TDocument, string>>)ConvertReturnValue(inputExpression, typeof(string));

// Test.
TDocument doc = new TDocument { Name = "Zzz" };
string name = convertedExpression.Compile().Invoke(doc);

Assert.Equal("Zzz", name);

Addendum

Note that for struct return types, the final expression may end up looking like this:

(TDocument d) => (int)(object)d.ID;
Kirill Shlenskiy
  • 9,367
  • 27
  • 39
  • It's a good starting point. Thanks for this. However, your method required that I now the `TReturn` at compile time. In my question it's stated that I don't know it, it's stored in a type. – Complexity Oct 21 '16 at 06:34
  • @Complexity, true that. I have added a non-generic solution. – Kirill Shlenskiy Oct 21 '16 at 06:36
  • Thanks for the non-generic solution but doesn't seem to solve my problem since I don't need an `Expression` but an `Expression` for which I don't know the output. I will use the generic implementation with a few if statements. I only have to support a few built-in types such as `string`, `int`, ... – Complexity Oct 21 '16 at 06:46
  • @Complexity, non-generic `Lambda` actually returns a generic `Expression`, it's just cast to a non-generic `Expression` - but casting it back is an option (as I've shown in the second example). Having said that it seems to me you're abusing generics. Anytime you have to resort to `if` statements you know it's a hack (although I suppose it's viable for really simple scenarios). If you want to achieve proper dynamic data handling, consider building bigger expression trees. – Kirill Shlenskiy Oct 21 '16 at 06:56
  • I've completely implemented it and until now it's working very fine. However, when trying to convert to a boolean the following expression `Rewriting child expression from type 'System.Object' to type 'System.Boolean' is not allowed, because it would change the meaning of the operation. If this is intentional, override 'VisitUnary' and change it to allow this rewrite.` – Complexity Oct 21 '16 at 12:38
  • @Complexity, that might be worth raising as a separate question (with a code example). There simply aren't enough details in your comment. It sounds like you're using some sort of `ExpressionVisitor` implementation that isn't happy with the type of node substitution you're trying to perform. – Kirill Shlenskiy Oct 21 '16 at 12:50
  • I will post a new one but I'm just using your code :-) You can try it yourself. – Complexity Oct 21 '16 at 12:54
  • @Complexity, uh oh. Are you able to post a minimal repro of the problem in the question above then? I've written a number of tests confirming the correctness of the above snippets - at least in the scenarios I could think of, so I am quite baffled by the error message you have posted. – Kirill Shlenskiy Oct 21 '16 at 13:30
  • I'm sorry but I was wrong. The fault was at some other location in my code. My apologies. – Complexity Oct 21 '16 at 14:09
  • @Complexity, not a problem. Glad you've figured it out. – Kirill Shlenskiy Oct 21 '16 at 14:52