[英]Switch on Enum (with Flags attribute) without declaring every possible combination?
我如何打开一个设置了 flags 属性的枚举(或者更准确地说是用于位操作)?
我希望能够在与声明的值相匹配的开关中命中所有情况。
问题是如果我有以下枚举
[Flags()]public enum CheckType
{
Form = 1,
QueryString = 2,
TempData = 4,
}
我想使用这样的开关
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;
}
如果“theCheckType”设置为两个 CheckType.Form | CheckType.TempData 我希望它符合两种情况。 显然,由于中断,它不会在我的示例中同时出现,但除此之外,它也失败了,因为 CheckType.Form 不等于 CheckType.Form | 检查类型.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 条件
就像是
if ((_CheckType & CheckType.Form) != 0)
{
DoSomething(/*Some type of collection is passed */);
}
if ((_CheckType & CheckType.TempData) != 0)
{
DoWhatever(/*Some type of collection is passed */);
}
....
但这也意味着,如果我有一个包含 20 个值的枚举,它必须每次都通过 go 到 20 个 If 条件,而不是像使用开关时那样“跳转”到仅需要的“案例”。
有什么神奇的方法可以解决这个问题吗?
我想到了循环声明的值然后使用开关的可能性,然后它只会为每个声明的值点击开关,但我不知道它是如何工作的,如果它的性能副是一个好主意(与很多 if 相比)?
有没有一种简单的方法可以遍历声明的所有枚举值?
我只能想出使用 ToString() 并按“,”拆分,然后遍历数组并解析每个字符串。
更新:
我发现我解释得不够好。 我的例子很简单(试图简化我的场景)。
我将它用于 Asp.net MVC 中的 ActionMethodSelectorAttribute 以确定在解析 url/route 时方法是否可用。
我通过在方法上声明这样的东西来做到这一点
[ActionSelectorKeyCondition(CheckType.Form | CheckType.TempData, "SomeKey")]
public ActionResult Index()
{
return View();
}
这意味着它应该检查 Form 或 TempData 是否具有为可用方法指定的键。
它将调用的方法(在我之前的示例中为 doSomething()、doSomethingElse() 和 doWhatever())实际上将 bool 作为返回值,并将使用一个参数(不同的 collections,它不共享一个可以被调用的接口)使用 - 在下面的链接中查看我的示例代码等)。
为了希望更好地了解我在做什么,我在 pastebin 上粘贴了一个简单的示例,说明我实际在做什么 - 它可以在这里找到http://pastebin.com/m478cc2b8
这个怎么样。 当然,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;
}
}
}
}
}
只需使用HasFlag
if(theCheckType.HasFlag(CheckType.Form)) DoSomething(...);
if(theCheckType.HasFlag(CheckType.QueryString)) DoSomethingElse(...);
if(theCheckType.HasFlag(CheckType.TempData)) DoWhatever(...);
标志枚举可以被视为一种简单的整数类型,其中每个单独的位对应于一个标志值。 您可以利用此属性将带位标记的枚举值转换为布尔数组,然后从相关的委托数组中分派您关心的方法。
编辑:我们当然可以通过使用 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 flagMask = 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
}
在 C# 7 中应该是可能的
switch (t1)
{
case var t when t.HasFlag(TST.M1):
{
break;
}
case var t when t.HasFlag(TST.M2):
{
break;
}
使用 C# 7,您现在可以编写如下内容:
public void Run(CheckType checkType)
{
switch (checkType)
{
case var type when CheckType.Form == (type & CheckType.Form):
DoSomething(/*Some type of collection is passed */);
break;
case var type when CheckType.QueryString == (type & CheckType.QueryString):
DoSomethingElse(/*Some other type of collection is passed */);
break;
case var type when CheckType.TempData == (type & CheckType.TempData):
DoWhatever(/*Some different type of collection is passed */);
break;
}
}
您将填写的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]();
}
(代码未经测试)
将其转换为其基本类型,这样做的好处是它会告诉您何时存在重复值。
[Flags]
public enum BuildingBlocks_Property_Reflection_Filters
{
None=0,
Default=2,
BackingField=4,
StringAssignment=8,
Base=16,
External=32,
List=64,
Local=128,
}
switch ((int)incomingFilter)
{
case (int)PropFilter.Default:
break;
case (int)PropFilter.BackingField:
break;
case (int)PropFilter.StringAssignment:
break;
case (int)PropFilter.Base:
break;
case (int)PropFilter.External:
break;
case (int)PropFilter.List:
break;
case (int)PropFilter.Local:
break;
case (int)(PropFilter.Local | PropFilter.Default):
break;
}
根据您的编辑和实际代码,我可能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;
}
我知道这已经解决了,这里有几个很好的答案,但我真的想要 OP 所要求的同样的东西。
有没有一种简单的方法可以遍历所有声明的枚举值?
当然,Jamie 接受的解决方案做得很好,还有一些其他有趣的答案,但我真的想得到一些更通用和更自然的东西,所以这就是我想出的:
public static TFlags[] Split<TFlags>(this TFlags flags) // , bool keepUndefined)
where TFlags : Enum
{
ulong valueToSplit = Convert.ToUInt64(flags);
TFlags[] definedFlags = (TFlags[])Enum.GetValues(typeof(TFlags));
List<TFlags> resultList = new List<TFlags>();
if (valueToSplit == 0)
{
TFlags flagItem =
Array.Find(definedFlags, flag => Convert.ToUInt64(flag) == 0x00);
resultList.Add(flagItem);
}
ulong checkBitValue = 1L;
while (valueToSplit != 0)
{
ulong checkValue = checkBitValue & valueToSplit;
if (checkValue != 0)
{
valueToSplit &= ~checkValue;
TFlags flagItem =
Array.Find(definedFlags, flag => Convert.ToUInt64(flag) == checkValue);
// undefined flag, need a way to convert integer to TFlags
if (Convert.ToUInt64(flagItem) != checkValue)
{
//if (keepUndefined)
// resultList.Add(checkValue);
continue;
}
resultList.Add(flagItem);
}
checkBitValue <<= 1;
}
return resultList.ToArray();
}
(这段代码已经过少量测试,到目前为止对我来说效果很好。)
我从来没有找到强制强制转换任意整数值的方法,甚至检查并匹配底层类型。 它总是给我一个将整数转换为 TFlags 的错误。
我假设这与它是泛型类型参数而不是真实类型有关。 我觉得可能有一些明显的东西,当我找到它时我会踢自己。
一个可能的解决方案是让调用者提供一个函数来为你转换它; 但这似乎与试图保持其通用性和可用性背道而驰。 您可以将其作为默认的 null 参数放入,如果已传入,则表明您要保留未定义的标志。
幸运的是,反过来也可以,我认为你可以从几乎任何整数类型转换为枚举,否则这会更难。 这样我就可以只使用 ulong 而不必担心必须获取底层类型并执行一些大的 switch 语句来获取适当大小的整数。
所以,这个想法就是这样你就可以在你需要采取行动的标志值上执行一个 Split() ,它会为你提供一个包含所有定义的数组,这样你就可以像往常一样循环并进行切换。 LBushkin 提到了排序,除了这些按标志值升序排列之外,这里没有尝试这样做。
我实际上认为帖子中的原始代码在概念上并没有那么糟糕。 似乎有人厌恶在接受的答案中执行类似的操作,在该答案中循环所有可能的标志并进行切换。 但是,在大多数情况下,对于未使用的标志,循环和开关应该非常快,因此除非它在紧密循环中运行,否则对于大多数情况来说可能没问题。
但我确实喜欢让你可以做一些更自然的事情的想法。
public DoTheChecks(CheckType typesToCheck)
{
CheckType[] allCheckTypes = typesToCheck.Split();
foreach (CheckType singleCheck in allCheckTypes)
switch (singleCheck)
{
case CheckType.Form:
CheckTheForms();
break;
等等。
LBushkin 的帖子也正在通过位图找出已设置的标志,其中有一条评论询问是否考虑了 None = 0 标志。 我添加了一个检查,以便如果标志为零,我们检查默认标志并在它存在时添加它。
此代码不检查枚举上的 Flags() 属性,因此可以传入一个可能不会产生预期结果的非标志枚举。 可能值得检查并抛出异常。 我看不出有任何理由为非标志枚举实现这样的东西,但是我所知道的语言并没有真正提供太多的方法来处理它。 如果有人这样做,最糟糕的事情就是他们会拿回垃圾。
除了能够设置任意未定义的标志值之外,这几乎可以满足我的所有需求。 不过,我花了很长时间才弄清楚如何做所有事情。 你可以看出他们从来没有打算让你像这样使用枚举。
我喜欢 LBushkin 的方法,但没有奏效。
1 << 31
的计算结果为负数,因此 while 循环将是无限的。
我不喜欢flagMask != 0
,我更喜欢在 for 循环中使用flagMask >= 1
。
using System;
public class Program
{
[Flags]
public enum test {
a = 1 << 0, // 1
b = 1 << 1, // 2
c = 1 << 2, // 4
d = 1 << 3, // 8
e = 1 << 4, // 16
f = 1 << 5, // 32
g = 1 << 6, // 64
h = 1 << 7 // 128
}
public static void Main()
{
debug(test.a | test.b | test.h);
}
public static void debug(test t) {
Console.WriteLine((int)t);
for(var j = 1 << 30; j >= 1; j >>= 1) {
var k = (test)((int)t & j);
if((int)k != 0)
Console.WriteLine(k);
}
}
}
最简单的方法是执行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;
}
更干净,您不需要使用带有HasFlag
的if
语句。 您可以进行任何您想要的组合,然后使 switch 语句易于阅读。
我建议拆分你的enums
,尝试看看你是否没有将不同的东西混合到同一个enum
中。 您可以设置多个enums
以减少案例数量。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.