我可以使用表达式设置结构的属性吗?

时间:2017-01-10 16:52:02

标签: c# .net expression fastmember

我有以下方法设置给定PropertyInfo上给定TInstance的值。这是为了避免反思效率低下。

public static Action<TInstance, object> CreateSetter<TInstance>(PropertyInfo propertyInfo, bool includeNonPublic = false)
{
    var setMethod = propertyInfo.GetSetMethod(includeNonPublic);

    var instance = Expression.Parameter(typeof(TInstance), "instance");
    var value = Expression.Parameter(typeof(object), "value");
    var valueCast = !propertyInfo.PropertyType.IsValueType
        ? Expression.TypeAs(value, propertyInfo.PropertyType)
        : Expression.Convert(value, propertyInfo.PropertyType);

    return Expression.Lambda<Action<TInstance, object>>(
        Expression.Call(instance, setMethod, valueCast), instance, value).Compile();
}

所以给出以下模型:

public sealed class PersonClass
{
    public string Name {get; set;}    
}

我可以使用

设置Name
var person = new PersonClass(); 
var nameProp = person.GetType().GetProperties().Where(p => p.Name == "Name").First();
var nameSetter = CreateSetter<PersonClass>(nameProp);
nameSetter(person, "Foo");

如果我尝试使用struct例如:

的方法,这一切都很好
public struct PersonStruct
{
    public string Name {get; set;}    
}

名称始终为null。我怀疑拳击/拆箱是以某种方式咬我。

事实上,如果我使用FastMember,则在使用时会出现相同的行为:

PersonStruct person = new PersonStruct();   
var accessor = TypeAccessor.Create(person.GetType());       
accessor[person, "Name"] = "Foo";

但是当我将person打为object时,FastMember可以正确设置值:

object person = new PersonStruct(); 
var accessor = TypeAccessor.Create(person.GetType());       
accessor[person, "Name"] = "Foo";

如果CreateSetter是值类型,我可以在TInstance内处理此拳击的任何想法吗?

2 个答案:

答案 0 :(得分:1)

如评论中所述,你真的不应该创建可变结构。但是,要回答这个问题,结构是值类型,因此结构的副本将传递给Action,因此原始人值不会更改。

您需要一种通过引用传递struct的方法。 但是,表达式不支持创建通过引用获取参数的“方法”。

您可以使用DynamicMethod类来执行以下操作:

public delegate void StructSetter<TInstance>(ref TInstance instance, object value) where TInstance : struct;

public StructSetter<TInstance> CreateSetter<TInstance>(
    PropertyInfo propertyInfo,
    bool includeNonPublic = false) where TInstance : struct
{
    DynamicMethod method =
        new DynamicMethod(
            "Set",
            typeof(void),
            new [] { typeof(TInstance).MakeByRefType(), typeof(object )},
            this.GetType());

    var generator = method.GetILGenerator();

    generator.Emit(OpCodes.Ldarg_0);
    generator.Emit(OpCodes.Ldarg_1);
    generator.Emit(OpCodes.Callvirt, propertyInfo.GetSetMethod(includeNonPublic));
    generator.Emit(OpCodes.Ret);

    return (StructSetter<TInstance>)method.CreateDelegate(typeof (StructSetter<TInstance> ));
}

我们必须创建一个StructSetter委托,因为标准Action委托不支持通过引用传递。

不要忘记缓存委托,否则编译成本会降低您的申请速度。

答案 1 :(得分:1)

您需要一个表达式来创建一个带有by-ref参数的委托,这样它将影响传递的结构,而不是副本。 E.g:

public struct PersonStruct
{
    public string Name {get; set;}    
}

delegate void FirstByRefAction<T1, T2>(ref T1 arg1, T2 arg2);

void Main()
{
    ParameterExpression par1 = Expression.Parameter(typeof(PersonStruct).MakeByRefType());
    ParameterExpression par2 = Expression.Parameter(typeof(string));
    FirstByRefAction<PersonStruct, string> setter = Expression.Lambda<FirstByRefAction<PersonStruct, string>>(
        Expression.Assign(Expression.Property(par1, "Name"), par2),
        par1, par2
        ).Compile();
    PersonStruct testStruct = new PersonStruct();
    setter(ref testStruct, "Test Name");
    Console.Write(testStruct.Name); // outputs "Test Name"
}
  

这是为了避免反思效率低下。

请注意,方法和属性调用表达式类型主要在内部使用反射。在解释表达式的情况下,它们在每次调用时都这样做,在IL编译表达式的情况下,反射仍然在编译步骤中使用。