代码之家  ›  专栏  ›  技术社区  ›  Falcon

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

c#
  •  28
  • Falcon  · 技术社区  · 14 年前

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

    喜欢

    const string SomeConstantEverySubclassMustDefine = "abc";
    

    我需要它,因为我需要它绑定到类型,而不是实例,并且您不能重写静态方法/属性IIRC。

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

    让我更详细地解释一下:

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

    6 回复  |  直到 11 年前
        1
  •  20
  •   Jon Skeet    14 年前

    不,您不能。我建议您使用一个抽象属性来抽象您的基类,您可以在需要时提取该属性。然后,每个子类可以通过返回常量(如果需要)来实现属性。缺点是您不能在基类中的静态方法中使用它——但是这些方法无论如何都不会与子类相关联。

    (也) 允许 子类以自定义每个实例的属性,如有必要…但这很少是实际问题。)

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

    如果你 仍然 要做到这一点并通过反射获取它,我建议您只编写单元测试来确保定义了相关的常量。当你超出类型系统所能描述的范围时,这通常是你能做到的最好的。

        2
  •  9
  •   Bob Fincheimer    14 年前

    做一个 abstract property 只有一个 get . 我认为您可以这样做来强制类具有值。然后您可以只返回属性中的常量。

    例如:

    基类:

    public abstract string MyConst { get; }
    

    然后在派生类中

    public override string MyConst {
        get { return "constant"; }
    }
    
        3
  •  8
  •   Nap    12 年前

    这是我的工作方式。我按照别人的建议使用了属性。

    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;
    
        4
  •  6
  •   Dan Tao    14 年前

    新理念

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

    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 )属性。

    如果您有子类的一个实例,那么它可以像常量一样工作,即使它在技术上是实例级的(对于给定类的所有实例,它都是相同的)。

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

    如果你想静态访问它,那么…那你就走运了。但在这种情况下,我看不到您如何在不使用反射的情况下获得值。也许这就是你的意图,我不知道。

        5
  •  1
  •   Damien_The_Unbeliever    14 年前

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

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

    然后在其他地方,您可以构造好的子类型实例,但是尝试构造坏的子类型会得到一个异常。我认为在构建时出现运行时错误是这种方案最快出现错误的时候。

    (如果涉及线程,则要对注册表使用ConcurrentDictionary)

        6
  •  0
  •   Community Justin Hirsch    7 年前

    还有一种方法没有被覆盖,它使用 new 要隐藏的修改器 consts 基类中的值。在某种程度上,它类似于午睡 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
    {
    }