2009-04-16 4 views
303

У меня есть перечисление в пространстве имен с низким уровнем. Я хотел бы предоставить класс или перечисление в пространстве имен среднего уровня, которое «наследует» перечисление низкого уровня.Enum "Inheritance"

namespace low 
{ 
    public enum base 
    { 
     x, y, z 
    } 
} 

namespace mid 
{ 
    public enum consume : low.base 
    { 
    } 
} 

Я надеюсь, что это возможно, или, возможно, каким-то класс, который может занять место перечисления потребляет, который обеспечит уровень абстракции для перечисления, но все же пусть экземпляр этого класса доступ к перечислению.

Мысли?

EDIT: Одна из причин, по которой я не просто переключил это на константы в классах, - это то, что переполнение нижнего уровня требуется службой, которую я должен потреблять. Мне дали WSDL и XSD, которые определяют структуру как перечисление. Сервис не может быть изменен.

+0

Я предложил решение, новый класс EnumBuilder: http://www.codeproject.com/KB/cs/EnumBuilder .aspx. Наслаждайтесь ... – marcusts

ответ

383

Это невозможно. Перечисления не могут наследоваться из других перечислений. Фактически все перечисления должны фактически наследовать от System.Enum. C# позволяет синтаксису изменять базовое представление значений enum, которое выглядит как наследование, но на самом деле они все еще наследуются от System.enum.

Подробную информацию см. В разделе 8.5.2 из CLI spec.Соответствующая информация из спецификации

  • Все перечисления должны наследоваться от System.Enum
  • Из вышесказанного, все перечислений типов значений и, следовательно, запечатаны
+1

И все типы значений происходят из System.ValueType –

+4

Следует упомянуть, что ответ @Seven является законным обходным путем: http://stackoverflow.com/a/4042826/538387 – Tohid

97

Короткий ответ - нет. Вы можете играть немного, если вы хотите:

Вы всегда можете сделать что-то вроде этого:

private enum Base 
{ 
    A, 
    B, 
    C 
} 

private enum Consume 
{ 
    A = Base.A, 
    B = Base.B, 
    C = Base.C, 
    D, 
    E 
} 

Но это не работает все, что больше, потому что Base.A = Consume.A

!

Вы всегда можете сделать что-то подобное, хотя:

public static class Extensions 
{ 
    public static T As<T>(this Consume c) where T : struct 
    { 
     return (T)System.Enum.Parse(typeof(T), c.ToString(), false); 
    } 
} 

для того, чтобы пересечь между Базой и потребляющим ...

Вы также можете отличать значения перечислений как ints и сравнивать их как ints вместо enum, но это тоже отстой.

Расширение обратный метод должен приведение типа его тип T.

+4

Я копаю это, мужик. Использовал эту концепцию, чтобы пузырить некоторые перечисления из моего ORM в мой открытый интерфейс (для тех, у кого нет ссылки ORM). – ObjectType

+3

Можно использовать перечисления для сравнения: 'Base.A == (Base) Consume.A' – Kresimir

+1

Использовать (десятичный) Base.A == (десятичный) Consume.A. Причина. Так работает объединение битов/масок (например, в Enum.IsDefined https://msdn.microsoft.com/en-us/library/system.enum.isdefined(v=vs.110).aspx). Таким образом, перечисление может быть установлено в целое число, не определенное в перечислении. Потребляемый тест = 123456; – TamusJRoyce

1

Перечни не являются реальными классами, даже если они похожи на него. Внутри они обрабатываются так же, как и их базовый тип (по умолчанию Int32). Поэтому вы можете сделать это только путем «копирования» одиночных значений из одного перечня в другой и отбрасывания их на их целое число, чтобы сравнить их для равенства.

12

Игнорируя тот факт, что база является зарезервированным словом, вы не можете наследовать перечисление.

Самое лучшее, что вы могли бы сделать что-то вроде этого:

public enum Baseenum 
{ 
    x, y, z 
} 

public enum Consume 
{ 
    x = Baseenum.x, 
    y = Baseenum.y, 
    z = Baseenum.z 
} 

public void Test() 
{ 
    Baseenum a = Baseenum.x; 
    Consume newA = (Consume) a; 

    if ((Int32) a == (Int32) newA) 
    { 
    MessageBox.Show(newA.ToString()); 
    } 
} 

Поскольку они все же базовый тип (т.е. целое) можно присвоить значение из экземпляра одного типа в другой который литой. Не идеальный, но он работает.

+2

база зарезервирована, но база не – erikkallen

+2

Он ссылается на использование OP базы в качестве имени перечисления, это просто имя примера. Я уверен, –

+0

То же, что и ответ Genisio. – nawfal

1

Перечисления не может быть из которой можно вывести других перечислений, но только из int, uint, short, ushort, long, ulong, byte и sbyte.

Как и Паскаль, вы можете использовать другие значения или константы enum для инициализации значения перечисления, но это о нем.

+8

Это немного неправильно из-за синтаксиса C# но enum не может фактически наследовать от int, uint и т. д. Под капотом они все еще наследуются от System.Enum. Просто член, представляющий перечисление, вводится в int, uint и т. Д. – JaredPar

+0

@ JaredPar. Когда перечисление перегружено из uint, это означает, что все значения являются значениями uint и т. Д. По умолчанию enum наследует int. (Взгляните на спецификацию C#, enum SomeEnum: uint {...} действительно работает.) –

+4

Фактически нет. Он наследует System.enum. Как было опубликовано раньше и чаще здесь, то, что вы считаете наследством, - это просто dangambiguity в csharp. – TomTom

140

Вы можете добиться того, что вы хотите с классами:

public class Base 
{ 
    public const int A = 1; 
    public const int B = 2; 
    public const int C = 3; 
} 
public class Consume : Base 
{ 
    public const int D = 4; 
    public const int E = 5; 
} 

Теперь вы можете использовать эти классы подобно тому, как когда они были перечислений:

int i = Consume.B; 

Update (после обновления вопроса):

Если вы присваиваете одинаковые значения int константам, как определено в существующем перечислении, то вы можете использовать между enu m и константы, например:

public enum SomeEnum // this is the existing enum (from WSDL) 
{ 
    A = 1, 
    B = 2, 
    ... 
} 
public class Base 
{ 
    public const int A = (int)SomeEnum.A; 
    //... 
} 
public class Consume : Base 
{ 
    public const int D = 4; 
    public const int E = 5; 
} 

// where you have to use the enum, use a cast: 
SomeEnum e = (SomeEnum)Consume.B; 
+5

Как вы перечислите поля в этом классе? Для меня это важное поведение перечисления: 'Enum.GetValues ​​(typeof (MyEnum)' –

+1

Вы можете использовать отражение: 'void Test() { foreach (System.Reflection.PropertyInfo pi в typeof (Consume) .GetProperties()) { Console.WriteLine (pi.Name); }} ' – mnieto

+2

Вы должны были бы убедиться, что сбор свойств с отражением игнорирует наследуемые свойства объекта – Robert

80

Решения выше, используя классы с int-константами, не имеют безопасности типа. То есть вы могли бы изобретать новые значения, фактически не определенные в классе. Кроме того, невозможно написать, например, метод ввода одного из этих классов.

Вы должны были бы написать

public void DoSomethingMeaningFull(int consumeValue) ... 

Однако существует класс решение на основе старых дней Java, когда не было никаких перечислений в наличии. Это обеспечивает почти перечислимое поведение. Единственное предостережение состоит в том, что эти константы не могут использоваться в switch-statement.

public class MyBaseEnum 
{ 
    public static readonly MyBaseEnum A = new MyBaseEnum(1); 
    public static readonly MyBaseEnum B = new MyBaseEnum(2); 
    public static readonly MyBaseEnum C = new MyBaseEnum(3); 

    public int InternalValue { get; protected set; } 

    protected MyBaseEnum(int internalValue) 
    { 
     this.InternalValue = internalValue; 
    } 
} 

public class MyEnum : MyBaseEnum 
{ 
    public static readonly MyEnum D = new MyEnum(4); 
    public static readonly MyEnum E = new MyEnum(5); 

    protected MyEnum(int internalValue) : base(internalValue) 
    { 
     // Nothing 
    } 
} 

[TestMethod] 
public void EnumTest() 
{ 
    this.DoSomethingMeaningful(MyEnum.A); 
} 

private void DoSomethingMeaningful(MyBaseEnum enumValue) 
{ 
    // ... 
    if(enumValue == MyEnum.A) { /* ... */ } 
    else if (enumValue == MyEnum.B) { /* ... */ } 
    // ... 
} 
+6

я думаю, что это правильный ответ вы.. не может иметь наследования для Enum, но это может позволить вам управлять им! – robob

+9

Приятный и чистый +1. Просто подсказка, вам действительно не нужно значение int вообще. –

+0

@SoMoS, вы предполагаете, что 'object' be используется [как рекомендуется для 'lock'] (http://msdn.microsoft. ком/EN-US/библиотека/c5kehkcz)? Я не могу придумать хороший способ сделать эту идею совместимой с «нумерацией» части 'enum', где' MyEnum.First binki

-6

Вы можете выполнить наследование в перечислении, однако оно ограничено только следующими типами. int, uint, byte, sbyte, short, ushort, long, ulong

E.g.

public enum Car:int{ 
Toyota, 
Benz, 
} 
+5

, которое не является наследованием в типичном смысле. – nawfal

+2

Я думаю, что OP просил наследовать одно перечисление от другого, а не только от базового числового типа (который все перечисления делают в C# либо неявно, либо явно.) – reirab

6

Я знаю, что этот ответ довольно поздно, но это то, что я в конечном итоге делает:

public class BaseAnimal : IEquatable<BaseAnimal> 
{ 
    public string Name { private set; get; } 
    public int Value { private set; get; } 

    public BaseAnimal(int value, String name) 
    { 
     this.Name = name; 
     this.Value = value; 
    } 

    public override String ToString() 
    { 
     return Name; 
    } 

    public bool Equals(BaseAnimal other) 
    { 
     return other.Name == this.Name && other.Value == this.Value; 
    } 
} 

public class AnimalType : BaseAnimal 
{ 
    public static readonly BaseAnimal Invertebrate = new BaseAnimal(1, "Invertebrate"); 

    public static readonly BaseAnimal Amphibians = new BaseAnimal(2, "Amphibians"); 

    // etc   
} 

public class DogType : AnimalType 
{ 
    public static readonly BaseAnimal Golden_Retriever = new BaseAnimal(3, "Golden_Retriever"); 

    public static readonly BaseAnimal Great_Dane = new BaseAnimal(4, "Great_Dane"); 

    // etc   
} 

Тогда я могу сделать что-то вроде:

public void SomeMethod() 
{ 
    var a = AnimalType.Amphibians; 
    var b = AnimalType.Amphibians; 

    if (a == b) 
    { 
     // should be equal 
    } 

    // call method as 
    Foo(a); 

    // using ifs 
    if (a == AnimalType.Amphibians) 
    { 
    } 
    else if (a == AnimalType.Invertebrate) 
    { 
    } 
    else if (a == DogType.Golden_Retriever) 
    { 
    } 
    // etc   
} 

public void Foo(BaseAnimal typeOfAnimal) 
{ 
} 
+2

Магические числа могут быть заменены объектами согласно http://stackoverflow.com/questions/757684/enum-inheritance#comment34753618_4042826. Но для этого конкретного случая вы можете получить лучшее из обоих миров, используя особенности существующей биологической номенклатуры, чтобы гарантировать уникальность. –

1

другой возможное решение:

public enum @base 
{ 
    x, 
    y, 
    z 
} 

public enum consume 
{ 
    x = @base.x, 
    y = @base.y, 
    z = @base.z, 

    a,b,c 
} 

// TODO: Add a unit-test to check that if @base and consume are aligned 

HTH

3

Это то, что я сделал. То, что я сделал по-другому, использует одно и то же имя и ключевое слово new на «потребляющем» enum. Поскольку имя enum такое же, вы можете просто бездумно использовать его, и это будет правильно.Плюс вы получаете intellisense. Вам просто нужно вручную позаботиться, настроив его, чтобы значения скопировались из базы и синхронизировали их. Вы можете помочь в этом вместе с комментариями кодов. Это еще одна причина, почему в базе данных при хранении значений enum я всегда сохраняю строку, а не значение. Потому что, если вы используете автоматически присваиваемые возрастающие целые значения, они могут со временем меняться.

// Base Class for balls 
public class BaseBall 
{ 
    // keep synced with subclasses! 
    public enum Sizes 
    { 
     Small, 
     Medium, 
     Large 
    } 
} 

public class VolleyBall : BaseBall 
{ 
    // keep synced with base class! 
    public new enum Sizes 
    { 
     Small = BaseBall.Sizes.Small, 
     Medium = BaseBall.Sizes.Medium, 
     Large = BaseBall.Sizes.Large, 
     SmallMedium, 
     MediumLarge, 
     Ginormous 
    } 
} 
+1

Рассмотрите возможность установки другого диапазона для новых значений производного класса (например, «SmallMedium = 100,'), чтобы вы могли поддерживать совместимость со старыми версиями своего программного обеспечения при добавлении новых значений в базовый класс. Например, добавление размера «Огромный» в вашем базовом перечислении присвоит значение «4», но '4' уже принято' SmallMedium' в производном классе. – Roberto

+1

@Roberto, Чтобы решить эту проблему, я никогда не сохраняю значения перечисления, а только имена. И соблюдение их синхронизации является обязательным условием здесь. Поэтому добавление «Huge» в базовый класс потребует «Огромного» в подклассе перед «SmallMedium» – toddmo

1

Это невозможно (как уже упоминалось @JaredPar). Попытка заставить логику обойти это - плохая практика. Если у вас есть base class, у которых есть enum, вам следует указать список всех возможных enum-values, а реализация класса должна работать со значениями, которые он знает.

E.g. Предположим, что у вас есть базовый класс BaseCatalog, и он имеет enum ProductFormats (Digital, Physical). Тогда у вас может быть MusicCatalog или BookCatalog, который может содержать как Digital, так и Physical, но если класс ClothingCatalog, он должен содержать только Physical.

0

Я также хотел перегрузить Enums и создал смесь the answer of 'Seven' on this page и the answer of 'Merlyn Morgan-Graham' on a duplicate post of this, а также пару улучшений.
Основные преимущества моего решения над другими:

  • автоматического приращения нижележащего Int значения
  • автоматического присвоения имен

Это решение вне коробки и может быть непосредственно вставляемый в ваш проект. Он разработан для моих нужд, поэтому, если вам не нравятся некоторые его части, просто замените их собственным кодом.

Во-первых, существует базовый класс CEnum, который должен наследовать все пользовательские перечисления. Он имеет базовую функциональность, аналогичную типу .net Enum:

public class CEnum 
{ 
    protected static readonly int msc_iUpdateNames = int.MinValue; 
    protected static int   ms_iAutoValue  = -1; 
    protected static List<int> ms_listiValue  = new List<int>(); 

    public int Value 
    { 
    get; 
    protected set; 
    } 

    public string Name 
    { 
    get; 
    protected set; 
    } 

    protected CEnum() 
    { 
    CommonConstructor (-1); 
    } 

    protected CEnum (int i_iValue) 
    { 
    CommonConstructor (i_iValue); 
    } 

    public static string[] GetNames (IList<CEnum> i_listoValue) 
    { 
    if (i_listoValue == null) 
     return null; 
    string[] asName = new string[i_listoValue.Count]; 
    for (int ixCnt = 0; ixCnt < asName.Length; ixCnt++) 
     asName[ixCnt] = i_listoValue[ixCnt]?.Name; 
    return asName; 
    } 

    public static CEnum[] GetValues() 
    { 
    return new CEnum[0]; 
    } 

    protected virtual void CommonConstructor (int i_iValue) 
    { 
    if (i_iValue == msc_iUpdateNames) 
    { 
     UpdateNames (this.GetType()); 
     return; 
    } 
    else if (i_iValue > ms_iAutoValue) 
     ms_iAutoValue = i_iValue; 
    else 
     i_iValue = ++ms_iAutoValue; 

    if (ms_listiValue.Contains (i_iValue)) 
     throw new ArgumentException ("duplicate value " + i_iValue.ToString()); 
    Value = i_iValue; 
    ms_listiValue.Add (i_iValue); 
    } 

    private static void UpdateNames (Type i_oType) 
    { 
    if (i_oType == null) 
     return; 
    FieldInfo[] aoFieldInfo = i_oType.GetFields (BindingFlags.Public | BindingFlags.Static); 

    foreach (FieldInfo oFieldInfo in aoFieldInfo) 
    { 
     CEnum oEnumResult = oFieldInfo.GetValue (null) as CEnum; 
     if (oEnumResult == null) 
     continue; 
     oEnumResult.Name = oFieldInfo.Name; 
    } 
    } 
} 

Во-вторых, здесь 2 производные классы Enum. Все производные классы нуждаются в некоторых базовых методах для работы, как ожидалось. Это всегда один и тот же шаблонный код; Я еще не нашел способ передать его в базовый класс. Код первого уровня наследования немного отличается от всех последующих уровней.

public class CEnumResult : CEnum 
{ 
    private static List<CEnumResult> ms_listoValue = new List<CEnumResult>(); 

    public static readonly CEnumResult Nothing   = new CEnumResult ( 0); 
    public static readonly CEnumResult SUCCESS   = new CEnumResult ( 1); 
    public static readonly CEnumResult UserAbort  = new CEnumResult (11); 
    public static readonly CEnumResult InProgress  = new CEnumResult (101); 
    public static readonly CEnumResult Pausing   = new CEnumResult (201); 
    private static readonly CEnumResult Dummy   = new CEnumResult (msc_iUpdateNames); 

    protected CEnumResult() : base() 
    { 
    } 

    protected CEnumResult (int i_iValue) : base (i_iValue) 
    { 
    } 

    protected override void CommonConstructor (int i_iValue) 
    { 
    base.CommonConstructor (i_iValue); 

    if (i_iValue == msc_iUpdateNames) 
     return; 
    if (this.GetType() == System.Reflection.MethodBase.GetCurrentMethod().DeclaringType) 
     ms_listoValue.Add (this); 
    } 

    public static new CEnumResult[] GetValues() 
    { 
    List<CEnumResult> listoValue = new List<CEnumResult>(); 
    listoValue.AddRange (ms_listoValue); 
    return listoValue.ToArray(); 
    } 
} 

public class CEnumResultClassCommon : CEnumResult 
{ 
    private static List<CEnumResultClassCommon> ms_listoValue = new List<CEnumResultClassCommon>(); 

    public static readonly CEnumResult Error_InternalProgramming   = new CEnumResultClassCommon (1000); 

    public static readonly CEnumResult Error_Initialization    = new CEnumResultClassCommon(); 
    public static readonly CEnumResult Error_ObjectNotInitialized   = new CEnumResultClassCommon(); 
    public static readonly CEnumResult Error_DLLMissing     = new CEnumResultClassCommon(); 
    // ... many more 
    private static readonly CEnumResult Dummy        = new CEnumResultClassCommon (msc_iUpdateNames); 

    protected CEnumResultClassCommon() : base() 
    { 
    } 

    protected CEnumResultClassCommon (int i_iValue) : base (i_iValue) 
    { 
    } 

    protected override void CommonConstructor (int i_iValue) 
    { 
    base.CommonConstructor (i_iValue); 

    if (i_iValue == msc_iUpdateNames) 
     return; 
    if (this.GetType() == System.Reflection.MethodBase.GetCurrentMethod().DeclaringType) 
     ms_listoValue.Add (this); 
    } 

    public static new CEnumResult[] GetValues() 
    { 
    List<CEnumResult> listoValue = new List<CEnumResult> (CEnumResult.GetValues()); 
    listoValue.AddRange (ms_listoValue); 
    return listoValue.ToArray(); 
    } 
} 

Классы были успешно протестированы с follwing кодом:

private static void Main (string[] args) 
{ 
    CEnumResult oEnumResult = CEnumResultClassCommon.Error_Initialization; 
    string sName = oEnumResult.Name; // sName = "Error_Initialization" 

    CEnum[] aoEnumResult = CEnumResultClassCommon.GetValues(); // aoEnumResult = {testCEnumResult.Program.CEnumResult[9]} 
    string[] asEnumNames = CEnum.GetNames (aoEnumResult); 
    int ixValue = Array.IndexOf (aoEnumResult, oEnumResult); // ixValue = 6 
}