C#基础之枚举

时间:2023-03-09 15:20:20
C#基础之枚举

1.认识Enum

  以前一直以为Enum是值类型,在VS中查看Enum的定义时才发现它是一个抽象的类。但是这个类很奇怪,Enum继承了ValueType这个很熟悉的值类型基类,它是唯一一个继承自ValueType类型但又不是值类型的引用类型。Enum还实现了3个接口,分别是IComparable、IFormattable、IConvertible。IComparable中只有一个CompareTo方法用来进行比较,IFormattable中也只有一个方法ToString用来格式化当前枚举对象,IConvertible则是用来进行值类型转化为引用类型的接口。我们自定义的枚举正是继承于Enum类型,之所以要继承Enum,是因为在枚举中不能定义任何属性、方法或事件(其实也就是方法),可是我们还需要操作枚举啊,这时就会用到Enum中的方法。不过又有一个奇怪的事情,在vs中随便写一段枚举如下面代码所示。查看这段代码的IL指令时发现hh的开头部分是.class,我脑子里大概记得枚举是值类型可是在IL中确实显示的是.class。接着我随便写了一个struct放到IL中发现仍然是以.class开头。然后我才发现自己有一个误区,值类型不是struct,struct是属于值类型的。当然最大的疑惑还是.class,从这里也可以看出在.net中所有类型都是class。不要忘了值类型也是继承于ValueType引用类型,到这里问题又来了,值类型是不能继承引用类型的,只能实现接口,那系统内这些值类型继承引用类型到底是如何实现的,特别是内存的分配。

  查阅资料后有一位前辈的意思是系统分配内存时会看这个类型是否继承ValueType,如果继承呢就是值类型,否则就是引用类型。不过刚学习了Enum显然这个观点并不完全对。但是思想可以理解那就是值类型继承引用类型时,值类型会在栈上开辟空间,引用类型会在堆上开辟空间,然后值类型中会有一个指针指向了堆中的引用类型。再回到IL中,我仔细比较了枚举类型、结构体、类这三种类型的IL头部,发现还是有区别的。如下面第二段IL指令所示,它们的区别在于auto与sequential,在.net中有一个内存布局字段LayoutKind,它用来修饰程序中的类型。LayoutKind有三种结果,auto表示内存布局有.net自动分配,sequential表示安照次序相继的分配,explicit我查英文是明确的意思,具体在什么地方用我还不清楚。类和枚举是auto自动分配的,struct和Int32值类型是sequential方式分配的,这个结果显示枚举是引用类型而不是值类型。对这个结果我感到很奇怪,在后面认识枚举中我将写的代码弄得Reflector中发现确实产生了装箱,这说明枚举确实是值类型。这个地方我没搞明白,还请各位前辈指点迷津!

public enum hh
{
a,
b,
c,
} .class public auto ansi sealed hh
extends [mscorlib]System.Enum
{
.field public static literal valuetype Microsoft.hh a = int32() .field public static literal valuetype Microsoft.hh b = int32() .field public static literal valuetype Microsoft.hh c = int32() .field public specialname rtspecialname int32 value__ }
      //枚举
.class public auto ansi sealed hh extends [mscorlib]System.Enum //类
.class public auto ansi beforefieldinit MyClass extends [mscorlib]System.Object //结构体
.class public sequential ansi sealed beforefieldinit jj extends [mscorlib]System.ValueType //Int32
.class public sequential ansi serializable sealed beforefieldinit Int32 extends System.ValueType

2.使用枚举

  回到枚举,枚举默认就是int类型的,它里面只能声明byte,sbyte,short,ushort,int,uint,long ulong这8种类型,在代码里写a='2'是不会报错的,在IL中已经转换为2的asci码50了。关于枚举中的成员赋值,总结起来有3点要注意。第一如果不为枚举成员指定初始值,其将会从0开始自动增加就如上面第一段代码中的枚举hh;第二如果在枚举中为枚举成员指定了初始值,其余剩下的成员将依旧以指定的成员初始值开始依次增加,如下面代码段所示c=6而f=2。第三当在外部写hh h=new hh()时,此时h并不是指第一个枚举值2,而是为0,也就是指向枚举成员中值为0的枚举成员。枚举里面只能声明上面提到的8种整型,因此枚举可以与整型进行相互转换,但是要注意都必须显示转换。如下面第二段代码所示,还有一些关于枚举的简单使用也在其中。最后还有一个关于枚举的功能就是位枚举,其实我们也经常看到。比如一个颜色枚举,有时候我们需要指定多个颜色就像Color.Red|Color.Blue|Color.Green这样。具体代码如下面第三段代码所示,在使用水果枚举时我们可以指定多个值,这给我们带来了不少的好处。只不过判断这种枚举组合是否是一个正确的组合不好判断。

  使用枚举可以让代码变得清晰简单,但是它的功能也可以使用类的静态字段来实现。不过枚举中的值将在编译时即替换为常量,而类的字段则不会,再加上类是分配在堆上的大型资源,性能肯定没有枚举快。同样结构体也可以声明公共字段来完成枚举的功能,那枚举和只含字段的结构体相比哪个更好呢?我觉得还是枚举,因为枚举还有一个优点,前面我特别强调了枚举与整型进行转换时一定要强制转换。这使得强类型的枚举比结构体更加安全,比如有一个方法MyFunc(MyEnum myEnum),参数中指定为枚举类型的话那么传参时将只能传入枚举类型,否则会报错。如果是结构体声明的int类型将有可能使这个方法被传入错误的参数而导致系统不稳定。所以以后对于需要声明分类信息的功能,枚举是首选。我资历浅,如有错误还请各位前辈指出。

public enum hh
{
a=,
b=,
c,
d,
e=,
f,
g,
h
} .class public auto ansi sealed hh
extends [mscorlib]System.Enum
{
.field public static literal valuetype Microsoft.hh a = int32() .field public static literal valuetype Microsoft.hh b = int32() .field public static literal valuetype Microsoft.hh c = int32() .field public static literal valuetype Microsoft.hh d = int32() .field public static literal valuetype Microsoft.hh e = int32() .field public static literal valuetype Microsoft.hh f = int32() .field public static literal valuetype Microsoft.hh g = int32() .field public static literal valuetype Microsoft.hh h = int32() .field public specialname rtspecialname int32 value__ }
static void Main(string[] args)
{
hh h1 = new hh();
Console.WriteLine(h1.ToString());//输出0
hh h2 = hh.a;
Console.WriteLine(h2.ToString());//输出a //枚举与整型相互转换
int i = (int)h2;
hh h3 = (hh);
//也可以使用Enum中的parse方法
hh h4 = (hh)Enum.Parse(typeof(hh), "");
Console.WriteLine(h3.ToString()); //a
Console.WriteLine(h4.ToString()); //24 //枚举与字符串之间的映射,在ToString中传入D表示转换为十进制2,为G表示转换为a
string s1 = h3.ToString("D"); //
string s2 = h3.ToString("G"); //a,ToString默认是以G这种形式转换的。
hh h5 = (hh)Enum.Parse(typeof(hh), "a", false);//h5为a
//在IConvertible中还有许多To类型方法 //arrayValue指的是hh[8],arrayName指的是string[8],它们都按照整型值从小到大排列
Array arrayValue = Enum.GetValues(typeof(hh));
Array arrayName = Enum.GetNames(typeof(hh));
Console.ReadLine();
}
    class Program
{
static void Main(string[] args)
{
//输出Apple,Peach
Fruit f = Fruit.Apple | Fruit.Peach;
Console.WriteLine(f.ToString());
//判断枚举组合是否包含想要的枚举值的2种方式
if (f.ToString().Contains("Apple"))
Console.WriteLine("ok");
if((Fruit.Apple&f)!=)
Console.WriteLine("ok"); //判断枚举中是否有这个成员比如葡萄Grape,可以使用IsDefined,下面的语句将返回false
bool isExist = Enum.IsDefined(typeof(Fruit), "Grape");
//可是对于组合枚举,将无法进行判断,下面两条语句将返回false、true,如果没有Mango将是false
bool isExist2 = Enum.IsDefined(typeof(Fruit), "Apple,Peach");
bool isExist3 = Enum.IsDefined(typeof(Fruit), 0x07);
//而且组合时和的组成可以有多种组成,比如这里的0x07也有可能是0x01与0x06的组合,
//因此对于判断枚举中是否可以存在这个组合枚举无法轻松判断。
Console.ReadLine();
}
}
//Flags特性是专门为枚举而提供的特性,如果没有Flags特性结果将会是7,而不是Apple,Peach
[Flags]
public enum Fruit
{
Watermelon = 0x01,
Pear = 0x02,
Apple = 0x03,
Peach = 0x04,
Mango=0x07
}

声明:本文原创发表于博客园,作者为方小白,如有错误欢迎指出 。本文未经作者许可不许转载,否则视为侵权。