C# Reflection - как установить значение поля для struct
как я могу установить значение в поле структуры - myStruct.myField с отражением с помощью DynamicMethod? Когда я позову setter(myStruct, 111) значение не было установлено, поскольку MyStruct - Это тип значения. Console.WriteLine(myStruct.myField) показывает значение 3.
изменение GetDelegate метод для установки значения в 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);
5 ответов
Edit:я снова совершил эту ошибку - забавный факт; unbox-any возвращает стоимостью; unbox возвращает указатель на данные - который позволяет на месте мутировать.
вот рабочее поколение IL:
    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);
но! Это мутация коробочной копии; после этого вам нужно будет распаковать:
    object obj = myStruct;
    setter(obj, 111);
    MyStruct andBackAgain = (MyStruct)obj;
    Console.WriteLine(andBackAgain.myField);
    Console.WriteLine(myStruct.myField);
чтобы сделать это на месте, вам, вероятно, понадобится метод, чтобы взять ref MyStruct или возвращение a MyStruct. Вы  мог бы верните коробочную копию, но это не делает ее намного проще в использовании. Честно говоря, это спорно: структуры обычно не должны быть изменяемыми.
Я думаю, как указывает Servy в комментариях, вероятно, лучше не иметь изменяемой структуры и вместо этого создать копию структуры с новым значением. Вы можете использовать SetValueDirect метод FieldInfo Если вы действительно хотите использовать отражение, чтобы мутировать структуру, но она использует недокументированный __makeref метод получить TypedReference.
я бы не рекомендовал использовать этот код; это просто показать, что это возможно:
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
здесь код с 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);
        }
самый простой способ использовать отражение для установки полей или свойств структуры-поместить структуру в коробку, передать структуру вSetField или SetProperty, а затем распакуйте структуру после завершения всех желаемых манипуляций.
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);
    }
}
согласно документации ECMA, каждый тип значения связан с двумя типами вещей: типом расположения хранилища и типом объекта кучи.  Тип объекта кучи, как и все типы объектов кучи, будет вести себя со ссылочной семантикой; передача ссылки на объект кучи в такой метод, как SetValue таким образом изменит объект, на который была передана ссылка.
Примечание для пользователей VB: VB.NET имеет действительно раздражающее поведение, которое почти имеет смысл в Option Strict On диалект, но который существует даже в Option Strict Off диалект: если переменная типа времени компиляции Object который содержит ссылку на коробочную структуру, присваивается другой переменной того же типа или передается в качестве параметра типа Object, VB.NET будет хранить или передать ссылку на копию исходного объекта.  При написании кода, как выше в VB.NET надо сделать obj типа ValueType, а не Object для предупреждения такого поведения.
чтобы добавить к другим ответам, вы можете фактически поместить внутри делегата, если ваш метод также возвращает измененную структуру.
так как мой Ил-фу не так велик, вот как вы сделали бы это с простым отражением:
// 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; 
    };
}
это означает, что вам нужно будет получить возвращаемое значение следующим образом:
SetHandler<MyStruct> setter = GetDelegate<MyStruct>(fi);
myStruct = setter(myStruct, 111);
Console.WriteLine(myStruct.myField);
но нет необходимости вставлять его перед вызовом setter.
кроме того, вы можете передать структуру, используя ref ключевое слово, в результате чего:
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);
