c# 二進制或算法實現枚舉的HasFlag函數


在權限的管理中,常常會出現一個權限包含的現象。例如,有三種基本權限:職員A、職員B、職員C.在此基礎上,有經理權限,它包括A和B兩種權限;還有老板權限,包含A/B/C三種權限。

在代碼中,我們可以用枚舉來管理這些權限。

[Flags]
public enum EnumHasFlag
{
    A = 1 << 0,
    B = 1 << 1,
    C = 1 << 2,
    Manager = A | B,
    Boss = A | B | C,
}

這段代碼的特點是,定義枚舉是用了一個屬性來限制[Flags],以及每個值都是用二進制遞增來賦值。這樣做的好處是,可以通過枚舉的HasFlag函數來判斷某一個權限是否包含另一個權限。

static void Main(string[] args)
{
    var rightA = EnumHasFlag.Boss;
    var rightB = EnumHasFlag.Manager;
    if (rightA.HasFlag(EnumHasFlag.C)) Console.WriteLine("rightA can do this");
    if (rightB.HasFlag(EnumHasFlag.C)) Console.WriteLine("rightB can do this");
    Console.ReadKey();
}

最終代碼會輸出:rightA can do this。這樣,通過HasFlag就可以判斷枚舉值的包含關系,從而進行相應的權限指定和管理。

這樣的效果,還可以用過二進制的或運算來實現。基本語句是source | target == source.某個數值A,與另一個數值B進行或運算之后的結果還是A的話,可以判斷A包含B。

static void Main(string[] args)
{
    var A = 1 << 0 | 1 << 1;
    if ((A | (1 << 0)) == A) Console.WriteLine("A has 1<<0");
    if ((A | (1 << 2)) != A) Console.WriteLine("A doesn't have 1<<2");
}

代碼的輸出結果為:

A has 1<<0  

A doesn't have 1<<2

在了解邏輯的前提下,我們可以做如下的開關:

static void Main(string[] args)
{
    ControlCenter(1 << 0 | 1 << 3);
}

static void ControlCenter(int input)
{
    if ((input | (1 << 0)) == input) Console.WriteLine("Do 0");
    if ((input | (1 << 1)) == input) Console.WriteLine("Do 1");
    if ((input | (1 << 2)) == input) Console.WriteLine("Do 2");
}

最終的輸出結果可以自己下去測試一下。

本篇用兩種方法來實現數值的包含關系管理。仔細的理解了實現的邏輯之后,可以用在很多地方。例如,我們可以將多個設置的是否值揉合成一個字段。形如'10111101',用最少的代碼來管理這些設置信息。在選項很少而且對象的活動領域很小的情況下,可以考慮用二進制的或運算來實現。這樣實現的優點是,可以不用單獨建立枚舉,代碼量少很多;缺點是,代碼的可讀性差,調用靈活度也不如枚舉的HasFlag,可擴展性也不強。

補充

下面有園友提出了source & target == target的判斷算法,來判斷source是否包含target。我覺得條件非常充分,而且整個思路比或運算更加清晰。后來查閱了其他的資料,發現對枚舉中的1,2,4,8的理解,很多都是從這個算式出發。

static void Main(string[] args)
{
    var xx = TestEnum.Manager;
    if ((xx & TestEnum.A) == TestEnum.A) Console.WriteLine("Has A");
    Console.ReadKey();
}

enum TestEnum
{
    A = 1 << 0,
    B = 1 << 1,
    C = 1 << 2,
    Manager = A | B,
    Boss = A | B | C,
}

 

15.6.6補充:

用linq to ef做hasflag查詢時,做了一個測試,結果很有趣:

var f1 = db.Where(x => !x.Status.HasFlag(FlagEnum.Deleted));
var f2 = db.Where(x => ((int)x.Status & (int)FlagEnum.Deleted) != (int)FlagEnum.Deleted);
var f3 = db.Where(x => x.Status.HasFlag(FlagEnum.Deleted));
var f4 = db.Where(x => ((int)x.Status & (int)FlagEnum.Deleted) == (int)FlagEnum.Deleted);

對應的sql語句的where條件是:

f1

WHERE  NOT (((( CAST( [Extent1].[Status] AS int)) & ( CAST( 4 AS int))) =  CAST( 4 AS int)) AND ((CASE WHEN (( CAST( [Extent1].[Status] AS int)) & ( CAST( 4 AS int)) IS NULL) THEN cast(1 as bit) ELSE cast(0 as bit) END) = 0))

f2

WHERE  NOT ((4 = (( CAST( [Extent1].[Status] AS int)) & (4))) AND (( CAST( [Extent1].[Status] AS int)) & (4) IS NOT NULL))

f3

WHERE (( CAST( [Extent1].[Status] AS int)) & ( CAST( 4 AS int))) =  CAST( 4 AS int)

f4

WHERE 4 = (( CAST( [Extent1].[Status] AS int)) & (4))}

直接說明了HasFlag和source & target = target的關系。

 

歡迎糾正和補充。轉載請注明出處:http://www.cnblogs.com/icyJ/


免責聲明!

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



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