我有一个这样定义的类:
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
条件跳过没有该属性的类。