2014-09-02 1 views
5

Как установить значение в поле struct - myStruct.myField с отражением с помощью DynamicMethod? Когда я вызываю setter(myStruct, 111) значение не было установлено, потому что MyStruct - тип значения. Console.WriteLine(myStruct.myField) показывает значение 3.
Как изменить метод GetDelegate для установки значения в myStruct.myField?C# Reflection - Как установить значение поля для struct

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); 
+1

Вот почему мы не работаем с изменяемыми структурами. Вы мутируете копию структуры. Создайте новую версию структуры с полями, инициализированными тем, кем вы хотите. – Servy

+1

[установить поле структуры] (http://stackoverflow.com/questions/1272454/generate-dynamic-method-to-set-a-field-of-a-struct-instead-of-using-reflection? rq = 1) вопрос * может * быть тем, что вы после ... Боковое примечание: приятно заданный вопрос с хорошим образцом ... но целая вещь, которую вы пытаетесь достичь, очень запутанна и вряд ли будет работать так, как вы хотите в большинстве случаев ... –

+0

Oops; Я понимаю, что я набрал свой ответ; отредактированный - однако, он работал бы намного лучше, как «ref MyStruct» –

ответ

10

Редактировать: I made this mistake again - fun fact; 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 или возвращенияMyStruct. You может вернуть копию в коробке, но это не делает ее намного проще в использовании. Откровенно говоря, это спорный вопрос: не должна Структура вообще быть изменяемой.

+0

+1 для использования ИЛ - это в моем «узнать» список ... – petelids

+0

@petelids, чтобы быть справедливым, я просто исправил IL OP, но: да, я делаю * lots * из IL - очень мощный для метапрограмм. Я использую его в protobuf-net, dapper, fast-member и нескольких других библиотеках. –

+0

Отредактированный ответ работает! andBackAgain.myField - 111. Но всегда myStruct.myField - 3. Есть ли другой способ установить значение для myStruct.myField, не используя ref MyStruct, используя отражение с высокой производительностью? – Cyrus

6

Я думаю, что в комментариях Servy указывает, что, вероятно, лучше не иметь изменяемую структуру и вместо этого создать копию структуры с новым значением. Вы можете использовать метод для экземпляра 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 
+0

Что это я даже не ... +1 Для ключевого слова '__makeref' я не могу поверить, что никогда наткнулся на это. Ты просто дал мне кучу вещей, чтобы поиграть. – Groo

+0

@Groo Есть больше недокументированных ключевых слов. '__arglist',' __refvalue', '__reftype'. Просто google :) –

+0

Спасибо @Groo - Мне кажется, я видел его на блоге [Eric Lippert's] (http://ericlippert.com/) где-то, и он, должно быть, застрял (извините, я не могу найти прямую ссылку). Я никогда не использовал его, кроме как играть, хотя и я не думаю, что когда-либо буду :) – petelids

0

Чтобы добавить другие ответы, вы можете на самом деле поле внутри делегата, если ваш метод также возвращает модифицированную-структуру.

С моей IL-фу не так уж велика, это то, как вы могли бы сделать это с простым отражением:

// 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); 
+0

У меня есть много структур и один метод void SetStructValue (объект someStruct, значение объекта). Как я могу вызвать ваш метод GetDelegate в моем методе SetStructValue. Я не могу назвать GetDelegate (fi), потому что тип someStruct не совпадает с типомof (MyStruct). Typeof someStruct может быть любой - любой структурой! – Cyrus

+0

Если ваша структура уже помещена в поле «объект», тогда нет смысла использовать дженерики, и вы можете использовать примеры, показанные в других ответах. Но если у вас есть переменная типа значения перед вызовом 'SetStructValue', то вам следует также рассмотреть возможность создания этого метода. – Groo

1

Вот код с рефами:

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); 
     } 
+0

Работает! Хорошее решение с параметром ref. – Cyrus

+0

Можете ли вы написать решение без общего типа в делегате? Я могу использовать делегат 'public delegate void SetHandler (ref object source, object value)' – Cyrus

0

Самым простым способом использования отражения для установки полей или свойства структуры, чтобы вставить структуру, передать коробчатую структуру в 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, который содержит ссылку к boxed structure присваивается другой переменной того же типа или передается как параметр типа Object, VB.NET будет хранить или передавать ссылку на копию исходного объекта. При написании кода, подобного приведенному выше, в VB.NET, необходимо сделать obj типа ValueType, а не Object, чтобы предотвратить такое поведение.

+0

Как обновить ваш метод, если вход является struct: public static void test (S1 s) ... но не используется (ref S1 s) – Cyrus

+0

Вы имели в виду «как сделать метод обновления внешней структуры:' public static void test (ref S1 s) ', но * note * использование' (ref S1 s) '? Я, вероятно, ошибся« не »для «обратите внимание» больше времени, чем я помню, но, конечно, он полностью меняет смысл. – supercat