Expression.Lambda生成一个带返回值的函数

时间:2018-04-15 23:06:57

标签: c# expression-trees

我抬头看了类似的东西,但我目前无法找到任何我能理解的东西。我从来不需要使用表达式,所以我不能真正理解它们是如何工作的,尽管它们看起来很有趣。我可以花时间研究它们,但目前我只是为了一个目的使用它们,这不是它们的设计目的,尽管我可能在这里错了。生成运行时函数以将值设置为类实例的FieldInfo。

我会用opcodes做到这一点,我更了解,但操作码并不适用于我需要使用的所有平台(即:UWP)。使用NetStandard 2.0,我可能会使用它们,但在我尝试之前,我想知道你是否可以告诉我这是否可行。目前我正在使用此代码:

 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
    }

但是现在我想支持结构,我想要的是通过ref传递setter Action中的第一个参数。据我所知,这是不可能的。

因此我想生成一个Func,返回参数传递的修改对象。在这里,我完全迷失了,对我来说太难了。

即使您可以找到解决方案,我仍然可以考虑将100%操作码转换为返回值,并且按值传递可能会影响我的应用程序的性能。

2 个答案:

答案 0 :(得分:1)

令人惊讶的是,这个带有自定义委托类型的简化pass-by-ref Expression示例适用于完整框架。我完全不确定它会在没有完整Compile()的平台上以相同的方式工作,因为你无法真正代表堆栈以外的任何地方ref T ,但......值得一试:

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

请注意,struct副本(因为缺少ref支持)不是世界末日,除非你有巨大的 struct秒。

答案 1 :(得分:0)

我不明白发生了什么,但这似乎工作正常

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