Question

I've got an extension method that used to take a strongly typed Expression<Func<>> parameter however for implementation reasons I had to change it to use a weakly type version. This has had a strange affect on the expression parameter as it now seems to be wrapping the lambda expression in an explicit call to a 'Convert' method.

Previously the paramters would look like:

m => m.Data

And now it looks like the following:

m => Convert(m.Data)

I have replicated the issue with the following example code:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Linq.Expressions;

namespace ConsoleApplication
{
    static class Program
    {
        static void Main(string[] args)
        {
            Model model = new Model()
            {
                Data = 123
            };

            Test(m => m.Data, m => m.Data);

            Console.ReadLine();                
        }

        public static void Test<TProperty>(Expression<Func<Model, TProperty>> strongTyped, Expression<Func<Model, object>> weakTyped)
        {
            Console.WriteLine("Strong Typed: {0}", strongTyped);
            Console.WriteLine("Weak Typed: {0}", weakTyped);
        }
    }

    public class Model
    {
        public int Data
        {
            get;
            set;
        }
    }
}

The output of which is as follows:

Strong Typed: m => m.Data
Weak Typed: m => Convert(m.Data)

I am guessing it has something to do with auto boxing the value type into an object type. Can anyone confirm this or does anyone know what is going on? Also does anyone know where the Convert method is declared?

Calling the compile method on the weak typed expression gives the following:

weakTyped.Compile().Method
{System.Object lambda_method(System.Runtime.CompilerServices.Closure, ConsoleApplication.Model)}
    [System.Reflection.Emit.DynamicMethod.RTDynamicMethod]: {System.Object lambda_method(System.Runtime.CompilerServices.Closure, ConsoleApplication.Model)}
    base {System.Reflection.MethodBase}: {System.Object lambda_method(System.Runtime.CompilerServices.Closure, ConsoleApplication.Model)}
    MemberType: Method
    ReturnParameter: null
    ReturnType: {Name = "Object" FullName = "System.Object"}
    ReturnTypeCustomAttributes: {System.Reflection.Emit.DynamicMethod.RTDynamicMethod.EmptyCAHolder}
Was it helpful?

Solution

Convert isn't a method at all - it's a UnaryExpression and indeed is there exactly why you theorized - boxing / type coercion. It's interesting to see when expression trees are generated, things we generally know are implicit actually appear explicitly.

If you were building an expression yourself, you can get the same effect by calling Expression.Convert():

Creates a UnaryExpression that represents a type conversion operation.

OTHER TIPS

Yes, it is Conversion expression which represents boxing and not only boxing. It contains user defined conversions etc.

For example, If a type defines user defined conversions that will be convered using "Conversion expression". In this case weakTyped.Body.Method will return the overloaded method something like op_Implicit...

You can prove this with following code.

public static void Test<TProperty>(Expression<Func<Model, TProperty>> strongTyped, Expression<Func<Model, object>> weakTyped)
{
    var expr = (UnaryExpression)weakTyped.Body;
    Console.WriteLine("Weak Typed method: {0}", expr.Method);
    Console.WriteLine("Strong Typed: {0}", strongTyped);
    Console.WriteLine("Weak Typed: {0}", weakTyped);
}
public static void TestFloat<TProperty>(Expression<Func<Model, TProperty>> strongTyped, Expression<Func<Model, decimal>> weakTyped)
{
    var expr = (UnaryExpression) weakTyped.Body;
    Console.WriteLine("Weak Typed method: {0}", expr.Method);
    Console.WriteLine("Strong Typed: {0}", strongTyped);
    Console.WriteLine("Weak Typed: {0}", weakTyped);
}

For decimal type this returns overloaded operator where as object weakTyped.Body.Method will be null since it is just a boxing conversion.

The conversion has to be included in the expression tree because object is not the same as the given parameter, but since it's a generic method, you could make the weak strong, by including the generic return property

public static void Test<TProperty, TReturnValue>(Expression<Func<Model, TProperty>> strongTyped, Expression<Func<Model, TReturnValue>> weakTyped)
{
    Console.WriteLine("Strong Typed: {0}", strongTyped);
    Console.WriteLine("Weak (now also strong) Typed: {0}", weakTyped);
}

You could still use the same call Test(m => m.Data, m => m.Data);, and both TProperty and TReturnValue will be resolved by the compiler

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top