【C#基礎概念】枚舉 (enum詳解)


我們重點來講解 簡單枚舉和標志枚舉的用法和區別

繼承

Object-> ValueType ->Enum
Object-> ValueType ->struct 包括int float等簡單值類型 Object-> ValueType ->ValueTuple Object-> ValueType ->Nullable

 

枚舉的簡介:

1、枚舉類型 是由基礎整型數值類型的一組命名常量定義的值類型

2.枚舉使用enum關鍵字來聲明, 枚舉可以和類並列也可以  寫在類里面,不能寫在方法里。

namespace ConsoleApp1
{
    class Program
    {
     //////////////省略。。。。。。。。。   
    }
        [Flags]
   enum Man
        {
         //////////////省略。。。。。。。。。
        }
}

3、枚舉都是隱式密封的(sealed),不允許作為基類派生子類。枚舉里面的成員只能是默認公共的靜態的(public、static),不能有訪問修飾符;枚舉本身一般不加訪問修飾符,要加的話只能是internal

    或public;

4、枚舉成員是常量 不可更改。可以當作字段來用。由於枚舉成員在編譯時將被替換為字面量,使得其成員取值類型受到了一定限制。
 
public readonly int SOU = 12;
public const int seiu = 12;
enum n { i=0}
編譯后的iL代碼
.field public initonly int32 SOU
.field public static literal int32 seiu = int32(12)
.field public static literal valuetype Galaxy.Program/n i = int32(0)
//枚舉中字段IL代碼和Const 常量定義的IL代碼是一樣的,編譯時常數,在編譯時是已知的,在程序的生命周期內不會改變

 

5、默認情況下,枚舉成員的關聯常數值為類型 int(被編譯查成IL: .field public specialname rtspecialname int32 value__) ;它們從零開始,並按定義文本順序遞增 1。 可以顯式指定任何
其他整數數值類型作為枚舉類型的基礎類型。 還可以顯式指定關聯的常數值,如下面的示例所示:
enum ErrorCode : int  //不是繼承,只是寫法像繼承,查看IL代碼 被編譯成.field public specialname rtspecialname int32 value__,如果是繼承應該顯示extends
{
None = 0,
Unknown = 1,
ConnectionLost = 100,
OutlierReading = 200
}

 

6、枚舉元素不可以重復,枚舉元素的值可以重復。最后一個元素后的逗號可有可無。枚舉最后的冒號可有可無。
7、 基礎類型指定為每個枚舉數分配的存儲大小。
 

 

System.Enum 類型和枚舉約束

System.Enum 類型是所有枚舉類型的抽象基類。 它提供多種方法來獲取有關枚舉類型及其值的信息。 有關更多
信息和示例,請參閱 System.Enum API 參考頁。
從 C# 7.3 開始,你可以在基類約束中使用 System.Enum (稱為枚舉約束),以指定類型參數為枚舉類型。 所有枚舉
類型也都滿足 struct 約束,此約束用於指定類型參數為不可為 null 的值類型。

轉換

對於任何枚舉類型,枚舉類型與其基礎整型類型之間存在顯式轉換。 如果將枚舉值轉換為其基礎類型,則結果為
枚舉成員的關聯整數值。
public enum Season
{
Spring,
Summer,
Autumn,
Winter
}
public class EnumConversionExample
{
public static void Main()
{
Season a = Season.Autumn;
Console.WriteLine($"Integral value of {a} is {(int)a}"); // output: Integral value of Autumn is 2
var b = (Season)1;
Console.WriteLine(b); // output: Summer
}
}

使用 Enum.IsDefined 方法來確定枚舉類型是否包含具有特定關聯值的枚舉成員。
對於任何枚舉類型,都存在分別與 System.Enum 類型的裝箱和取消裝箱相互轉換。


  1、簡單枚舉:枚舉變落表示一個成員;

 

 

 

 

 

枚舉類型的作用是限制其變量只能從有限的選項中取值,這些選項(枚舉類型的成員)各自對應於一個數字,數字默認從 0 開始,並以此遞增。例如:

public enum Days
{
Sunday, Monday, Tuesday, // ...
}

其中 Sunday 的值是 0,Monday 是 1,以此類推。為了一眼能看出每個成員代表的值,一般推薦顯示地將成員值寫出來,不要省略:

public enum Days
{
Sunday = 0, Monday = 1, Tuesday = 2, // ...
}

C# 枚舉成員的類型默認是 int 類型,通過繼承可以聲明枚舉成員為其它類型,比如:

publicenum Days : byte
{
Monday = 1,
Tuesday = 2,
Wednesday = 3,
Thursday = 4,
Friday = 5,
Saturday = 6,
Sunday = 7
}

枚舉類型一定是繼承自 byte、sbyte、short、ushort、int、uint、long 和 ulong 中的一種,不能是其它類型。下面是幾個枚舉的常見用法(以上面的 Days 枚舉為例):

// 枚舉轉字符串
string foo = Days.Saturday.ToString(); // "Saturday"
string foo = Enum.GetName(typeof(Days), 6); // "Saturday"
// 字符串轉枚舉
Enum.TryParse("Tuesday", out Days bar); // true, bar = Days.Tuesday
(Days)Enum.Parse(typeof(Days), "Tuesday"); // Days.Tuesday

// 枚舉轉數字
byte foo = (byte)Days.Monday; // 1
// 數字轉枚舉
Days foo = (Days)2; // Days.Tuesday

// 獲取枚舉所屬的數字類型
Type foo = Enum.GetUnderlyingType(typeof(Days))); // System.Byte

// 獲取所有的枚舉成員
Array foo = Enum.GetValues(typeof(MyEnum);
// 獲取所有枚舉成員的字段名
string[] foo = Enum.GetNames(typeof(Days));

另外,值得注意的是,枚舉可能會得到非預期的值(值沒有對應的成員)。比如:

Days d = (Days)21; // 不會報錯
Enum.IsDefined(typeof(Days), d); // false

即使枚舉沒有值為 0 的成員,它的默認值永遠都是 0。

var z = default(Days); // 0

枚舉可以通過 Description、Display 等特性來為成員添加有用的輔助信息,比如:

public enum ApiStatus
{
    [Description("成功")]
    OK = 0,
    [Description("資源未找到")]
    NotFound = 2,
    [Description("拒絕訪問")]
    AccessDenied = 3
}

static class EnumExtensions
{
    public static string GetDescription(this Enum val)
    {
        var field = val.GetType().GetField(val.ToString());
        var customAttribute = Attribute.GetCustomAttribute(field, typeof(DescriptionAttribute));
        if (customAttribute == null) { return val.ToString(); }
        else { return ((DescriptionAttribute)customAttribute).Description; }
    }
}

static void Main(string[] args)
{
    Console.WriteLine(ApiStatus.Ok.GetDescription()); // "成功"
}

上面這些我認為已經包含了大部分我們日常用到的枚舉知識了。下面我們繼續回到文章開頭說的用戶角色存儲問題。

 

2、標記枚舉:枚舉變量可以表示一個集合,每當可枚舉表示可能值的集合而不是單個值時,應使用[Flags]屬性

 

 

2.1 標志枚舉的使用准則

避免創建標志枚舉,其中某些值的組合無效。

 避免使用值為零的標記枚舉值,除非該值表示 "所有標志均已清除" 並正確命名,如下一個准則所述。

✔️ DO name 標記枚舉的零值 None 。 對於標志枚舉,值始終為 "清除所有標志"。

 

 

2.2三種寫法都是等效的;

第一種寫法:位數大了以后不容易察覺錯誤;

第二中寫法:推薦,容易理解 標志枚舉是如何運作的。

第三種寫法:容易閱讀,但是有個bug,當移位超出整形限制范圍時候,編譯器不會報錯,計算結果失效。曹操不是兄弟,但是在int整型范圍之下 1<<621<<30 值是一樣的,導致計算結果出錯。

 

static void Main(string[] args)
        {
           Man xiongdi = Man.關羽 | Man.劉備 | Man.張飛;

            if((xiongdi.HasFlag(Man.曹操)))
            Console.WriteLine($"{Man.司馬懿}是兄弟");//司馬懿是兄弟
            if ((xiongdi.HasFlag(Man.關羽)))
                Console.WriteLine($"{Man.關羽}是兄弟");//關羽是兄弟
            

        }
        [Flags]
        enum Man 
        {
            曹操=1<<30,//IL反編譯顯示:曹操 = 0x40000000,
            司馬懿 =1<<31,//IL反編譯顯示:司馬懿 = int.MinValue,
            劉備 =1<<32,//IL反編譯顯示:劉備 = 0x1,
            關羽 =1<<33,//IL反編譯顯示:關羽 = 0x2,
            張飛 =1<<62,//IL反編譯顯示:張飛 = 0x40000000

        }

 


每當可枚舉表示可能值的集合而不是單個值時,應使用[Flags]屬性。此類集合通常與按位運算符一起使用,例如:

var allowedColors = MyColor.Red | MyColor.Green | MyColor.Blue;
 

請注意,[Flags]屬性本身並不啟用此功能,它所做的只是允許.ToString()方法的良好表示:

enum Suits { Spades = 1, Clubs = 2, Diamonds = 4, Hearts = 8 }
[Flags] enum SuitsFlags { Spades = 1, Clubs = 2, Diamonds = 4, Hearts = 8 }

...

var str1 = (Suits.Spades | Suits.Diamonds).ToString();
           //"5"
var str2 = (SuitsFlags.Spades | SuitsFlags.Diamonds).ToString();
           //"Spades, Diamonds"
 

還需要注意的是,[Flags]不會自動使枚舉值的冪為2。如果省略數字值,枚舉將不會像在按位操作中預期的那樣工作,因為默認情況下,值以0和增量開頭。

          Console.WriteLine(default(Man)); //枚舉的默認值都是0

 

聲明不正確:

[Flags]
public enum MyColors
{
    Yellow,  // 0
    Green,   // 1
    Red,     // 2
    Blue     // 3
}

 

 

如果以這種方式聲明,值將為黃色=0、綠色=1、紅色=2、藍色=3。這將使其作為標志無效。

以下是正確聲明的示例:

[Flags]
public enum MyColors
{
    Yellow = 1,
    Green = 2,
    Red = 4,
    Blue = 8
}

2.3位枚舉使用案例

//1、給用戶創建、讀取,修改和刪除的權限
var parmission = Permission.Create | parmission.Read | parmission.Update | parmission.Delete;

//2、去掉用戶的修改和刪除權限
parmission = parmission &~parmission.Update;
parmission = parmission &~parmission.Delete;

//3、給用戶加上修改的權限
parmission = parmission | parmission.Update;

//4、判斷用戶是否有創建的權限
var isCreate =(myProperties.AllowedColors.HasFlag(MyColor.Yellow);

 

 

或在.NET 4之前:

if((myProperties.AllowedColors & MyColor.Yellow) == MyColor.Yellow)
{
    // Yellow is allowed...
}

if((myProperties.AllowedColors & MyColor.Green) == MyColor.Green)
{
    // Green is allowed...
}

 

3、簡單枚舉和標記枚舉的區別?

using System; class Example { // Define an Enum without FlagsAttribute. enum SingleHue : short { None = 0, Black = 1, Red = 2, Green = 4, Blue = 8 }; // Define an Enum with FlagsAttribute.  [Flags] enum MultiHue : short { None = 0, Black = 1, Red = 2, Green = 4, Blue = 8 }; static void Main( ) { // Display all possible combinations of values.  Console.WriteLine( "All possible combinations of values without FlagsAttribute:"); for(int val = 0; val <= 16; val++ ) Console.WriteLine( "{0,3} - {1:G}", val, (SingleHue)val); // Display all combinations of values, and invalid values.  Console.WriteLine( "\nAll possible combinations of values with FlagsAttribute:"); for( int val = 0; val <= 16; val++ ) Console.WriteLine( "{0,3} - {1:G}", val, (MultiHue)val); } } // The example displays the following output: // All possible combinations of values without FlagsAttribute: // 0 - None // 1 - Black // 2 - Red // 3 - 3 // 4 - Green // 5 - 5 // 6 - 6 // 7 - 7 // 8 - Blue // 9 - 9 // 10 - 10 // 11 - 11 // 12 - 12 // 13 - 13 // 14 - 14 // 15 - 15 // 16 - 16 // // All possible combinations of values with FlagsAttribute: // 0 - None // 1 - Black // 2 - Red // 3 - Black, Red // 4 - Green // 5 - Black, Green // 6 - Red, Green // 7 - Black, Red, Green // 8 - Blue // 9 - Black, Blue // 10 - Red, Blue // 11 - Black, Red, Blue // 12 - Green, Blue // 13 - Black, Green, Blue // 14 - Red, Green, Blue // 15 - Black, Red, Green, Blue // 16 - 16

前面的示例定義了兩個與顏色相關的枚舉: SingleHueMultiHue后者具有 FlagsAttribute 屬性; 前者不具有屬性。 此示例顯示了在一個整數范圍(包括不表示枚舉類型的基礎值的整數)轉換為枚舉類型及其顯示的字符串表示形式時的行為差異。 例如,請注意,3不能表示為 SingleHue 值,因為3不是任何成員的基礎值 SingleHue ,而特性使你可以將 FlagsAttribute 3 表示為 MultiHue 的值 Black, Red

 遍歷Enum

foreach (var f in Enum.GetValues(typeof(FileSystemRights)))
{
    Console.WriteLine(f.ToString().PadLeft(28) + Convert.ToString((int)f, 2).PadLeft(32, '0'));
}

 

 

接下來是權限的運算:

  1. 權限的加法, 使用與運算來實現。

  0001 | 0100 = 0101, 表示同時具有第一位和第三位的權限管理了, 枚舉表示為:

1 Permissions per = Permissions.Insert | Permissions.Update

  2. 權限的減法, 使用與運算+非運算來實現。

  如上面要去掉Insert權限, 操作為:

1 Permissions per &= ~Permissions.Insert 2 // 即是 3 0101 & ~0001 = 0101 & 1110 = 0100

   3. 權限的判斷, 使用與運算。

  判斷是否有操作權限時, 把用戶的的權限與操作權限進行與運算, 如果得到的結果仍是操作權限管理, 則表示用戶具有該權限:

1   Permissions per = Permissions.Insert | Permissions.Update; 2   if((per & Permissions.Insert)== Permissions.Insert) 3   { 4   //有操作權限 5   }

   比較過程為 0101 & 0001 = 0001, 0001的0位用與C#位運算把其它位都置成0, 變成只比較1的這一位.


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM