【问题标题】:How to get value of abstract "const" property using reflection?如何使用反射获取抽象“const”属性的值?
【发布时间】:2012-02-06 15:56:26
【问题描述】:

我有一个这样定义的类:

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 值这样的东西,所以我必须使用属性。

那么我可以用什么来代替那些???s 来取回“int”(假设其中一个字段是UniformInt)。

顺便说一句:如何将fields 限制为仅继承Uniform&lt;&gt; 的字段类型?

【问题讨论】:

    标签: c# reflection introspection


    【解决方案1】:

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

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

    更新

    在考虑属性如何提供帮助时,经过一些试验,我想到了以下内容。它绝对不会赢得选美比赛,但作为一种学习练习,它很有帮助;)

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

    【讨论】:

    • 哇——public class UniformInt : Uniform&lt;UniformInt, int&gt;——这不是创建循环引用吗?
    • @Mark Nope :) 它甚至是一种模式:奇怪的重复模板模式 (en.wikipedia.org/wiki/Curiously_recurring_template_pattern) 代码可以作为控制台应用程序运行而没有任何问题
    【解决方案2】:

    解决方案 1

    为所有返回GlslType 的派生类添加静态方法。没有什么需要添加到基类中。可以使用单元测试+反射来检查缺少的实现。由Wouter de Kort推荐。

    解决方案 2

    Uniform&lt;T&gt; 改为静态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

    使用attributes instead。比如:

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

    结论

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

    【讨论】:

      【解决方案3】:

      GlslType 不是静态的,因此您需要一个对象引用才能访问它的值。抽象类中的静态属性主题已经被广泛讨论过,即:

      【讨论】:

      • 从这些我收集了 3 个可能的解决方案:(1)使用 Wouter 建议的静态方法,(2)在基类中使用静态属性来保证它的存在,然后用“new " 在派生类中,(3) 使用类属性。
      【解决方案4】:

      您需要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);
      

      【讨论】:

      • 没有别的办法了吗?我真的不想使用他们现有的构造函数来实例化一个,因为它会产生不良影响....我认为我必须编写一个 internal 构造函数,但是我必须将所说的秘密构造函数添加到 Uniform&lt;T&gt; 的每个 子类,以便我可以获得GlslType 的值?
      • @Mark,从逻辑上思考:您想获取非静态属性的值。没有类型的实例?这与任何反思或其他任何事情都没有任何关系。其简单而基本的 OOP 规则。您只能获取静态属性的值,而不需要包含类的实例。
      • 我的意思是如果我们将GlslType 更改为其他更容易获得的东西。这不是一成不变的。
      • 旁白...我想我不应该写Uniform&lt;T&gt;,因为我的意思不是同一个T 用来调用GetCode&lt;T&gt;.. 我的意思是通用的@987654333 @。 type.GetFields(BindingFlags.Public | BindingFlags.Instance).Where(f =&gt; typeof(Uniform&lt;&gt;).IsAssignableFrom(f.FieldType)) 没有返回任何东西...我在想是因为Uniform&lt;&gt; 是抽象的...没有什么可以分配的?
      • @Mark,不,这是因为f.FieldType 是一个封闭的泛型类型。因此,您需要按照 mu 答案中的说明指定通用参数:typeof(Uniform&lt;&gt;).MakeGenericType(SomeType).IsAssignableFrom(f.FieldType)typeof(Uniform&lt;&gt;).IsAssignableFrom(f.FieldType) 返回 false 是正常的。 f.FieldType 不是从 Uniform&lt;&gt; 派生的。它派生自Uniform&lt;SomeType&gt;,这是它可以分配的来源。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-11-03
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多