如何使用反射获取抽象"const"属性的值?



我有一个这样定义的类:

public abstract class Uniform<T>
{
    public abstract string GlslType { get; }
    ...
}

然后定义了一个子类:

public class UniformInt : Uniform<int>
{
    public override string GlslType
    {
        get { return "int"; }
    }
}

然后是其他地方的一种方法,看起来像这样:

    public static string GetCode<T>()
    {
        var sb = new StringBuilder();
        var type = typeof(T);
        sb.AppendFormat("struct {0} {{n", type.Name);
        var fields = type.GetFields(BindingFlags.Public | BindingFlags.Instance);
        foreach(var f in fields)
        {
            sb.AppendFormat("    {0} {1};n", f.FieldType.GetProperty("GlslType").GetValue(???), f.Name);
        }
        ...
    }

我在填写??? s时遇到了问题。我相信GetValue需要一个对象的实例,但我并不真正关心它是什么实例,因为它们都返回相同的值。AFAIK中没有public abstract static readonly值,所以我必须使用属性。

那么,我可以用什么来代替那些???来返回"int"(假设其中一个字段是UniformInt)。

顺便提一下:如何将fields限制为仅继承Uniform<>的字段类型?

您需要一个UniformInt的实例才能获得非静态属性的值:

UniformInt someUniformInt = ...
f.FieldType.GetProperty("GlslType").GetValue(someUniformInt, null)

顺便提一下:如何将字段限制为仅继承Uniform的字段类型?

bool isDerivesFromUniformOfInt = typeof(Uniform<int>)
    .IsAssignableFrom(f.FieldType);

或者如果你事先不知道T的类型:

bool isDerivesFromUniformOfT = typeof(Uniform<>)
    .MakeGenericType(typeof(T))
    .IsAssignableFrom(f.FieldType);

问题是,由于属性不是静态的,编译器不知道它们都返回相同的值。由于您的UniformInt不是密封的,所以另一个用户可以从中继承并重写GlslType以返回其他内容。然后UniformInt和所有派生类都可以用于GetCode<T>()方法。

静态方法确实是最好的选择。为了确保在所有类上实现它们(因为静态方法不能是抽象的,所以不能强制执行),我会编写一个简单的单元测试,使用反射加载从Uniform<T>继承的所有类,并检查它们是否定义了静态属性。

更新

在思考Attributes如何提供帮助时,经过一些实验,我得出了以下结论。它肯定不会赢得选美比赛,但作为一种学习练习,它很有帮助;)

using System;
using System.Linq;
namespace StackOverflow
{
    internal class StackOverflowTest
    {
        private static void Main()
        {
            string sInt = UniformInt.GlslType;
            string sDouble = UniformDouble.GlslType;
        }
    }
    public abstract class Uniform<B, T> // Curiously recurring template pattern 
        where B : Uniform<B, T>
    {
        public static string GlslType
        {
            get
            {
                var attribute = typeof(B).GetCustomAttributes(typeof(GlslTypeAttribute), true);
                if (!attribute.Any())
                {
                    throw new InvalidOperationException(
                        "The GslType cannot be determined. Make sure the GslTypeAttribute is added to all derived classes.");
                }
                return ((GlslTypeAttribute)attribute[0]).GlslType;
            }
        }
    }
    [AttributeUsage(AttributeTargets.Class, Inherited = true, AllowMultiple = false)]
    internal sealed class GlslTypeAttribute : Attribute
    {
        public string GlslType { get; private set; }
        public GlslTypeAttribute(string glslType)
        {
            GlslType = glslType;
        }
    }
    [GlslType("int")]
    public class UniformInt : Uniform<UniformInt, int> // Curiously recurring template pattern 
    {
    }
    [GlslType("double")]
    public class UniformDouble : Uniform<UniformDouble, double> // Curiously recurring template pattern 
    {
    }
}

GlslType不是静态的,因此在访问其值之前需要一个对象引用。抽象类中静态属性的主题已经被广泛地涵盖,即:

  • C#,实现';静态摘要';类似的方法
  • 可以';t定义静态抽象字符串属性

解决方案1

将静态方法添加到所有返回GlslType的派生类中。任何都不需要添加到基类中。可以使用单元测试+反射来检查是否缺少实现。Wouter de Kort建议。

解决方案2

更改Uniform<T>使GlslType变为静态:

public abstract class Uniform<T>
{
    public static string GlslType { get { throw new NotImplementedException("Please override with "new" in derived class."); } }
    ...
}

UniformInt更改为"覆盖"GlslType,保持静态修饰符:

public class UniformInt : Uniform<int>
{
    public new static string GlslType
    {
        get { return "int"; }
    }
}

null, null:填充???

sb.AppendFormat("    {0} {1};n", f.FieldType.GetProperty("GlslType").GetValue(null,null), f.Name);

解决方案3

请改用属性。类似于:

[GlslType("int")]
public class UniformInt : Uniform<int>
{
}

结论

这三种解决方案都非常相似,并且似乎有相同的缺点(不能强制派生类来实现它)。通过方法1或2抛出异常将有助于快速查找错误,或者使用3,我可以通过修改fields条件跳过没有该属性的类。

相关内容

  • 没有找到相关文章

最新更新