2015-05-18 5 views
1

Я хочу установить значения, используя отражение, но делая функцию настройки доступной для нескольких подобъектов. У меня нет проблем, когда подобъекты являются классами, но с структурами он не работает.C# Итеративное отражение

В примере, у меня есть следующий класс и структура

class MyConfig 
{ 
    Gravity gravity; 
} 
struct Gravity 
{ 
    Vector2 direction; 
} 
struct Vector2 
{ 
    float X,Y; 
} 

Я хотел бы быть в состоянии установить значение, как это:

MyConfig cfg=new MyConfig(); 
setValueViaReflection (cfg,"gravity.direction.X",76.5f); 

, которые, очевидно, должны установить cfg.gravity.direction .X к 76.5f

Мой код прямо сейчас это:

void setValueViaReflection (object obj,string fieldName,object value) 
{ 
int i; 
TypeInfo baseType=null; 
FieldInfo field; 

    string []split=fieldName.Split ('.'); 

    // get one subobject in each iteration 
    for (i=0;i<split.Count()-1;i++) 
    { 
     string fname=split[i]; 
     baseType=obj.GetType().GetTypeInfo(); 

     field=baseType.GetDeclaredField (fname); 
     if (field==null) return; 

     obj=field.GetValue (obj); 
     if (obj==null) return; 

    } 

    // finally you've got the final type, set value 
    baseType=obj.GetType().GetTypeInfo(); 

    field=baseType.GetDeclaredField (split[split.Count()-1]); 
    if (field==null) return; 

    field.SetValue (obj,value); 
} 

Я знаю, что я должен использовать SetValueDirect, но использование его не имеет значения (значение изменено в «obj», но похоже, это тип значения, поэтому он не меняет исходный объект.

Я думаю, проблема в поле. GetValue, который создает тип значения, делая окончательный SetValueDirect бесполезным.

Код работает хорошо, если Gravity и Vector2 заданы как классы.

+0

Я считаю, что вам придется явно указывать структурные переменные. [Соответствующий ответ] (http://stackoverflow.com/a/13308275/147613) – Amit

+0

Вы должны установить значение, возвращаемое из GetValue в родительском (если это тип значения). – Brannon

+0

Это одна из многих причин того, почему вам следует избегать типа изменяемого значения. – Servy

ответ

1

Ваш анализ в корне правильно. То есть проблема связана с использованием типов значений. Основная проблема заключается в том, что при изменении экземпляра типа значения это не влияет на исходную копию этого экземпляра. Он изменяет только текущую копию, которую вы извлекли из исходного хранилища (в данном случае это поле). Чтобы изменения повлияли на исходное хранилище, вам придется изменить текущую копию, а затем сохранить эту копию обратно в это хранилище.

Конечно, при перемещении пути значений поля это означает, что на каждом шаге вам необходимо изменить текущее значение и сохранить его обратно, по крайней мере, для типов значений. Для полей ссылочного типа это технически не обязательно — изменение поля в этом значении хранения делает обновляет исходное хранилище —, но нет никакого вреда в сохранении старого значения (то есть ссылки на объект) обратно на его хранение.

Для меня эта проблема кажется намного легче думать о рекурсивно. То есть вы уже знаете, что легко решить базовый случай, так как структура обеспечивает этот механизм для вас непосредственно с помощью методов GetValue() и SetValue().

Итак, если вы можете как-то уменьшить проблему поэтапно в этом базовом корпусе, вы решили основную проблему. Обратите внимание, что ключевым аспектом этой редукции является то, что, решив базовый случай, вам необходимо распространить полученные результаты на цепочку полей; это подразумевает промежуточные результаты, поэтому здесь подразумевается не только рекурсия, она не будет конвертируема в простое итерационное решение (т. е. это не «рекурсия хвоста»).

Другими словами, не только рекурсия - это более простой способ приблизиться к проблеме, итеративное решение по-прежнему потребует некоторых аспектов рекурсии (т. Е. Структуры данных, которая ведет себя как стек). Таким образом, вы можете использовать рекурсию, поскольку она более компактна и удобна в написании (и IMHO - это более простой способ подумать о проблеме).

Вот рекурсивный метод, который делает то, что вы хотите:

static void SetValueByPath(object target, string path, object value) 
{ 
    int dotIndex = path.IndexOf('.'); 
    string targetProperty = dotIndex > 0 ? 
     targetProperty = path.Substring(0, dotIndex) : path; 
    FieldInfo fieldInfo = target.GetType().GetTypeInfo().GetDeclaredField(targetProperty); 

    if (dotIndex > 0) 
    { 
     object currentValue = fieldInfo.GetValue(target); 

     SetValueByPath(currentValue, path.Substring(dotIndex + 1), value); 

     value = currentValue; 
    } 

    fieldInfo.SetValue(target, value); 
} 

Обратите внимание, что в то время как бокс происходит с полями типа значение, среда делает правильную вещь с ними. Вы можете изменить поле в типе с коротким значением и не создавать новую копию типа значения; поле обновляется в исходном ссылочном значении (то есть currentValue в вышеуказанном коде).

Обратите внимание, что приведенный выше код работает нормально, даже если в миксе имеются поля ссылочного типа. Например. если тип Gravity был class. Если бы вы действительно позаботились, было бы возможно изменить приведенный выше код, чтобы пропустить копирование значения промежуточного ссылочного типа обратно в его поле, так как в этом случае значение поля не изменилось бы, но это просто усложняет код для реального выгода.

+0

Вчера я попробовал рекурсивное решение перед сном и работал, но ваше решение компактно, чище, и объяснение - это высшая отметка за классную работу :) (я все еще новичок в размышлениях) Большое спасибо! – KakCAT

Смежные вопросы