C# 为int、float等类型指定其他属性以进行反射

C# 为int、float等类型指定其他属性以进行反射,c#,unity3d,reflection,C#,Unity3d,Reflection,我试图自动显示(通过反射收集)位于Unity中特定脚本中的变量。问题在于分配自定义值(例如:“字符串显示名”、“bool显示名”、“bool写入名”等)。当涉及到我的自定义类时,我理解我将如何做,但我希望避免为此而重新创建float、string、int等类型 例如,假设我有: public class myBaseClass { public string Name = "Display Name"; public bool AmReadable = true; pub

我试图自动显示(通过反射收集)位于Unity中特定脚本中的变量。问题在于分配自定义值(例如:“字符串显示名”、“bool显示名”、“bool写入名”等)。当涉及到我的自定义类时,我理解我将如何做,但我希望避免为此而重新创建float、string、int等类型

例如,假设我有:

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

因此,在Unity的一些脚本中,我定义了它:

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

因此,稍后通过反射,我可以检查是否应该读取“myFirstVariable”,它的显示名称等等,而对于“mySecondVariable”,我无法执行此检查。如何在不重新设计轮子和为每种类型(如float、string、int、List等)创建类的情况下实现这一点?

您可以定义一个通用包装器:

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; }
}
以及:

包装值对象(
int
float
等)可能不是最好的方法。除了额外的复杂性(以及bug的可能性),您现在正在膨胀游戏的内存

(在这些示例中,我有意避免使用较新的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;
}
这样做的好处是将字段的元数据保留在元数据中,而不是在创建的每个实例中都携带所有内容的副本

实际属性也很容易创建。我将从最简单的一个开始,
writeableAttribute

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

还不能用包装纸,所以对我来说就像伏都教一样。我会马上测试它,但如果它像我想象的那样工作,它将完全是我所需要的。不过还有一个问题:假设我通过GetValue获得这个反射的值,并将其命名为myTestDoubleFloat。当我执行myTestDoubleFloat.ValueFirst时,它会按其应有的方式返回-如何从中获取“AmReadable”、“AmWritable”、“Name”?因为它返回“类型MyDoubleFloat不包含对…”的定义”,所以必须检查类型为
MyProperty
的支持字段。要么这样做,要么使您的主属性myTestDoubleFloat的类型为
MyProperty
,因为它是保存所有元数据的包装器。请告诉我如何从属性中获取第一个描述的变量?简而言之,我是这样做的:System.Reflection.PropertyInfo[]properties=this.GetType().GetProperties();(“这个”是附加到包含我正在搜索的变量的游戏对象上的脚本)。然后我为(在“properties”数组上)做了些什么——假设properties[0]是myFirstVariable(或者testMyFirstVariable是properties[0]的GetValue)——我如何从这两种形式中的任何一种形式得到变量的MyProperty的“Name”或“AmReadable”(等等)?这东西太神奇了,这是我一生中的哪里?非常感谢。
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;
}
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
public sealed class WritableAttribute : Attribute
{
}
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
public sealed class DisplayNameAttribute : Attribute
{
    public string DisplayName { get; private set; }

    public DisplayNameAttribute(string 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);
    }
}
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());
    }
}