将其他属性分配给int,float等类型以进行反射

时间:2018-10-16 20:04:24

标签: c# unity3d reflection

我试图自动显示变量(通过反射收集),这些变量位于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 :(得分:4)

您可以定义通用包装器:

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; }
}

并使属性的getter和setter映射到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);
    }

答案 1 :(得分:2)

包装值对象(intfloat等)可能不是最佳方法。除了额外的复杂性(以及可能的错误)之外,您现在还在消耗游戏的内存。

(在这些示例中,我有意避免使用更新的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());
    }
}