Expression.Lambda to generate a function with return value

c# expression-trees

Question

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.

Accepted Answer

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.


Popular Answer

I don't understand what's going on, but this seems to have worked right

    public static CastedAction<T> MakeSetter(FieldInfo field)
    {
        if (field.FieldType.IsInterfaceEx() == true && field.FieldType.IsValueTypeEx() == false)
        {
            ParameterExpression targetExp = Expression.Parameter(typeof(T).MakeByRefType(), "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);

            var setter = Expression.Lambda<ActionRef<T, object>>(assignExp, targetExp, valueExp).Compile();

            return new CastedAction<T>(setter); 
        }

        throw new ArgumentException("<color=orange>Svelto.ECS</color> unsupported field (must be an interface and a class)");
    }

public delegate void ActionRef<T, O>(ref T target, O value);

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

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

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

    public void Call(ref T target, object value)
    {
        setter(ref target, value);
    }
}


Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Is this KB legal? Yes, learn why
Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Is this KB legal? Yes, learn why