我有以下方法设置给定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
内处理此拳击的任何想法吗?
答案 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编译表达式的情况下,反射仍然在编译步骤中使用。