假设我有下面的代码,它使用反射更新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);
}
}
代码工作正常。现在,假设我不想使用反射,因为它很慢。相反,我想生成一些CIL,直接修改id
字段,并将该CIL转换为可重用的委托(例如,使用DynamicMethod特性)。特别地,我想用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
?
1.使用反射生成调用setter的CIL(作为本文的第一个代码段)。这是没有意义的,因为要求是摆脱反射,但这是一个可能的实现,所以我只是提到。
1.使用签名为public delegate void Setter(ref object target, object value)
的自定义委托,而不是使用Action<object, object>
。
1.不使用Action<object, object>
,而使用Action<object[], object>
,数组的第一个元素是目标对象。
我不喜欢2和3的原因是因为我不想让对象的setter和结构的setter有不同的委托(以及不想让set-object-field委托比必要的更复杂,例如。Action<object, object>
)。我估计CreateSetIdDelegate
的实现将根据目标类型是struct还是object生成不同的CIL,但我希望它返回相同的委托,为用户提供相同的API。
6条答案
按热度按时间yhqotfr81#
再次编辑:现在这个工作结构。
在C# 4中有一种很好的方法来实现这一点,但是在此之前,您必须为任何东西编写自己的
ILGenerator
发出代码。他们在.NET Framework 4中添加了ExpressionType.Assign
。这在C# 4中有效(已测试):
编辑:这是我的测试代码。
wbrvyc0a2#
我遇到了一个类似的问题,花了我一个周末的大部分时间,但在大量搜索、阅读和分解C#测试项目后,我终于弄明白了。而且这个版本只需要.NET 2,而不是4。
注意这里的“#if UNSAFE_IL”。我其实想出了两种方法,但第一种是真的...引用Ecma-335,IL的标准文档:
“与box不同,复制值类型以在对象中使用时需要box,而从对象复制值类型时不需要unbox。通常,它只是计算已经存在于装箱对象内部的值类型的地址。
因此,如果你想玩危险的游戏,你可以使用OpCodes.Unbox将对象句柄更改为指向结构的指针,然后可以将其用作Stfld或Callvirt的第一个参数。这样做实际上会在适当的位置修改结构体,甚至不需要通过ref传递目标对象。
但是,请注意,该标准并不保证Unbox将为您提供一个指向盒装版本的指针。特别是,它建议Nullable<>可以导致Unbox创建副本。无论如何,如果发生这种情况,您可能会得到一个无声的失败,它在本地副本上设置字段或属性值,然后立即丢弃。
因此,安全的方法是通过ref传递对象,将地址存储在局部变量中,进行修改,然后将结果重新装箱并将其放回ByRef对象参数中。
我做了一些粗略的计时,调用每个版本10,000,000次,使用2种不同的结构:
包含1个字段的结构:.46 s“不安全”委托.70 s“安全”委托4.5 s FieldInfo.SetValue
包含4个字段的结构:.46 s“不安全”委托.88 s“安全”委托4.5 s FieldInfo.SetValue
请注意,装箱使“安全”版本的速度随着结构大小而降低,而其他两种方法不受结构大小的影响。我想在某个时候装箱成本会超过反射成本。但我不相信“不安全”的版本在任何重要的能力。
vohkndzv3#
经过一些实验:
与表达式树方法的主要区别在于只读字段也可以更改。
cbeh67ev4#
下面的代码适用于结构体而不使用ref:
下面是我的测试代码:
lf5gs5x25#
你可能想看看动态方法(反射不一定很慢!)...
Gerhard对此有一个很好的文章:http://jachman.wordpress.com/2006/08/22/2000-faster-using-dynamic-method-calls/(已存档here。)
jvidinwx6#
您可以相当容易地修改它以使用结构。它目前是基于字典的,但你的情况更容易。
http://www.damonpayne.com/2009/09/07/TwoWayBindingToNameValuePairs.aspx