13

How can I set value into struct field - myStruct.myField with reflection using DynamicMethod? When I call setter(myStruct, 111) value was not set, because MyStruct is value type. Console.WriteLine(myStruct.myField) shows value 3.
How to modify GetDelegate method to set value into myStruct.myField?

public struct MyStruct
{
    public int myField;
}

public delegate void SetHandler(object source, object value);

private static SetHandler GetDelegate(Type type, FieldInfo fieldInfo)
{
    DynamicMethod dm = new DynamicMethod("setter", typeof(void), new Type[] { typeof(object), typeof(object) }, type, true);
    ILGenerator setGenerator = dm.GetILGenerator();

    setGenerator.Emit(OpCodes.Ldarg_0);
    setGenerator.DeclareLocal(type);
    setGenerator.Emit(OpCodes.Unbox_Any, type);
    setGenerator.Emit(OpCodes.Stloc_0);
    setGenerator.Emit(OpCodes.Ldloca_S, 0);
    setGenerator.Emit(OpCodes.Ldarg_1);
    setGenerator.Emit(OpCodes.Unbox_Any, fieldInfo.FieldType);
    setGenerator.Emit(OpCodes.Stfld, fieldInfo);
    setGenerator.Emit(OpCodes.Ldloc, 0);
    setGenerator.Emit(OpCodes.Box, type);
    setGenerator.Emit(OpCodes.Ret);
    return (SetHandler)dm.CreateDelegate(typeof(SetHandler));
}

MyStruct myStruct = new MyStruct();
myStruct.myField = 3;

FieldInfo fi = typeof(MyStruct).GetField("myField", BindingFlags.Public | BindingFlags.Instance);

SetHandler setter = GetDelegate(typeof(MyStruct), fi);
setter(myStruct, 111);
Console.WriteLine(myStruct.myField);
Cyrus
  • 2,261
  • 2
  • 22
  • 37
  • 1
    This is why we don't work with mutable structs. You're mutating a copy of the struct. Create a new version of the struct with the fields initialized to what you want them to be. – Servy Sep 02 '14 at 20:00
  • 1
    Related [set a field of struct](http://stackoverflow.com/questions/1272454/generate-dynamic-method-to-set-a-field-of-a-struct-instead-of-using-reflection?rq=1) question *may* be what you are after... Side note: nicely asked question with good sample... but whole thing you are trying to achieve is very confusing and unlikely to work the way you want in most cases... – Alexei Levenkov Sep 02 '14 at 20:16
  • Oops; I realize I'd screwed up my answer; edited - however, it would work a lot better as a `ref MyStruct` – Marc Gravell Sep 02 '14 at 20:40

5 Answers5

14

Edit: I made this mistake again - fun fact; unbox-any returns the value; unbox returns the pointer to the data - which allows in-place mutate.

Here's the working IL generation:

    setGenerator.Emit(OpCodes.Ldarg_0);
    setGenerator.Emit(OpCodes.Unbox, type);
    setGenerator.Emit(OpCodes.Ldarg_1);
    setGenerator.Emit(OpCodes.Unbox_Any, fieldInfo.FieldType);
    setGenerator.Emit(OpCodes.Stfld, fieldInfo);
    setGenerator.Emit(OpCodes.Ret);

But! This is mutating a boxed copy; you would need to unbox afterwards:

    object obj = myStruct;
    setter(obj, 111);
    MyStruct andBackAgain = (MyStruct)obj;
    Console.WriteLine(andBackAgain.myField);
    Console.WriteLine(myStruct.myField);

To do it in place, you would probably need the method to take a ref MyStruct, or to return a MyStruct. You could return the boxed copy, but that doesn't make it much easier to use. Frankly, it is moot: structs should not generally be mutable.

Community
  • 1
  • 1
Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900
  • 2
    @petelids to be fair, I just fixed the OP's IL, but: yes, I do *lots* of IL - very powerful for meta-programming. I use it in protobuf-net, dapper, fast-member, and a few other libraries. – Marc Gravell Sep 02 '14 at 20:53
  • Edited answer works! andBackAgain.myField is 111. But always myStruct.myField is 3. Is there another way to set value for myStruct.myField without using ref MyStruct using reflection with high performance? – Cyrus Sep 02 '14 at 21:15
  • 1
    @cyrus `ref MyStruct` and `TypedReference` are the only things that don't create a copy – Marc Gravell Sep 02 '14 at 21:41
  • @MarcGravell Can you write a solution with `ref` in delegate? I can use delegate without any generic types! `public delegate void SetHandler(ref object source, object value)`, but with this delegate I get an error: `Cannot bind to the target method because its signature or security transparency is not compatible with that of the delegate type` – Cyrus Sep 03 '14 at 07:28
  • 1
    @Cyrus `ref object` won't help you; that is the same problem; it *has* to be `ref MyStruct`, `MyStruct*` or `TypedReference` if you want to avoid the "mutating a clone" thing. But again, most structs should not be mutable *anyway*. – Marc Gravell Sep 03 '14 at 08:11
  • [Same thing but using expression trees](http://stackoverflow.com/a/31898080/855432) – ghord Aug 08 '15 at 20:12
11

I think as Servy points out in the comments it's probably better to not have a mutable struct and instead create a copy of the struct with the new value. You can use the SetValueDirect method on the FieldInfo instance if you really want to use reflection to mutate the struct but it uses the undocumented __makeref method to get a TypedReference.

I really wouldn't recommend using this code; it's purely to show that this is possible:

MyStruct myStruct = new MyStruct();
myStruct.myField = 3;

FieldInfo fi = typeof(MyStruct).GetField("myField", BindingFlags.Public | BindingFlags.Instance);
TypedReference reference = __makeref(myStruct);
fi.SetValueDirect(reference, 111);
Console.WriteLine(myStruct.myField); //prints 111
petelids
  • 12,305
  • 3
  • 47
  • 57
  • What is this I don't even... +1 For the `__makeref` keyword, I cannot believe I never stumbled upon that. You just gave me a bunch of stuff to play with. – vgru Sep 02 '14 at 20:20
  • @Groo There are more undocumented keywords. `__arglist`, `__refvalue`, `__reftype`. Just google :) – Sriram Sakthivel Sep 02 '14 at 20:24
  • Thanks @Groo - I think I saw it on [Eric Lippert's](http://ericlippert.com/) blog somewhere and it must have stuck (Sorry, I can't find the direct link). I've never used it other than to play with though and I don't think I ever will :) – petelids Sep 02 '14 at 20:27
  • @petelids: Yeah, I just found [this pearl](http://stackoverflow.com/q/4764573/69809), looks pretty cool. – vgru Sep 02 '14 at 20:28
  • @Groo - that's a really interesting link. I was supposed to be doing some work tonight but I think I may have just lost an hour or two to that link! – petelids Sep 02 '14 at 20:35
6

The easiest way to use Reflection to set fields or properties of a structure is to box the structure, pass the boxed structure to SetField or SetProperty, and then unbox the structure once all desired manipulations are complete.

public static class refStructTest
{
    struct S1
    {
        public int x;
        public int y { get; set; }
        public override string ToString()
        {
            return String.Format("[{0},{1}]", x, y);
        }
    }
    public static void test()
    {
        var s = default(S1);
        s.x = 2;
        s.y = 3;
        Object obj = s;
        var fld = typeof(S1).GetField("x");
        var prop = typeof(S1).GetProperty("y");
        fld.SetValue(obj,5);
        prop.SetValue(obj,6,null);
        s = (S1)obj;
        Console.WriteLine("Result={0}", s);
    }
}

According to the ECMA documentation, each value type is associated with two kinds of things: a storage location type and a heap object type. The heap object type, like all heap object types, will behave with reference semantics; passing a reference to a heap object to a method like SetValue will thus modify the object to which the reference was passed.

Note for VB users: VB.NET has a really annoying behavior which almost make sense in the Option Strict On dialect, but which exists even in the Option Strict Off dialect: if a variable of compile-time type Object which holds a reference to a boxed structure is assigned to another variable of that same type or passed as a parameter of type Object, VB.NET will store or pass a reference to a copy of the original object. When writing code like the above in VB.NET, one should make obj be of type ValueType rather than Object to prevent such behavior.

supercat
  • 77,689
  • 9
  • 166
  • 211
  • How to update your method, if the input is struct: public static void test(S1 s) ... but not use (ref S1 s) – Cyrus Sep 28 '14 at 06:01
  • Did you mean "how to make a method update an outside struct: `public static void test(ref S1 s)`, but *note* use of `(ref S1 s)`? I've probably mistyped "not" for "note" more times than I can remember, but of course it changes the meaning entirely. – supercat Sep 28 '14 at 15:57
3

Here the code with ref:

public delegate void SetHandler<T>(ref T source, object value) where T : struct;

        private static SetHandler<T> GetDelegate<T>(FieldInfo fieldInfo) where T : struct
        {
            var type = typeof(T);
            DynamicMethod dm = new DynamicMethod("setter", typeof(void), new Type[] { type.MakeByRefType(), typeof(object) }, type, true);
            ILGenerator setGenerator = dm.GetILGenerator();

            setGenerator.Emit(OpCodes.Ldarg_0);
            setGenerator.DeclareLocal(type);
            setGenerator.Emit(OpCodes.Ldarg_0);
            setGenerator.Emit(OpCodes.Ldnull);
            setGenerator.Emit(OpCodes.Stind_Ref);
            setGenerator.Emit(OpCodes.Ldarg_1);
            setGenerator.Emit(OpCodes.Unbox_Any, fieldInfo.FieldType);
            setGenerator.Emit(OpCodes.Stfld, fieldInfo);
            setGenerator.Emit(OpCodes.Ldloc, 0);
            setGenerator.Emit(OpCodes.Box, type);
            setGenerator.Emit(OpCodes.Ret);
                return (SetHandler<T>)dm.CreateDelegate(typeof(SetHandler<>).MakeGenericType(type));
        }

        static void Main(string[] args)
        {
            MyStruct myStruct = new MyStruct();
            myStruct.myField = 3;

            FieldInfo fi = typeof(MyStruct).GetField("myField", BindingFlags.Public | BindingFlags.Instance);

            var setter = GetDelegate<MyStruct>(fi);
            setter(ref myStruct, 111);
            Console.WriteLine(myStruct.myField);
        }
brz
  • 5,926
  • 1
  • 18
  • 18
  • Works! Nice solution with ref parameter. – Cyrus Sep 03 '14 at 06:57
  • Can you write a solution without the generic type in delegate? I can use delegate `public delegate void SetHandler(ref object source, object value)` – Cyrus Sep 03 '14 at 07:25
0

To add to other answers, you can actually box inside the delegate, as long as your method also returns the modified struct.

Since my IL-fu is not that great, this is how you would do it with plain reflection:

// the good side is that you can use generic delegates for added type safety
public delegate T SetHandler<T>(T source, object value);

private static SetHandler<T> GetDelegate<T>(FieldInfo fieldInfo)
{
    return (s, val) => 
    { 
        object obj = s; // we have to box before calling SetValue
        fieldInfo.SetValue(obj, val);
        return (T)obj; 
    };
}

This means you will need to fetch the return value like this:

SetHandler<MyStruct> setter = GetDelegate<MyStruct>(fi);
myStruct = setter(myStruct, 111);
Console.WriteLine(myStruct.myField);

But there's no need to box it before calling the setter.

Alternatively, you can pass the struct using the ref keyword, which would result in:

public delegate void SetHandler<T>(ref T source, object value);

private static SetHandler<T> GetDelegate<T>(FieldInfo fieldInfo)
{
    return (ref T s, object val) => 
    { 
        object obj = s;
        fieldInfo.SetValue(obj, val);
        s = (T)obj; 
    };
}

SetHandler<MyStruct> setter = GetDelegate<MyStruct>(fi);
setter(ref myStruct, 111); // no need to return anymore
Console.WriteLine(myStruct.myField);
vgru
  • 49,838
  • 16
  • 120
  • 201
  • I have many structs, and one method void SetStructValue(object someStruct, object value). How can I call your method GetDelegate in my method SetStructValue. I cannot call GetDelegate(fi) because type of someStruct is not the same as typeof(MyStruct). Typeof someStruct can be everythig - any struct! – Cyrus Sep 28 '14 at 06:08
  • If your struct is already boxed to an `object`, then there is no point in using generics and you can use the examples shown in other answers. But if you have a value type variable before calling your `SetStructValue`, then you should consider making that method generic also. – vgru Sep 28 '14 at 07:46