2009-06-24 51 views
39

我該如何打開一個枚舉,它具有flags屬性集(或者更確切地說是用於位操作)?打開枚舉(帶標誌屬性),但未聲明每種可能的組合?

我希望能夠在匹配聲明值的開關中擊中所有情況。

的問題是,如果我有以下枚舉

[Flags()]public enum CheckType 
{ 
    Form = 1, 
    QueryString = 2, 
    TempData = 4, 
} 

,我想如果「theCheckType」設置爲兩個CheckType.Form使用開關這樣

switch(theCheckType) 
{ 
    case CheckType.Form: 
     DoSomething(/*Some type of collection is passed */); 
     break; 

    case CheckType.QueryString: 
     DoSomethingElse(/*Some other type of collection is passed */); 
     break; 

    case CheckType.TempData 
     DoWhatever(/*Some different type of collection is passed */); 
     break; 
} 

| CheckType.TempData我希望它能打兩種情況。很明顯,它不會在我的例子中因爲中斷而觸及,但除此之外,它也會失敗,因爲CheckType.Form不等於CheckType.Form | CheckType.TempData

唯一的解決方案,然後我可以看到它是爲每個可能的組合枚舉值做一個情況?

喜歡的東西

case CheckType.Form | CheckType.TempData: 
     DoSomething(/*Some type of collection is passed */); 
     DoWhatever(/*Some different type of collection is passed */); 
     break; 

    case CheckType.Form | CheckType.TempData | CheckType.QueryString: 
     DoSomething(/*Some type of collection is passed */); 
     DoSomethingElse(/*Some other type of collection is passed */); 
     break; 

... and so on... 

但是,真正地不是非常需要的(因爲它會迅速變得非常大)

現在我有3如果海誓山盟,而不是

喜歡的東西

條件右後
if ((_CheckType & CheckType.Form) != 0) 
{ 
    DoSomething(/*Some type of collection is passed */); 
} 

if ((_CheckType & CheckType.TempData) != 0) 
{ 
    DoWhatever(/*Some type of collection is passed */); 
} 

.... 

但這也意味着,如果我有一個枚舉wi它必須經歷20個值如果每一次都滿足條件,而不是像使用開關那樣「跳躍」到僅僅需要的「案例」。

是否有一些神奇的解決方案來解決這個問題?

我想過可能性循環聲明的值,然後使用開關,然後它只會打開每個值聲明的開關,但我不知道它將如何工作,如果它的性能副是一個好主意(與很多if相比)?

是否有一種簡單的方法來遍歷所有聲明的枚舉值?

我只能想出使用ToString()和「」分割,然後遍歷數組並解析每一個字符串。


UPDATE:

我看到,我沒有做工作不夠好解釋。 我的例子是簡單的(試圖簡化我的方案)。

我將它用於Asp.net MVC中的ActionMethodSelectorAttribute,以確定解析url/route時是否應該有可用的方法。

我通過在方法

[ActionSelectorKeyCondition(CheckType.Form | CheckType.TempData, "SomeKey")] 
public ActionResult Index() 
{ 
    return View(); 
} 

這將意味着它應該檢查是否按照規定的方法可用於窗體或TempData的有一個關鍵的聲明是這樣做的。在我之前的例子中doSomething(),doSomethingElse()和doWhatever())方法實際上會有bool作爲返回值,並且會被一個參數調用(不同的集合不共享一個可以使用的接口 - 請參閱下面鏈接中的示例代碼等)。

爲了希望給的我在做什麼我都粘貼的什麼,我實際上做對引擎收錄一個簡單的例子,一個更好的主意 - 它可以在這裏找到http://pastebin.com/m478cc2b8

回答

39

這個怎麼樣。當然,DoSomething等的參數和返回類型可以是任何你喜歡的。

class Program 
{ 
    [Flags] 
    public enum CheckType 
    { 
     Form = 1, 
     QueryString = 2, 
     TempData = 4, 
    } 

    private static bool DoSomething(IEnumerable cln) 
    { 
     Console.WriteLine("DoSomething"); 
     return true; 
    } 

    private static bool DoSomethingElse(IEnumerable cln) 
    { 
     Console.WriteLine("DoSomethingElse"); 
     return true; 
    } 

    private static bool DoWhatever(IEnumerable cln) 
    { 
     Console.WriteLine("DoWhatever"); 
     return true; 
    } 

    static void Main(string[] args) 
    { 
     var theCheckType = CheckType.QueryString | CheckType.TempData; 
     var checkTypeValues = Enum.GetValues(typeof(CheckType)); 
     foreach (CheckType value in checkTypeValues) 
     { 
      if ((theCheckType & value) == value) 
      { 
       switch (value) 
       { 
        case CheckType.Form: 
         DoSomething(null); 
         break; 
        case CheckType.QueryString: 
         DoSomethingElse(null); 
         break; 
        case CheckType.TempData: 
         DoWhatever(null); 
         break; 
       } 
      } 
     } 
    } 
} 
4

怎麼樣一個Dictionary<CheckType,Action>,你將填補像

dict.Add(CheckType.Form, DoSomething); 
dict.Add(CheckType.TempDate, DoSomethingElse); 
... 

你的價值的分解

flags = Enum.GetValues(typeof(CheckType)).Where(e => (value & (CheckType)e) == (CheckType)e).Cast<CheckType>(); 

然後

foreach (var flag in flags) 
{ 
    if (dict.ContainsKey(flag)) dict[flag](); 
} 

(代碼未測試)

+0

感謝您的回答。 我已更新我的原始帖子。 我不認爲有可能使用字典,因爲我在一個屬性中聲明它(參見示例) - 或者至少我不知道它應該如何完成。 – MartinF 2009-06-24 22:33:39

+0

對於MartinF的問題可能不夠充分,但非常優雅... +1;) – 2009-07-06 23:59:09

+0

Enum(...)。GetValues返回一個Array;你不能使用'Where。 – BillW 2016-05-05 04:10:08

13

標誌枚舉可以被視爲其中每個單獨比特對應於所標記的值中的一個的簡單整數類型。您可以利用此屬性將位標記的枚舉值轉換爲布爾值數組,然後從相關的委託數組派發您關心的方法。

編輯:我們當然可以讓這段代碼通過使用LINQ和一些輔助功能更緊湊,但我覺得它更容易在較複雜的形式來了解。這可能是可維護性勝過優雅的情況。

下面是一個例子:

[Flags()]public enum CheckType 
{ 
    Form = 1,  
    QueryString = 2, 
    TempData = 4, 
} 

void PerformActions(CheckType c) 
{ 
    // array of bits set in the parameter {c} 
    bool[] actionMask = { false, false, false }; 
    // array of delegates to the corresponding actions we can invoke... 
    Action availableActions = { DoSomething, DoSomethingElse, DoAnotherThing }; 

    // disassemble the flags into a array of booleans 
    for(int i = 0; i < actionMask.Length; i++) 
    actionMask[i] = (c & (1 << i)) != 0; 

    // for each set flag, dispatch the corresponding action method 
    for(int actionIndex = 0; actionIndex < actionMask.Length; actionIndex++) 
    { 
     if(actionMask[actionIndex]) 
      availableActions[actionIndex](); // invoke the corresponding action 
    } 
} 

或者,如果你評估沒關係的順序,這裏要說的是作品一樣好簡單,更清晰的解決方案。如果爲了此事確實,與包含標記的順序數組要在評估他們更換比特移位操作:根據您的編輯和您的真實代碼

int flagValue = 1 << 31; // start with high-order bit... 
while(flagMask != 0) // loop terminates once all flags have been compared 
{ 
    // switch on only a single bit... 
    switch(theCheckType & flagMask) 
    { 
    case CheckType.Form: 
    DoSomething(/*Some type of collection is passed */); 
    break; 

    case CheckType.QueryString: 
    DoSomethingElse(/*Some other type of collection is passed */); 
    break; 

    case CheckType.TempData 
    DoWhatever(/*Some different type of collection is passed */); 
    break; 
    } 

    flagMask >>= 1; // bit-shift the flag value one bit to the right 
} 
+0

非常好的解決方案! – Henri 2009-06-24 21:14:13

1

,我可能會更新IsValidForRequest方法看起來像這樣:

public sealed override bool IsValidForRequest 
    (ControllerContext cc, MethodInfo mi) 
{ 
    _ControllerContext = cc; 

    var map = new Dictionary<CheckType, Func<bool>> 
     { 
      { CheckType.Form,() => CheckForm(cc.HttpContext.Request.Form) }, 
      { CheckType.Parameter, 
       () => CheckParameter(cc.HttpContext.Request.Params) }, 
      { CheckType.TempData,() => CheckTempData(cc.Controller.TempData) }, 
      { CheckType.RouteData,() => CheckRouteData(cc.RouteData.Values) } 
     }; 

    foreach (var item in map) 
    { 
     if ((item.Key & _CheckType) == item.Key) 
     { 
      if (item.Value()) 
      { 
       return true; 
      } 
     } 
    } 
    return false; 
} 
1

只需使用HasFlag

if(theCheckType.HasFlag(CheckType.Form)) DoSomething(...); 
if(theCheckType.HasFlag(CheckType.QueryString)) DoSomethingElse(...); 
if(theCheckType.HasFlag(CheckType.TempData)) DoWhatever(...); 
0

最簡單的方法是隻執行ORed枚舉,你的情況,你可以做到以下幾點:

[Flags()]public enum CheckType 
{ 
    Form = 1, 
    QueryString = 2, 
    TempData = 4, 
    FormQueryString = Form | QueryString, 
    QueryStringTempData = QueryString | TempData, 
    All = FormQueryString | TempData 
} 

一旦你的enum設置其現在很容易執行您的switch聲明。

例如,如果我有如下設置:

var chkType = CheckType.Form | CheckType.QueryString; 

我可以使用下面的switch聲明如下:

switch(chkType){ 
case CheckType.Form: 
    // Have Form 
break; 
case CheckType.QueryString: 
    // Have QueryString 
break; 
case CheckType.TempData: 
    // Have TempData 
break; 
case CheckType.FormQueryString: 
    // Have both Form and QueryString 
break; 
case CheckType.QueryStringTempData: 
    // Have both QueryString and TempData 
break; 
case CheckType.All: 
    // All bit options are set 
break; 
} 

乾淨多了,你不需要使用if聲明與HasFlag。你可以做任何你想要的組合,然後使開關語句易於閱讀。

我會建議分崩離析你enums,試着看看你是不是不同的東西混合到同enum。您可以設置多個enums以減少個案數量。