1

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.

3
  • @Pac0 you can't have Action<ref T>, though... not sure if Expression is going to work with a custom delegate with a ref arg Commented Apr 15, 2018 at 23:12
  • @MarcGravell I see, i am now understanding OP's problem. Thanks. Commented Apr 15, 2018 at 23:14
  • BTW: I do a lot of meta-programming, and frankly for UWP I've reached the conclusion that the most pragmatic option for this type of scenario is going to be Roslyn-based code analysis and C# emit, so the emit happens as a build step Commented Apr 15, 2018 at 23:26

2 Answers 2

1

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.

Sign up to request clarification or add additional context in comments.

3 Comments

The question seems to be about setting a struct field, not getting it... which would make copying the struct very wrong.
@BenVoigt unless it is a With(...)-style return, but yes: agreed
thanks a lot. I added some comments in my code at the end to clarify what I want to get. I intuitively thought that MakeByRefType could be useful but I didn't know how it should have worked at all. I am not sure I got your code, but I will try to adapt it to my case asap!
0

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);
    }
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.