我可以强制子类声明一个常量吗?

时间:2010-08-13 13:04:31

标签: c#

我想强制子类定义一个常量值。

const string SomeConstantEverySubclassMustDefine = "abc";

我需要这个,因为我需要将它绑定到Type而不是实例,并且你不能覆盖静态方法/属性iirc。

我真的想对这些常量进行编译时检查。

让我更详细地解释一下:

我们的域模型中的某些类是特殊的,您可以根据类型为它们采取某些操作。因此逻辑与类型有关。要采取的操作需要绑定到类型的字符串。我确实可以每次创建一个实例作为变通方法并声明一个抽象属性,但这不是我想要的。我想在编译时强制执行字符串的声明,只是为了确定。

6 个答案:

答案 0 :(得分:20)

不,你不能。我建议你使用抽象属性使你的基类抽象,你可以在需要时获取它。然后,每个子类只需要返回一个常量即可实现该属性。缺点是您不能在基类中的静态方法中使用它 - 但这些方法与子类无关。

(它还允许允许子类根据需要自定义每个实例的属性......但这很少是实际问题。)

如果这对您不够,您可能需要考虑并行类型层次结构。基本上,多态性在.NET中不会以特定于类型的方式发生;仅以特定于实例的方式。

如果仍然想要这样做并使用反射获取它,我建议您只编写单元测试以确保定义相关常量。当你超越类型系统可以描述的范围时,这通常是你能做到的最好的。

答案 1 :(得分:9)

仅使用abstract property制作get。这就是我认为你可以做的强制一个类有价值。然后你可以在属性中返回一个常量。

E.G:

基类:

public abstract string MyConst { get; }

然后在派生类

public override string MyConst {
    get { return "constant"; }
}

答案 2 :(得分:8)

以下是我的工作方式。我使用了其他人建议的属性。

public class ObjectAttribute : Attribute
{
    public int ObjectSize { get; set; }
    public ObjectAttribute(int objectSize)
    {
        this.ObjectSize = objectSize;
    }
}
public abstract class BaseObject
{
    public static int GetObjectSize<T>() where T : IPacket
    {
        ObjectAttribute[] attributes = (ObjectAttribute[])typeof(T).GetCustomAttributes(typeof(ObjectAttribute), false);
        return attributes.Length > 0 ? attributes[0].ObjectSize : 0;
    }
}
[ObjectAttribute(15)]
public class AObject : BaseObject
{
    public string Code { get; set; }
    public int Height { get; set; }
}
[ObjectAttribute(25)]
public class BObject : BaseObject
{
    public string Code { get; set; }
    public int Weight { get; set; }
}

如果您希望实例访问该属性,只需将其添加到基本抽象类。

public abstract class BaseObject
{
    public static int GetObjectSize<T>() where T : IPacket
    {
        ObjectAttribute[] attributes = (ObjectAttribute[])typeof(T).GetCustomAttributes(typeof(ObjectAttribute), false);
        return attributes.Length > 0 ? attributes[0].ObjectSize : 0;
    }

    public int ObjectSize 
    {
        get
        {
            ObjectAttribute[] attributes = (ObjectAttribute[])GetType().GetCustomAttributes(typeof(ObjectAttribute), false);
            return attributes.Length > 0 ? attributes[0].ObjectSize : 0;
        }
    }
}

常量的用法

int constantValueA = AObject.GetObjectSize<AObject>();
int constantValueB = BObject.GetObjectSize<BObject>();
AObject aInstance = new AObject();
int instanceValueA = aInstance.ObjectSize;

答案 3 :(得分:6)

新想法

这是一个奇怪的想法:不是直接使用继承,而是创建一个单独的类,为从某种类型T派生的每个类型提供常量值。 this 类型的构造函数使用反射来验证是否确实为每个派生类型提供了值。

public abstract class Constant<T, TConstant>
{
    private Dictionary<Type, TConstant> _constants;

    protected Constant()
    {
        _constants = new Dictionary<Type, TConstant>();

        // Here any class deriving from Constant<T, TConstant>
        // should put a value in the dictionary for every type
        // deriving from T, using the DefineConstant method below.
        DefineConstants();

        EnsureConstantsDefinedForAllTypes();
    }

    protected abstract void DefineConstants();

    protected void DefineConstant<U>(TConstant constant) where U : T
    {
        _constants[typeof(U)] = constant;
    }

    private void EnsureConstantsDefinedForAllTypes()
    {
        Type baseType = typeof(T);

        // Here we discover all types deriving from T
        // and verify that each has a key present in the
        // dictionary.
        var appDomain = AppDomain.CurrentDomain;
        var assemblies = appDomain.GetAssemblies();
        var types = assemblies
            .SelectMany(a => a.GetTypes())
            .Where(t => baseType.IsAssignableFrom(t));

        foreach (Type t in types)
        {
            if (!_constants.ContainsKey(t))
            {
                throw new Exception(
                    string.Format("No constant defined for type '{0}'.", t)
                );
            }
        }
    }

    public TConstant GetValue<U>() where U : T
    {
        return _constants[typeof(U)];
    }
}

基本示例:

public class BaseType
{
    public static Constant<BaseType, string> Description { get; private set; }

    static BaseType()
    {
        Description = new BaseTypeDescription();
    }
}

public class DerivedType : BaseType
{ }

internal sealed class BaseTypeDescription : Constant<BaseType, string>
{
    public BaseTypeDescription() : base()
    { }

    protected override DefineConstants()
    {
        DefineConstant<BaseType>("A base type");
        DefineConstant<DerivedType>("A derived type");
    }
}

现在我有了允许我这样做的代码:

var description = BaseType.Description;

// returns "A base type"
string baseTypeDescription = description.GetValue<BaseType>();

// returns "A derived type"
string derivedTypeDescription = description.GetValue<DerivedType>();

原始答案

您可能不喜欢它,但最接近的方法是声明一个抽象的只读(无set)属性。

如果你有一个你的子类的实例,那么这可以和常量一样工作,即使它在技术上是实例级的(它对于给定类的所有实例都是一样的)。 / p>

例如,考虑IList.IsReadOnly。在大多数情况下,这实际上是一个告诉您底层类实现的属性,而不是特定于特定实例的任何状态。 (它可能是一个接口成员而不是抽象类成员,但它的想法是一样的。)

如果你试图静态访问它,那么......你运气不好。但在这种情况下,我无法看到你如何在不使用反射的情况下获得价值。也许那是你的意图;我不知道。

答案 4 :(得分:1)

你可以在基类中有一个静态方法,例如“Register”,它传递一个Type和一个常量值,意图是它由子类的类构造函数调用。然后,在所有基类构造函数中添加一个检查,表明正在构造的对象是注册类型。

abstract class Base
{
    private static Dictionary<Type, string> _registry = new Dictionary<Type, string>();

    protected static void Register(Type t, string constVal)
    {
        _registry.Add(t, constVal);
    }

    protected Base()
    {
        if(!_registry.ContainsKey(this.GetType()))
        throw new NotSupportedException("Type must have a registered constant");
    }

    public string TypeConstant
    {
        get
        {
            return _registry[this.GetType()];
        }
    }
}

class GoodSubtype : Base
{
    static GoodSubtype()
    {
        Base.Register(typeof(GoodSubtype), "Good");
    }

    public GoodSubtype()
        : base()
    {
    }
}

class Badsubtype : Base
{
    public Badsubtype()
        : base()
    {
    }
}

然后在其他地方,您可以构建GoodSubtype实例,但尝试构造Badsubtype会获得异常。我认为构造中的运行时错误是最快的,你可以通过这种类型的方案得到错误。

(如果涉及线程,您希望将ConcurrentDictionary用于您的注册表)

答案 5 :(得分:0)

还有一个尚未涵盖的方法,它使用new修饰符隐藏基类中的consts值。在某种程度上,它类似于Nap的solution,但不允许每个实例访问,因此不允许在基类内进行多态访问。此解决方案仅在您希望定义常量值但希望可以选择将其更改为不同子类中的不同值时才有用。

static void Main(string[] args)
{
    Console.WriteLine("BaseClass.MyConst = {0}, ClassA.MyConst = {1}, ClassB.MyConst = {2}", BaseClass.MyConst, ClassA.MyConst, ClassB.MyConst);
    Console.ReadKey();
}

class BaseClass
{
    public const int MyConst = 1;
}

class ClassA : BaseClass
{
    public new const int MyConst = 2;
}

class ClassB : BaseClass
{
}