Присвойте дополнительные атрибуты таким типам, как int, float для целей отражения - PullRequest
0 голосов
/ 16 октября 2018

Я пытаюсь автоматизировать отображение (сбор с помощью отражения) моих переменных, которые находятся в определенных скриптах в Unity.Проблема заключается в назначении пользовательских значений (например: «строка DisplayName», «bool DisplayMe», «bool WriteMe» и т. Д.).Когда дело доходит до моих пользовательских классов, я понимаю, как я это сделаю, но я бы хотел для этого не переделывать типы, такие как float, string, int и т. Д.

Например, скажем, у меня есть:

public class myBaseClass
{
    public string Name = "Display Name";
    public bool AmReadable = true;
    public bool AmWritable = true;
}

Тогда:

public class myDoubleFloat: myBaseClass
{
    public float ValueFirst;
    public float ValueSecond;
}

Поэтому в некоторых скриптах в Unity я определяю это:

public class SomeScriptOnGameObject : MonoBehaviour
{
    public myDoubleFloat myFirstVariable{get; set;}
    public float mySecondVariable{get; set;}
}

Так что позже с помощью отражения я могу проверить, является ли "myFirstVariable"должны быть прочитаны, это отображаемое имя и т. д. - хотя для "mySecondVariable" я не могу выполнить эту проверку.Как мне это сделать, не изобретая велосипед и не создав класс для каждого из этих типов, таких как float, string, int, List и т. Д .?

Ответы [ 2 ]

0 голосов
/ 17 октября 2018

Обтекание объектов-значений (int, float и т. Д.), Вероятно, не лучший подход.Помимо дополнительной сложности (и возможности ошибок), теперь вы увеличиваете объем памяти вашей игры.

(я намеренно избегаю использования нового синтаксиса C # в этих примерах)

Поскольку выуже в контексте отражения, вместо того, чтобы обернуть ваши объекты значения, я бы предложил подход на основе атрибутов.Например:

public class SomeScriptOnGameObject
{
    [DisplayName("First Variable"), Writable]
    public float FirstVariable { get; set; }

    [DisplayName("Second Variable")]
    public float SecondVariable { get; set; }

    [DisplayName("Some Field")]
    public float Field;

    public float FieldWithNoAttributes;
}

Преимущество заключается в сохранении метаданных полей в метаданных, а не в переносе копии всего содержимого в каждом создаваемом вами экземпляре.

Фактические атрибутытакже легко создавать.Я начну с самого простого: WritableAttribute:

[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
public sealed class WritableAttribute : Attribute
{
}

Этот пустой класс - это все, что нужно для пометки поля или свойства как «Доступные для записи».AttributeUsage помечает это как допустимое только для полей и свойств (не, например, для класса).

Другой атрибут, DisplayName, только немного сложнее:

[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
public sealed class DisplayNameAttribute : Attribute
{
    public string DisplayName { get; private set; }

    public DisplayNameAttribute(string displayName)
    {
        DisplayName = displayName;
    }
}

Основным отличием является конструктор с аргументом displayName и свойством DisplayName.Это заставляет компилятор ожидать аргумент для атрибута.

С некоторыми методами расширения вы можете сделать вещи очень чистыми:

public static class AttributeExtensions
{
    public static bool IsWritable(this MemberInfo memberInfo)
    {
        return memberInfo.GetCustomAttributes(typeof(WritableAttribute)).Any();
    }

    public static string DisplayName(this MemberInfo memberInfo)
    {
        var displayNameAttribute =
            memberInfo.GetCustomAttributes(typeof(DisplayNameAttribute))
                .FirstOrDefault() as DisplayNameAttribute;
        return displayNameAttribute == null ? null : displayNameAttribute.DisplayName;
    }

    public static PropertyInfo Property<T>(this T _, string propertyName)
    {
        return typeof(T).GetProperty(propertyName);
    }

    public static FieldInfo Field<T>(this T _, string fieldName)
    {
        return typeof(T).GetField(fieldName);
    }
}

(Поскольку вы упомянули, что уже используете отражение, вытам могут не понадобиться два последних метода.)

Наконец, простой тест XUnit для демонстрации:

public class UnitTest1
{
    [Fact]
    public void Test1()
    {
        var obj = new SomeScriptOnGameObject();

        Assert.True(obj.Property("FirstVariable").IsWritable());
        Assert.False(obj.Property("SecondVariable").IsWritable());
        Assert.False(obj.Field("Field").IsWritable());

        Assert.Equal("First Variable", obj.Property("FirstVariable").DisplayName());
        Assert.Equal("Second Variable", obj.Property("SecondVariable").DisplayName());
        Assert.Equal("Some Field", obj.Field("Field").DisplayName());

        Assert.Null(obj.Field("FieldWithNoAttributes").DisplayName());
    }
}
0 голосов
/ 16 октября 2018

Вы можете определить универсальную оболочку:

public class MyProperty<T>
{
    private T _value;

    public T Get() => _value;

    public T Set(T newValue) => _value = newValue;

    public string Name { get; set; }

    public bool AmReadable { get; set; }

    public bool AmWritable { get; set; }
}

И сделать методы получения и установки ваших свойств для сопоставления с некоторыми вспомогательными полями типа MyProperty<T>:

public class SomeScriptOnGameObject : MonoBehaviour
{
    private MyProperty<MyDoubleFloat> _myFirstVariable;

    private MyProperty<float> _mySecondVariable;

    public MyDoubleFloat MyFirstVariable
    {
        get => _myFirstVariable.Get();
        set => _myFirstVariable.Set(value);
    }

    public float MySecondVariable
    {
        get => _mySecondVariable.Get();
        set => _mySecondVariable.Set(value);
    }

    public SomeScriptOnGameObject()
    {
        _myFirstVariable = new MyProperty<MyDoubleFloat>
        {
            //configuration
        };

        _mySecondVariable = new MyProperty<float>
        {
            //configuration
        };
    }
}

Если вы хотитедля фантазии вы можете даже добавить неявный оператор, чтобы избавиться от Get() и сделать любое T назначаемым из MyProperty<T>:

    public class MyProperty<T>
    {
        private T _value;

        public T Set(T newValue) => _value = newValue;

        public string Name { get; set; }

        public bool AmReadable { get; set; }

        public bool AmWritable { get; set; }

        public static implicit operator T(MyProperty<T> myProperty) => 
            myProperty != null ? myProperty._value : default;
    }

И:

  public MyDoubleFloat MyFirstVariable
    {
        get => _myFirstVariable;
        set => _myFirstVariable.Set(value);
    }
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...