我可以使用表达式设置结构的 属性 吗?
Can I set the property of a struct using Expressions?
我有以下方法可以在给定 TInstance
上设置给定 PropertyInfo
的值。这是为了避免反射的低效率。
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
。我怀疑 boxing/unboxing 不知何故在咬我。
事实上,如果我使用 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";
当 TInstance
是值类型时,我如何在 CreateSetter
中处理这个装箱有什么想法吗?
如评论中所述,您确实不应该创建可变结构。但是,要回答这个问题,结构是值类型,因此您的结构的副本会传递给 Action
,因此原始的 person 值不会更改。
您需要一种通过引用传递 struct
的方法。 但是,表达式不支持创建通过引用获取参数的 "methods"。
你可以使用 DynamicMethod
class 来做这样的事情:
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
委托不支持通过引用传递。
不要忘记缓存委托,否则编译成本会减慢您的应用程序。
您需要一个表达式来创建一个接受引用参数的委托,这样它将影响传递的结构,而不是副本。例如:
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"
}
This is to avoid the inefficiency of reflection.
请注意,调用方法和 属性 的表达式类型主要在内部使用反射。在解释表达式的情况下,它们在每次调用时都这样做,在 IL 编译表达式的情况下,编译步骤中仍然使用反射。
我有以下方法可以在给定 TInstance
上设置给定 PropertyInfo
的值。这是为了避免反射的低效率。
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
。我怀疑 boxing/unboxing 不知何故在咬我。
事实上,如果我使用 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";
当 TInstance
是值类型时,我如何在 CreateSetter
中处理这个装箱有什么想法吗?
如评论中所述,您确实不应该创建可变结构。但是,要回答这个问题,结构是值类型,因此您的结构的副本会传递给 Action
,因此原始的 person 值不会更改。
您需要一种通过引用传递 struct
的方法。 但是,表达式不支持创建通过引用获取参数的 "methods"。
你可以使用 DynamicMethod
class 来做这样的事情:
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
委托不支持通过引用传递。
不要忘记缓存委托,否则编译成本会减慢您的应用程序。
您需要一个表达式来创建一个接受引用参数的委托,这样它将影响传递的结构,而不是副本。例如:
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"
}
This is to avoid the inefficiency of reflection.
请注意,调用方法和 属性 的表达式类型主要在内部使用反射。在解释表达式的情况下,它们在每次调用时都这样做,在 IL 编译表达式的情况下,编译步骤中仍然使用反射。