生成动态方法来设置结构体的字段,而不是使用反射
假设我有以下代码,它使用反射更新 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);
}
}
该代码运行良好。 现在,假设我不想使用反射,因为它很慢。 相反,我想生成一些直接修改 id 字段的 CIL,并将该 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
}
我的问题:除了使用以下技术之一之外,还有什么方法可以实现 CreateSetIdDelegate
吗?
- 生成使用反射调用 setter 的 CIL(如本文中的第一个代码段)。 这是没有意义的,因为要求是摆脱反射,但这是一个可能的实现,所以我只是提到。
- 不要使用 Action
- 不要使用
Action
,而是使用Action
并将数组的第一个元素作为目标对象。
我不喜欢2&的原因是 3 是因为我不想为对象的 setter 和结构的 setter 使用不同的委托(也不想让 set-object-field 委托变得比必要的更复杂,例如 Action
)。 我认为 CreateSetIdDelegate 的实现会根据目标类型是结构体还是对象生成不同的 CIL,但我希望它返回向用户提供相同 API 的相同委托。
Let's say I have the following code which update a field of a struct
using reflection. Since the struct instance is copied into the DynamicUpdate
method, it needs to be boxed to an object before being passed.
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);
}
}
The code works fine. Now, let's say I don't want to use reflection because it's slow. Instead, I want to generate some CIL directly modifying the id
field and convert that CIL into a reusable delegate (say, using Dynamic Method feature). Specially, I want to replace the above code with s/t like this:
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
}
My question: is there any way to implement CreateSetIdDelegate
excepts from using one of the following techniques?
- Generate CIL that invoke the setter using reflection (as the 1st code segment in this post). This makes no sense, given the requirement is to get rid of reflection, but it's a possible implementation so I just mention.
- Instead of using
Action<object, object>
, use a custom delegate whose signature ispublic delegate void Setter(ref object target, object value)
. - Instead of using
Action<object, object>
, useAction<object[], object>
with the 1st element of the array being the target object.
The reason I don't like 2 & 3 is because I don't want to have different delegates for the setter of object and setter of struct (as well as not wanting to make the set-object-field delegate more complicated than necessary, e.g. Action<object, object>
). I reckon that the implementation of CreateSetIdDelegate
would generate different CIL depending whether the target type is struct or object, but I want it to return the same delegate offering the same API to user.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(6)
再次编辑:现在可以工作了。
在 C# 4 中有一种很棒的方法可以做到这一点,但在此之前您必须为任何内容编写自己的
ILGenerator
发出代码。 他们向 .NET Framework 4 添加了ExpressionType.Assign
。这适用于 C# 4(已测试):
编辑:这是我的测试代码。
EDIT again: This works structs now.
There's a gorgeous way to do it in C# 4, but you'll have to write your own
ILGenerator
emit code for anything before that. They added anExpressionType.Assign
to the .NET Framework 4.This works in C# 4 (tested):
Edit: Here was my test code.
我遇到了类似的问题,花了我大半个周末,但经过大量搜索、阅读和反汇编 C# 测试项目后,我终于弄清楚了。 而且这个版本只需要 .NET 2,而不是 4。
请注意其中的“#if UNSAFE_IL”内容。 我实际上想出了两种方法来做到这一点,但第一种方法真的……很黑客。 引用 IL 标准文档 Ecma-335 中的话:
“与需要复制值类型以便在对象中使用的装箱不同,取消装箱不需要从对象复制值类型。通常它只是计算已存在于装箱对象内部的值类型的地址。”
因此,如果你想玩危险的游戏,你可以使用 OpCodes.Unbox 将对象句柄更改为指向结构的指针,然后可以将其用作 Stfld 或 Callvirt 的第一个参数。 这样做实际上最终会就地修改结构,您甚至不需要通过 ref 传递目标对象。
但是,请注意,该标准并不保证 Unbox 将为您提供指向盒装版本的指针。 特别地,它表明 Nullable<> 。 可以导致取消装箱创建副本。 无论如何,如果发生这种情况,您可能会遇到静默失败,它会在本地副本上设置字段或属性值,然后立即将其丢弃。
因此,安全的方法是通过 ref 传递对象,将地址存储在局部变量中,进行修改,然后重新装箱结果并将其放回 ByRef 对象参数中。
我做了一些粗略的计时,使用 2 种不同的结构调用每个版本 10,000,000 次:
具有 1 个字段的结构:
.46 s“不安全”代表
.70 s“安全”代表
4.5 s FieldInfo.SetValue
具有 4 个字段的结构:
.46 s“不安全”代表
.88 s“安全”代表
4.5 s FieldInfo.SetValue
请注意,装箱使“安全”版本速度随结构大小而降低,而其他两种方法不受结构大小的影响。 我想在某些时候,装箱成本会超过反射成本。 但我不会相信“不安全”版本的任何重要功能。
I ran into a similar issue, and it took me most of a weekend, but I finally figured it out after a lot of searching, reading, and disassembling C# test projects. And this version only requires .NET 2, not 4.
Note the "#if UNSAFE_IL" stuff in there. I actually came up with 2 ways to do it, but the first one is really... hackish. To quote from Ecma-335, the standards document for IL:
"Unlike box, which is required to make a copy of a value type for use in the object, unbox is not required to copy the value type from the object. Typically it simply computes the address of the value type that is already present inside of the boxed object."
So if you want to play dangerously, you can use OpCodes.Unbox to change your object handle into a pointer to your structure, which can then be used as the first parameter of a Stfld or Callvirt. Doing it this way actually ends up modifying the struct in place, and you don't even need to pass your target object by ref.
However, note that the standard doesn't guarantee that Unbox will give you a pointer to the boxed version. Particularly, it suggests that Nullable<> can cause Unbox to create a copy. Anyway, if that happens, you'll probably get a silent failure, where it sets the field or property value on a local copy which is then immediately discarded.
So the safe way to do it is pass your object by ref, store the address in a local variable, make the modification, and then rebox the result and put it back in your ByRef object parameter.
I did some rough timings, calling each version 10,000,000 times, with 2 different structures:
Structure with 1 field:
.46 s "Unsafe" delegate
.70 s "Safe" delegate
4.5 s FieldInfo.SetValue
Structure with 4 fields:
.46 s "Unsafe" delegate
.88 s "Safe" delegate
4.5 s FieldInfo.SetValue
Notice that the boxing makes the the "Safe" version speed decrease with structure size, whereas the other two methods are unaffected by structure size. I guess at some point the boxing cost would overrun the reflection cost. But I wouldn't trust the "Unsafe" version in any important capacity.
经过一些实验:
与表达式树方法的主要区别在于只读字段也可以更改。
After some experiments:
The main difference from the expression tree approach is that readonly fields can also be changed.
此代码适用于不使用 ref 的结构:
这是我的测试代码:
This code works for structs without using ref:
Here is my test code:
您可能想看看动态方法(反射不必很慢!)...
Gerhard 有一篇关于此的很好的文章:http://jachman.wordpress.com/2006/08/22/2000-faster-using-dynamic-method-calls/ (存档此处。)
You may want to have a look at dynamic methods (reflection does not have to be slow!)...
Gerhard has a nice post about that: http://jachman.wordpress.com/2006/08/22/2000-faster-using-dynamic-method-calls/ (archived here.)
您可以相当容易地修改它以使用结构。 目前它是基于字典的,但你的情况更容易。
http://www.damonpayne.com/2009/09/07/TwoWayBindingToNameValuePairs。 ASPX
You could fairly easily modify this to work with structs. It's currently dictionary based but your situation is easier.
http://www.damonpayne.com/2009/09/07/TwoWayBindingToNameValuePairs.aspx