想象一下,我们有一个可变的struct
(是的,不要开始):
public struct MutableStruct
{
public int Foo { get; set; }
public override string ToString()
{
return Foo.ToString();
}
}
使用反射,我们可以获取此struct
的盒装实例并在框内突变:
// this is basically what we want to emulate
object obj = new MutableStruct { Foo = 123 };
obj.GetType().GetProperty("Foo").SetValue(obj, 456);
System.Console.WriteLine(obj); // "456"
我喜欢做的是写一些可以做到这一点的IL - 但速度更快。我是一个元编程瘾君子; p
取消包装任何值并使用常规IL改变值是微不足道的 - 但你不能只是在之后调用它,因为这会创建一个不同的框。我猜测我们需要做的是将它复制到现有的盒子上。我调查了ldobj
/ stobj
,但这些似乎没有完成任务(除非我遗漏了一些东西)。
那么:是否存在这样做的机制?或者我是否必须限制自己进行反思以执行盒装struct
的就地更新?
或换句话说:... evil goes here...
是什么?
var method = new DynamicMethod("evil", null,
new[] { typeof(object), typeof(object) });
var il = method.GetILGenerator();
// ... evil goes here...
il.Emit(OpCodes.Ret);
Action<object, object> action = (Action<object, object>)
method.CreateDelegate(typeof(Action<object, object>));
action(obj, 789);
System.Console.WriteLine(obj); // "789"
答案 0 :(得分:18)
嗯,这很有趣。
使用
实际上,它主要是Unbox(见history for version that works with Ldflda
和Stind_*
似乎有效。Ldflda
and Stind_*
)。
这是我在LinqPad中一起攻击的内容,以证明这一点。
public struct MutableStruct
{
public int Foo { get; set; }
public override string ToString()
{
return Foo.ToString();
}
}
void Main()
{
var foo = typeof(MutableStruct).GetProperty("Foo");
var setFoo = foo.SetMethod;
var dynMtd = new DynamicMethod("Evil", typeof(void), new [] { typeof(object), typeof(int) });
var il = dynMtd.GetILGenerator();
il.Emit(OpCodes.Ldarg_0); // object
il.Emit(OpCodes.Unbox, typeof(MutableStruct)); // MutableStruct&
il.Emit(OpCodes.Ldarg_1); // MutableStruct& int
il.Emit(OpCodes.Call, setFoo); // --empty--
il.Emit(OpCodes.Ret); // --empty--
var del = (Action<object, int>)dynMtd.CreateDelegate(typeof(Action<object, int>));
var mut = new MutableStruct { Foo = 123 };
var boxed= (object)mut;
del(boxed, 456);
var unboxed = (MutableStruct)boxed;
// unboxed.Foo = 456, mut.Foo = 123
}
答案 1 :(得分:6)
你走了:
只需使用unsafe
:)
static void Main(string[] args)
{
object foo = new MutableStruct {Foo = 123};
Console.WriteLine(foo);
Bar(foo);
Console.WriteLine(foo);
}
static unsafe void Bar(object foo)
{
GCHandle h = GCHandle.Alloc(foo, GCHandleType.Pinned);
MutableStruct* fp = (MutableStruct*)(void*) h.AddrOfPinnedObject();
fp->Foo = 789;
}
IL的实施留给了读者。
<强>更新强>
根据Kevin的回答,这是一个最小的工作示例:
ldarg.0
unbox MutableStruct
ldarg.1
call instance void MutableStruct::set_Foo(int32)
ret
答案 2 :(得分:2)
您可以更轻松地完成此操作。在我们有动态的.NET 4.5下试试这个。
struct Test
{
public Int32 Number { get; set; }
public override string ToString()
{
return this.Number.ToString();
}
}
class Program
{
static void Main( string[] args )
{
Object test = new Test();
dynamic proxy = test;
proxy.Number = 1;
Console.WriteLine( test );
Console.ReadLine();
}
}
我知道这不是反思,但仍然很有趣。
答案 3 :(得分:1)
即使没有不安全的代码,纯粹的C#:
using System;
internal interface I {
void Increment();
}
struct S : I {
public readonly int Value;
public S(int value) { Value = value; }
public void Increment() {
this = new S(Value + 1); // pure evil :O
}
public override string ToString() {
return Value.ToString();
}
}
class Program {
static void Main() {
object s = new S(123);
((I) s).Increment();
Console.WriteLine(s); // prints 124
}
}
在C#中,this
引用内部值类型实例方法实际上是ref
- 参数(或out
- 值类型构造函数中的参数,这就是为什么this
可以不会被捕获到闭包中,就像任何方法中的ref
/ out
参数一样)并且可以修改。
在未装箱的值上调用struct instance方法时,this
赋值将有效替换调用站点的值。当在盒装实例上调用实例方法时(通过上面示例中的虚拟调用或接口调用),ref
- 参数指向box对象内的值,因此可以修改盒装值。
答案 4 :(得分:0)
我发布了solution using Expression Trees for setting fields in another thread。将代码更改为使用属性是非常简单的: