2009-06-24 47 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以减少个案数量。