我有一个这样定义的类:
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);
}
...
}
我在填写???
时遇到问题。我相信GetValue
期望一个对象的实例,但我并不关心它是什么实例,因为它们都返回相同的值。 AFAIK没有public abstract static readonly
值,所以我必须使用属性。
那么我可以用什么代替???
来取回“int”(假设一个字段是UniformInt
)。
作为一方:如何将fields
限制为仅继承Uniform<>
的字段类型?
答案 0 :(得分:2)
您需要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);
答案 1 :(得分:1)
问题在于,由于您的属性不是静态的,因此编译器不会知道它们都返回相同的值。由于您的UniformInt
未被密封,因此其他用户可以从中继承并覆盖GlslType
以返回其他内容。然后UniformInt
和所有派生类可用于您的GetCode<T>()
方法。
静态方法确实是最好的选择。为了确保你在所有类上实现它们(你不能强迫它,因为静态方法不能是抽象的)我会写一个简单的单元测试,它使用反射来加载从Uniform<T>
继承的所有类并检查如果他们定义了静态属性。
更新
在考虑属性如何提供帮助时,经过一些实验,我想出了以下内容。它绝对不会赢得选美比赛,但作为一项学习活动,它很有帮助;)
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
{
}
}
答案 2 :(得分:1)
GlslType
不是静态的,因此您需要一个对象引用才能访问它的值。抽象类中静态属性的主题已经被广泛涵盖,即:
答案 3 :(得分:0)
将静态方法添加到返回GlslType
的所有派生类。没有需要添加到基类。可以使用单元测试+反射来检查缺少的实现。建议Wouter de Kort。
更改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);
使用attributes instead。类似的东西:
[GlslType("int")]
public class UniformInt : Uniform<int>
{
}
所有这三种解决方案都非常相似,似乎也有相同的缺点(无法强制派生类来实现它)。通过方法1或2抛出异常将有助于快速找到错误,或者通过修改fields
条件,我可以跳过没有该属性的类。