Как использовать Reflection для правильной установки типов значений в цепочке вызовов - PullRequest
0 голосов
/ 21 марта 2020

Итак, я пишу парсер, который использует документ xml для создания группы объектов и заполнения их свойств. Я хочу, чтобы пользователь мог с помощью атрибутов поместить свойство, например position.x = "5", а позиция X будет установлена ​​в 5.

Моя проблема в том, что поля типа значения могут быть легко изменены с помощью кода ниже как я могу изменить исходное значение, но свойства типа значения возвращают копию, которая должна быть изменена и переназначена обратно на исходный объект. Любые идеи о том, как обрабатывать свойства типа значения?

Пример: position - это структура типа Vector2, которая является свойством. MyObject.position.x = 5 не будет работать, потому что вызов SetValue для позиции создаст временную позицию и установит для x значение 5. Поэтому фактическая позиция, прикрепленная к объекту, не получит значение 5. Если я сделаю MyObject.position = new Vector2 (5,0), я получу позицию, установленную на 5,0, так как я не пытаюсь изменить позицию элементы и вместо этого присваивать целое значение позиции.

Примечание: я написал несколько методов расширения, чтобы 'GetValue' был действительным для информации об элементе.

private bool SetAttributeValue(string text,object obj,MemberInfo setter)
{
    BuildParseMethods();
    if (!TryParseAttribute(text, setter.GetMemberType(), out object value))
        return false;

    setter.SetValue(obj, value);

    return true;
}


private void SetChainAttribute(XmlAttribute attribute,object instance)
{
    object target = instance;
    MemberInfo[] bindings = null;
    string[] parts = attribute.Name.Split('.');

    Type type = instance.GetType();
    for (int i = 0; i < parts.Length; i++)
    {
        bindings = type.GetMember(parts[i], BindingFlags.Public | BindingFlags.Instance | BindingFlags.SetField | BindingFlags.SetProperty | BindingFlags.IgnoreCase);

        if (bindings == null || bindings.Length == 0)
        {
            Debug.LogError($"Unable to find element {i} of {attribute.Name}");
            return;
        }

        if (i < parts.Length - 1)
        {
            target = bindings[0].GetValue(target);
            type = target != null ? target.GetType() : bindings[0].GetType();
        }
    }

    SetAttributeValue(attribute.Value, target, bindings[0]);
}

1 Ответ

0 голосов
/ 21 марта 2020

Структуры в C# должны быть неизменными, поэтому при передаче структур в качестве параметров, возвращаемых из свойства / метода, компилятор создает копии структур для предотвращения его изменения.

Когда вы возможно, вы поняли, что вы можете установить свойство Position для переопределения всего Vector2, чтобы вы могли следовать по этому пути в вашем коде отражения:

var myObj = new MyObject();
myObj.Position = new Vector2();
// This produces error CS1612 Cannot modify the return value of 'MyObject.Position' because it is not a variable
myObj.Position.X = 5; 

// Workaround:
// Use reflection to set Position instead of Position.X
var prop = myObj.GetType().GetProperty("Position");
prop.SetValue(myObj, Activator.CreateInstance(prop.PropertyType, 5));

Activator.CreateInstance находит правильный конструктор Vector2 для вызова на основе предоставленных аргументов.

Другой альтернативный способ - использовать структурированную структуру в вашем MyObject: https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/types/boxing-and-unboxing

interface IVector2
{
    int X { get; set; }
}

struct Vector2 : IVector2
{
}

class MyObject
{
    public IVector2 BoxedPosition { get; set; }
}

Структура в штучной упаковке может «пережить» проблему с копированием, но может произойти компромисс между производительностью и у вас должен быть доступ к исходному коду Vector2.

...