C# Reflection - How to set field value for struct
Asked Answered
O

5

14

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);
Ordinarily answered 2/9, 2014 at 19:55 Comment(3)
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.Bengurion
Related set a field of struct 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...Barbecue
Oops; I realize I'd screwed up my answer; edited - however, it would work a lot better as a ref MyStructScullion
S
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.

Scullion answered 2/9, 2014 at 20:16 Comment(6)
@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.Scullion
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?Ordinarily
@cyrus ref MyStruct and TypedReference are the only things that don't create a copyScullion
@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 typeOrdinarily
@Ordinarily 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.Scullion
Same thing but using expression treesTrefler
K
14

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
Karachi answered 2/9, 2014 at 20:17 Comment(5)
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.Jillane
@Groo There are more undocumented keywords. __arglist, __refvalue, __reftype. Just google :)Coroneted
Thanks @Groo - I think I saw it on Eric Lippert's 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 :)Karachi
@petelids: Yeah, I just found this pearl, looks pretty cool.Jillane
@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!Karachi
I
8

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.

Indihar answered 3/9, 2014 at 17:57 Comment(2)
How to update your method, if the input is struct: public static void test(S1 s) ... but not use (ref S1 s)Ordinarily
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.Indihar
L
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);
        }
Locally answered 2/9, 2014 at 20:49 Comment(2)
Works! Nice solution with ref parameter.Ordinarily
Can you write a solution without the generic type in delegate? I can use delegate public delegate void SetHandler(ref object source, object value)Ordinarily
J
1

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);
Jillane answered 2/9, 2014 at 20:25 Comment(2)
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<MyStruct>(fi) because type of someStruct is not the same as typeof(MyStruct). Typeof someStruct can be everythig - any struct!Ordinarily
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.Jillane

© 2022 - 2024 — McMap. All rights reserved.