1

Suppose I have the following type with an implicit convertion operator:

public readonly struct WrappedInt
{
    public WrappedInt(int value)
    {
        Value = value;
    }

    public int Value { get; }

    public static implicit operator int (WrappedInt wrapper) => wrapper.Value;
}

Then here is an application:

var wrapper = new WrappedInt(42);
int x1 = Unwrap(wrapper);
int x2 = UnwrapUsingExpression(wrapper);

private static int Unwrap(WrappedInt wrapper)
{
    int result = wrapper;
    return result;
}

private static int UnwrapUsingExpression(WrappedInt wrapper)
{
    var wrapperParameterExpression = Expression.Parameter(typeof(WrappedInt), "wrapper");
    var resultVariableExpression = Expression.Variable(typeof(int), "result");

    Expression right = wrapperParameterExpression;     // THIS is important
    var assignExpression = Expression.Assign(resultVariableExpression, right);
    var blockExpression = Expression.Block(
        new ParameterExpression[] { resultVariableExpression },
        new Expression[] { resultVariableExpression, assignExpression, resultVariableExpression }
    );

    var unwrapFunc = Expression.Lambda<Func<WrappedInt, int>>(blockExpression, wrapperParameterExpression).Compile();
    return unwrapFunc(wrapper);
}

Please note that in the Unwrap function I do int result = wrapper; which uses my implicit convertion operator - fine.

Now I want to do the same but in the expression tree - UnwrapWithExpression routine. Here I'm assigning my 'result' variable directly to 'wrapper' parameter using

Expression right = wrapperParameterExpression;

But this does not work - I get a runtime exception:

System.ArgumentException: 'Expression of type 'WrappedInt' cannot be used for assignment to type 'System.Int32'.

I know how to work around it, basically either access the Value property:

Expression right = Expression.Property(wrapperParameterExpression, nameof(WrappedInt.Value));

Or converting it:

Expression right = Expression.Convert(wrapperParameterExpression, typeof(int));

But why I cannot simply use the original direct assignment and make my implicit operator do the work?

Yuriy Ivaskevych
  • 956
  • 8
  • 18
  • 2
    Expressions are a lot more restrictive than normal C#, particularly around implicit type conversions. These are just the rules you need to play within. – canton7 Nov 05 '19 at 14:04
  • 1
    In the normal way the compiler does its magic for the implicit conversion. With expressions you have to be more explicit. – Nkosi Nov 05 '19 at 14:10
  • Hmm I see, so I guess I'll just stick with one of my workarounds then. Not very good with expressions yet so are such restrictions specified/listed somewhere? Or one would come across them by trial and error? – Yuriy Ivaskevych Nov 05 '19 at 14:15
  • 1
    https://stackoverflow.com/questions/6187664/implicit-cast-not-happening-in-expression-tree – Nkosi Nov 05 '19 at 14:15
  • Expressions are pretty much trial-and-error (though the [Expression docs](https://learn.microsoft.com/en-us/dotnet/api/system.linq.expressions.expression?view=netframework-4.8) do help), and looking at what the compiler will generate if you create an expression using a lambda (DebugView helps here). I wouldn't say using `Expression.Convert` is a workaround - you are *actually* doing a conversion. It's just that the C# compiler silently inserts it for you. – canton7 Nov 05 '19 at 14:34
  • @canton7, Makes sence, thanks. You might want to post that as answer so the question can be closed. – Yuriy Ivaskevych Nov 05 '19 at 15:50

1 Answers1

2

Expressions are more restrictive than C# in many ways, particularly around implicit type conversions. You will find many cases where the C# compiler will insert a type conversion for you, but where you need to add an explicit Expression.Convert.

If you want to call the implicit conversion method, you will need to add an Expression.Convert. This is how expressions work, for better or for worse. Doing so is not a workaround.

If you write the expression:

Expression<Func<WrappedInt, int>> convert = x => x;

and then look at convert.DebugView in a debugger, you see this:

.Lambda #Lambda1<System.Func`2[WrappedInt,System.Int32]>(WrappedInt $x) {
    (System.Int32)$x
}

That (System.Int32)$x is a Convert node (which you can verify by looking at convert.Body in the debugger). The compiler's decided that the best equivalent to that C# in the land of Expressions is Expression.Convert, so that is what you should do, too.

canton7
  • 37,633
  • 3
  • 64
  • 77