CLR 枚举类型

更新:2007 年 11 月

从 C++ 托管扩展到 Visual C++ 2008,枚举的声明和行为发生了更改。

托管扩展的枚举声明以 __value 关键字开头。其目的是将本机枚举与从 System::ValueType 派生的 CLR 枚举区分开来,同时建议一个类似功能。例如:

__value enum e1 { fail, pass };
public __value enum e2 : unsigned short  { 
   not_ok = 1024, 
   maybe, ok = 2048 
};

新语法解决区分本机枚举和 CLR 枚举问题的方法是:强调后者的类性质而非其值类型根。同样,丢弃了 __value 关键字,而由空格分隔的关键字对 enum class 替换。这对引用、值和接口类的声明提供了对称的成对关键字:

enum class ec;
value class vc;
ref class rc;
interface class ic;

枚举对 e1 和 e2 的转换在新语法中如下所示:

enum class e1 { fail, pass };
public enum class e2 : unsigned short { 
   not_ok = 1024,
   maybe, ok = 2048 
};

除此语法的稍微更改外,CLR 枚举的行为在许多方面发生了更改:

  • 不再支持 CLR 枚举的前向声明。没有映射。而简单地将其标记为编译时错误。
__value enum status; // Managed Extensions: ok
enum class status;   // new syntax: error
  • 内置算术类型和 Object 类层次结构之间的重载决策在这两种语言版本中相反! 它的副作用是 CLR 枚举不再隐式转换为算术类型。

  • 在新语法中,CLR 枚举维护它自己的范围,这在托管扩展中不会发生。以前,枚举数在枚举的包含范围内可见。现在,枚举数封装在枚举的范围内。

CLR 枚举是一种对象

考虑以下代码片断:

__value enum status { fail, pass };

void f( Object* ){ Console::WriteLine("f(Object)\n"); }
void f( int ){ Console::WriteLine("f(int)\n"); }

int main()
{
   status rslt = fail;

   f( rslt ); // which f is invoked?
}

对本机 C++ 程序员来说,调用哪个重载的 f 的实例的问题的通常回答是:调用 f(int) 的实例。枚举是符号整数常数,并且它参与此情况下优先级高的标准整数提升。 实际上,在托管扩展中,它是调用解析到的实例。这导致了许多疑问 — 不是当我们在本机 C++ 框架中使用枚举时,而是在我们需要它们与现有 BCL(基类库)交互时,其中 Enum 是从 Object 间接派生的类。在 Visual C++ 2008 语言设计中,调用的 f 的实例是 f(Object^) 的实例。

Visual C++ 2008 选择强制执行此操作的方式是不支持 CLR 枚举类型和算术类型之间的隐式转换。这意味着 CLR 枚举类型的对象向算术类型的任何分配都需要显式强制转换。例如,假设

void f( int );

作为一个非重载方法,在托管扩展中,调用

f( rslt ); // ok: Managed Extensions; error: new syntax

是可以的,并且 rslt 内包含的值隐式转换为一个整数值。在 Visual C++ 2008 中,此调用无法编译。若要正确地转换它,必须插入一个转换运算符:

f( safe_cast<int>( rslt )); // ok: new syntax

CLR 枚举类型的范围

C 和 C++ 语言的差别之一是 C++ 中增加了结构功能内的范围。在 C 中,结构仅是一个不支持接口或关联范围的数据聚合。这在当时是一个相当基本的更改,对原先使用 C 语言的新 C++ 用户来说是一个有争议的问题。本机枚举和 CLR 枚举之间的关系是类似的。

在托管扩展中,为了模拟不存在本机枚举内的范围,曾尝试为 CLR 枚举的枚举数定义弱插入名称。这并没有证明是成功的。问题是它导致枚举数溢出到全局命名空间中,从而难于管理名称冲突。在新语法中,我们在支持 CLR 枚举内的范围方面与其他 CLR 语言保持一致。

这意味着新语法无法识别 CLR 枚举的枚举数的任何非限定使用。让我们看一个具体的示例。

// Managed Extensions supporting weak injection
__gc class XDCMake {
public:
   __value enum _recognizerEnum { 
      UNDEFINED,
      OPTION_USAGE, 
      XDC0001_ERR_PATH_DOES_NOT_EXIST = 1,
      XDC0002_ERR_CANNOT_WRITE_TO = 2,
      XDC0003_ERR_INCLUDE_TAGS_NOT_SUPPORTED = 3,
      XDC0004_WRN_XML_LOAD_FAILURE = 4,
      XDC0006_WRN_NONEXISTENT_FILES = 6,
   };

   ListDictionary* optionList;
   ListDictionary* itagList;

   XDCMake() {
      optionList = new ListDictionary;

      // here are the problems …
      optionList->Add(S"?", __box(OPTION_USAGE)); // (1)
      optionList->Add(S"help", __box(OPTION_USAGE)); // (2)

      itagList = new ListDictionary;
      itagList->Add(S"returns", 
         __box(XDC0004_WRN_XML_LOAD_FAILURE)); // (3)
   }
};

为了编译源代码,需要将枚举数名称的三个非限定使用((1)、(2) 和 (3))分别限定在新语法的转换中。以下是原始源代码的一个正确转换:

ref class XDCMake {
public:
   enum class _recognizerEnum {
      UNDEFINED, OPTION_USAGE, 
      XDC0001_ERR_PATH_DOES_NOT_EXIST = 1,
      XDC0002_ERR_CANNOT_WRITE_TO = 2,
      XDC0003_ERR_INCLUDE_TAGS_NOT_SUPPORTED = 3,
      XDC0004_WRN_XML_LOAD_FAILURE = 4,
      XDC0006_WRN_NONEXISTENT_FILES = 6
   };

   ListDictionary^ optionList;
   ListDictionary^ itagList;

   XDCMake() {
      optionList = gcnew ListDictionary;
      optionList->Add("?",_recognizerEnum::OPTION_USAGE); // (1)
      optionList->Add("help",_recognizerEnum::OPTION_USAGE); //(2)
      itagList = gcnew ListDictionary;
      itagList->Add( "returns", 
         _recognizerEnum::XDC0004_WRN_XML_LOAD_FAILURE); //(3)
   }
};

这会更改本机枚举和 CLR 枚举之间的设计策略。由于在 Visual C++ 2008 中 CLR 枚举保持关联范围,对类中枚举的声明进行封装既没必要又效率低下。这个用法随着贝尔实验室的 cfront 2.0 不断发展,也用来解决全局命名污染的问题。

在由贝尔实验室的 Jerry Schwarz 发布的新 iostream 库的原始测试版中,Jerry 没有封装为该库定义的所有关联枚举以及公共枚举数(如 read、write、append 等),使用户几乎不可能编译自已的现有代码。第一种解决方案是重整名称,如 io_read、io_write 等。第二种解决方案是通过将范围添加到枚举来修改语言,但这在当时是不切实际的。(折中的解决方案是:封装类或类层次结构中的枚举,其中标记名和枚举的枚举数将填充封闭类范围。) 也就是说,至少最初将枚举放置在类中的动机是不理性的,但是却对全局命名空间污染问题作了实际的响应。

有了 Visual C++ 2008 枚举,封装类中的枚举不再有任何明显的好处。事实上,如果查看 System 命名空间,您将看到枚举、类和接口都存在于相同的声明空间中。

请参见

概念

值类型及其行为

参考

enum class