Buu*_*yen 11 .net c# reflection reflection.emit dynamic-method
假设我有以下代码更新struct使用反射的字段.由于struct实例被复制到DynamicUpdate方法中,因此需要在传递之前将其装箱到对象.
struct Person
{
public int id;
}
class Test
{
static void Main()
{
object person = RuntimeHelpers.GetObjectValue(new Person());
DynamicUpdate(person);
Console.WriteLine(((Person)person).id); // print 10
}
private static void DynamicUpdate(object o)
{
FieldInfo field = typeof(Person).GetField("id");
field.SetValue(o, 10);
}
}
Run Code Online (Sandbox Code Playgroud)
代码工作正常.现在,假设我不想使用反射,因为它很慢.相反,我想生成一些CIL直接修改id字段并将该CIL转换为可重用的委托(例如,使用动态方法功能).特别是,我想用s/t替换上面的代码,如下所示:
static void Main()
{
var action = CreateSetIdDelegate(typeof(Person));
object person = RuntimeHelpers.GetObjectValue(new Person());
action(person, 10);
Console.WriteLine(((Person)person).id); // print 10
}
private static Action<object, object> CreateSetIdDelegate(Type t)
{
// build dynamic method and return delegate
}
Run Code Online (Sandbox Code Playgroud)
我的问题:有没有办法CreateSetIdDelegate使用以下技术之一来实现排除?
Action<object, object>,使用签名的自定义委托public delegate void Setter(ref object target, object value).Action<object, object>,使用Action<object[], object>数组的第一个元素作为目标对象. 我不喜欢2和3的原因是因为我不想为对象的setter和struct的setter设置不同的委托(以及不希望使set-object-field委托更复杂而不是必要的,例如Action<object, object>).我认为实现CreateSetIdDelegate将根据目标类型是struct还是object来生成不同的CIL,但我希望它返回向用户提供相同API的同一委托.
Sam*_*ell 14
再次编辑:这个工作结构现在.
在C#4中有一种很棒的方法可以做到这一点,但在此之前你必须编写自己的ILGenerator发射代码.他们ExpressionType.Assign在.NET Framework 4中添加了一个.
这适用于C#4(已测试):
public delegate void ByRefStructAction(ref SomeType instance, object value);
private static ByRefStructAction BuildSetter(FieldInfo field)
{
ParameterExpression instance = Expression.Parameter(typeof(SomeType).MakeByRefType(), "instance");
ParameterExpression value = Expression.Parameter(typeof(object), "value");
Expression<ByRefStructAction> expr =
Expression.Lambda<ByRefStructAction>(
Expression.Assign(
Expression.Field(instance, field),
Expression.Convert(value, field.FieldType)),
instance,
value);
return expr.Compile();
}
Run Code Online (Sandbox Code Playgroud)
编辑:这是我的测试代码.
public struct SomeType
{
public int member;
}
[TestMethod]
public void TestIL()
{
FieldInfo field = typeof(SomeType).GetField("member");
var setter = BuildSetter(field);
SomeType instance = new SomeType();
int value = 12;
setter(ref instance, value);
Assert.AreEqual(value, instance.member);
}
Run Code Online (Sandbox Code Playgroud)
Bry*_*ner 10
我遇到了类似的问题,它花了我一个周末的大部分时间,但经过大量的搜索,阅读和反汇编C#测试项目后,我终于弄明白了.这个版本只需要.NET 2,而不是4.
public delegate void SetterDelegate(ref object target, object value);
private static Type[] ParamTypes = new Type[]
{
typeof(object).MakeByRefType(), typeof(object)
};
private static SetterDelegate CreateSetMethod(MemberInfo memberInfo)
{
Type ParamType;
if (memberInfo is PropertyInfo)
ParamType = ((PropertyInfo)memberInfo).PropertyType;
else if (memberInfo is FieldInfo)
ParamType = ((FieldInfo)memberInfo).FieldType;
else
throw new Exception("Can only create set methods for properties and fields.");
DynamicMethod setter = new DynamicMethod(
"",
typeof(void),
ParamTypes,
memberInfo.ReflectedType.Module,
true);
ILGenerator generator = setter.GetILGenerator();
generator.Emit(OpCodes.Ldarg_0);
generator.Emit(OpCodes.Ldind_Ref);
if (memberInfo.DeclaringType.IsValueType)
{
#if UNSAFE_IL
generator.Emit(OpCodes.Unbox, memberInfo.DeclaringType);
#else
generator.DeclareLocal(memberInfo.DeclaringType.MakeByRefType());
generator.Emit(OpCodes.Unbox, memberInfo.DeclaringType);
generator.Emit(OpCodes.Stloc_0);
generator.Emit(OpCodes.Ldloc_0);
#endif // UNSAFE_IL
}
generator.Emit(OpCodes.Ldarg_1);
if (ParamType.IsValueType)
generator.Emit(OpCodes.Unbox_Any, ParamType);
if (memberInfo is PropertyInfo)
generator.Emit(OpCodes.Callvirt, ((PropertyInfo)memberInfo).GetSetMethod());
else if (memberInfo is FieldInfo)
generator.Emit(OpCodes.Stfld, (FieldInfo)memberInfo);
if (memberInfo.DeclaringType.IsValueType)
{
#if !UNSAFE_IL
generator.Emit(OpCodes.Ldarg_0);
generator.Emit(OpCodes.Ldloc_0);
generator.Emit(OpCodes.Ldobj, memberInfo.DeclaringType);
generator.Emit(OpCodes.Box, memberInfo.DeclaringType);
generator.Emit(OpCodes.Stind_Ref);
#endif // UNSAFE_IL
}
generator.Emit(OpCodes.Ret);
return (SetterDelegate)setter.CreateDelegate(typeof(SetterDelegate));
}
Run Code Online (Sandbox Code Playgroud)
注意那里的"#if UNSAFE_IL"内容.我实际上提出了两种方法来实现它,但第一种方法真的是... hackish.引用Ecma-335,IL的标准文件:
"与要在对象中使用值类型的副本所需的box不同,unbox不需要从对象复制值类型.通常它只是计算已经存在的值类型的地址.盒装物品."
因此,如果您想要危险地玩,可以使用OpCodes.Unbox将对象句柄更改为指向您的结构的指针,然后可以将其用作Stfld或Callvirt的第一个参数.这样做实际上最终会修改结构,你甚至不需要通过ref传递目标对象.
但请注意,标准并不保证Unbox会为您提供指向盒装版本的指针.特别是,它表明Nullable <>可以导致Unbox创建副本.无论如何,如果发生这种情况,您可能会遇到静默失败,它会在本地副本上设置字段或属性值,然后立即丢弃.
因此,安全的方法是通过ref传递对象,将地址存储在局部变量中,进行修改,然后重新生成结果并将其放回ByRef对象参数中.
我做了一些粗略的时间,每个版本调用10,000,000次,有两种不同的结构:
具有1个字段的结构:.46 s"不安全"委托.70 s"安全"委托4.5 s FieldInfo.SetValue
具有4个字段的结构:.46 s"不安全"委托.88 s"安全"委托4.5 s FieldInfo.SetValue
请注意,装箱使"安全"版本速度随结构大小减小,而其他两种方法不受结构大小的影响.我想在某些时候拳击成本会超过反射成本.但我不相信任何重要能力的"不安全"版本.
小智 5
经过一些实验:
public delegate void ClassFieldSetter<in T, in TValue>(T target, TValue value) where T : class;
public delegate void StructFieldSetter<T, in TValue>(ref T target, TValue value) where T : struct;
public static class FieldSetterCreator
{
public static ClassFieldSetter<T, TValue> CreateClassFieldSetter<T, TValue>(FieldInfo field)
where T : class
{
return CreateSetter<T, TValue, ClassFieldSetter<T, TValue>>(field);
}
public static StructFieldSetter<T, TValue> CreateStructFieldSetter<T, TValue>(FieldInfo field)
where T : struct
{
return CreateSetter<T, TValue, StructFieldSetter<T, TValue>>(field);
}
private static TDelegate CreateSetter<T, TValue, TDelegate>(FieldInfo field)
{
return (TDelegate)(object)CreateSetter(field, typeof(T), typeof(TValue), typeof(TDelegate));
}
private static Delegate CreateSetter(FieldInfo field, Type instanceType, Type valueType, Type delegateType)
{
if (!field.DeclaringType.IsAssignableFrom(instanceType))
throw new ArgumentException("The field is declared it different type");
if (!field.FieldType.IsAssignableFrom(valueType))
throw new ArgumentException("The field type is not assignable from the value");
var paramType = instanceType.IsValueType ? instanceType.MakeByRefType() : instanceType;
var setter = new DynamicMethod("", typeof(void),
new[] { paramType, valueType },
field.DeclaringType.Module, true);
var generator = setter.GetILGenerator();
generator.Emit(OpCodes.Ldarg_0);
generator.Emit(OpCodes.Ldarg_1);
generator.Emit(OpCodes.Stfld, field);
generator.Emit(OpCodes.Ret);
return setter.CreateDelegate(delegateType);
}
}
Run Code Online (Sandbox Code Playgroud)
与表达式树方法的主要区别在于,只读字段也可以更改.
| 归档时间: |
|
| 查看次数: |
7705 次 |
| 最近记录: |