2

我有以下方法,它在给定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";

但是,当我将personas装箱时,object便FastMember能够正确设置该值:

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

有什么想法可以在CreateSetterfor whenTInstance是值类型时处理这个拳击吗?

4

2 回答 2

1

您需要一个表达式来创建一个接受 by-ref 参数的委托,以便它影响传递的结构,而不是副本。例如:

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 编译表达式的情况下,反射仍然在编译步骤中使用。

于 2017-01-10T17:29:49.097 回答
1

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

您需要一种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委托不支持通过引用传递。

不要忘记缓存委托,否则编译成本会减慢您的应用程序。

于 2017-01-10T17:19:05.720 回答