Почему typeof (System.Enum).IsEnum = false?

Как мы знаем, System.Enum является базой для всех перечислений, но мне интересно, почему отражение говорит о том, что оно не является перечислением?

Console.WriteLine(typeof(System.Enum).IsEnum) // says it is false

Я не могу понять логику, поэтому System.Enum не является перечислением, но все, что происходит от него, это перечисление?

У меня был второй шок, когда я увидел в msdn, что это класс

public abstract class Enum : ValueType, 
    IComparable, IFormattable, IConvertible

Итак, Enum - это класс, однако это тип значения (полученный из специального класса ValueType, который делает перечисление как тип значения) и является базовым для всех перечислений, но не является перечислением:)

Ну, если вы не считаете, что Enum является классом, проверьте typeof(System.Enum).IsClass

Возникает вопрос: есть ли причина, по которой IsEnum является ложным, а IsClass - true для типа, который является типом значений и является базовым для всех перечислений?

enum AAA { }
typeof(System.Enum).IsClass //True
typeof(System.Enum).IsEnum  //False
typeof(AAA).IsClass         //False
typeof(AAA).IsEnum          //True
typeof(AAA).BaseType        //System.Enum

Ответы

Ответ 1

IL не знает структуры. IL имеет только классы.

Итак, что такое С# struct? Это запечатанный класс, который расширяет тип System.ValueType. System.ValueType также определяет, что возвращают свойства IsClass и IsStruct класса Type.

Итак, почему Type.IsClass возвращает false? На самом деле довольно просто. Пока Type.IsClass действительно вернет false для перечисления, тип, который вы получаете, например. typeof(Enum) на самом деле не System.Type - it System.RuntimeType. И System.RuntimeType определяет метод IsValueTypeImpl несколько иначе:

return !(this == typeof(ValueType)) 
       && !(this == typeof(Enum)) 
       && this.IsSubclassOf(typeof(ValueType));

Итак, существует явная дополнительная проверка - сам тип Enum, а из ValueType, поэтому семантически a struct, фактически классифицируется как не тип значения.

Но отдельные типы перечислений, полученные из System.Enum, также являются подклассами ValueType и не являются частным случаем System.Enum, поэтому они регистрируются как не классы.

В целом, не предполагайте, что для С# существуют вещи, которые верны для С#. И, конечно же, не предполагайте, что абстракции на высоком уровне все еще сохраняются на практике - технически .NET ориентирован на 100% объектно-ориентированным, с единственным "мастером" System.Object поверх иерархии классов. Даже System.ValueType продолжается (должен) System.Object. Но - значения типов на самом деле не System.Object; когда вы добавляете их в System.Object, вы создаете новый объект, который обертывает фактический тип значения.

Так же, как и типы значений,.NETs Enum являются "уродливыми хаками". Это особая вещь, поскольку среда выполнения (и много внутреннего кода .NET) рассматривается, и они там, чтобы упростить для вас, как программиста, или повысить производительность (и безопасность и безопасность, и...).

В конце концов, как вы обнаружили, некоторые вещи должны быть непоследовательными. Enum происходит от ValueType. В соответствии с семантикой С# она должна быть struct. Но вы не можете расширить struct! И тем не менее, это то, что вы на самом деле хотите сделать в этом случае.

Я подозреваю, что если перечисления должны были быть добавлены в .NET в (скажем) 5.0, они будут реализованы по-разному. Возможно, это просто интерфейс IEnum и несколько методов расширения. Но методов расширения не было в С# 1.0, а для типов значений они налагали бы ненужные штрафы за производительность.

Ответ 2

Внутренне IsEnum вызывает следующий метод

IsSubclassOf(RuntimeType.EnumType)

со следующей реализацией (см. комментарий к методу):

        // Returns true if this class is a true subclass of c.  Everything 
        // else returns false.  If this class and c are the same class false is
        // returned. 
        //
        [System.Runtime.InteropServices.ComVisible(true)]
        [Pure]
        public virtual bool IsSubclassOf(Type c) 
        {
            Type p = this; 
            if (p == c) 
                return false;
            while (p != null) { 
                if (p == c)
                    return true;
                p = p.BaseType;
            } 
            return false;
        } 

поэтому он работает только для потомков Enum

Еще более интересен метод IsClass:

public bool IsClass {
            [Pure] 
            get {return ((GetAttributeFlagsImpl() & TypeAttributes.ClassSemanticsMask) == TypeAttributes.Class && !IsValueType);}
        } 
...
 public bool IsValueType {
            [Pure] 
            get {return IsValueTypeImpl();}
        } 
...
 protected virtual bool IsValueTypeImpl() 
        {
            // Note that typeof(Enum) and typeof(ValueType) are not themselves value types. 
            // But there is no point excluding them here because customer derived System.Type
            // (non-runtime type) objects can never be equal to a runtime type, which typeof(XXX) is.
            // Ideally we should throw a NotImplementedException here or just return false because
            // customer implementations of IsSubclassOf should never return true between a non-runtime 
            // type and a runtime type. There is no benefits in making that breaking change though.

            return IsSubclassOf(RuntimeType.ValueType); 
        }

Он проверяет семантику типа [class], и, как вы упомянули, Enum является классом. И затем проверяет, не является ли это типом значения (см. Код выше)