Search code examples
c#expression-trees

Expression.Lambda to generate a function with return value


I looked up for something similar, but I couldn't find anything that I am able to comprehend at the moment. I never needed to use Expressions so I don't really understand how they work, although they do look interesting. I can spend time studying them, but currently I am using them just for one single purpose, which is not really what they have been designed for, although I may be wrong here. It's to generate a run time function to set a value in to a FieldInfo of a class instance.

I would have done it with opcodes, which I understand better, but opcodes are not available on all the platforms I need to work with (i.e.: UWP). With NetStandard 2.0 I may actually use them, but before I try it, I wonder if you can tell me if this is possible. Currently I am using this code:

 public static CastedAction<T> MakeSetter(FieldInfo field)
    {
        if (field.FieldType.IsInterfaceEx() == true && field.FieldType.IsValueTypeEx() == false)
        {
            ParameterExpression targetExp = Expression.Parameter(typeof(T), "target");
            ParameterExpression valueExp = Expression.Parameter(typeof(object), "value");

            MemberExpression fieldExp = Expression.Field(targetExp, field);
            UnaryExpression convertedExp = Expression.TypeAs(valueExp, field.FieldType);
            BinaryExpression assignExp = Expression.Assign(fieldExp, convertedExp);

            Type type = typeof(Action<,>).MakeGenericType(typeof(T), typeof(object));

            var setter = Expression.Lambda(type, assignExp, targetExp, valueExp).Compile();

            return new CastedAction<T>(setter); 
        }

        throw new ArgumentException();
    }
}

public class CastedAction<T>  
{
    readonly Action<T, object> setter;

    public CastedAction(Delegate setter)
    {
        this.setter = (Action<T, object>)setter;
    }

    public CastedAction(Action<T, object> setter)
    {
        this.setter = setter;
    }

    public void Call(ref T target, object value)
    {
        setter(target, value); //I want to pass ref target here
        //target = setter(target, value); may be an alternative
    }

However nowe I want to support structs as well and what I would like to have is to pass the first parameter in the setter Action by ref. As far as I understood this is not possible.

Therefore I was thinking to generate a Func returning the modified object passed by parameter. Here I got totally lost, too hard for me.

Even tho you may find a solution for this, I may still think to switch 100% op code as returning and passing by value could affect the performance of my application.


Solution

  • Amazingly, this simplified pass-by-ref Expression example with a custom delegate type works... on full framework. I'm not at all sure that it will work the same way on a platform without full Compile(), because you can't really represent a ref T anywhere other than the stack, but ... worth a try:

    using System;
    using System.Linq.Expressions;
    using System.Runtime.Serialization;
    
    delegate string ByRefFunc<T>(ref T val);
    struct X
    {
        public X(string name) => Name = name;
        public string Name { get; }
    }
    static class P
    {
        static void Main()
        {
            var p = Expression.Parameter(typeof(X).MakeByRefType(), "p");
            var lambda = Expression.Lambda<ByRefFunc<X>>(
                Expression.Property(p, "Name"), p);
            X x = new X("abc");
            var s = lambda.Compile()(ref x);
            Console.WriteLine(s);       
        }
    }
    

    Note that a struct copy (because of missing ref support) isn't the end of the world, unless you have huge structs.