C#执行switch语句中的所有case

5

我有一个如下的switch语句:

switch(task)
    {
        case 1:
            print();
            break;
        case 2:
            save();
            break;
        case 3:
            sendmail();
            break;
    }

我需要一种执行所有任务的方法,这意味着如果任务是(All),我想要打印、保存和发送邮件。是否可以通过对给定的情况进行一些修改来实现?我知道,我可以按以下方式进行:

case All:
    print();
    save();
    sendmail();
    break;

但是,正如我所说,我想知道在switch语句中是否有一种方法可以执行所有的case。

提前感谢您的帮助。


1
也许你应该考虑使用标志枚举并创建一个 Dictionary<enum, Action>。您可以使用 foreach 循环遍历所有枚举值,检查是否有条目可用并执行方法。因此,在这种情况下不需要使用 switch。 - Sebastian Schumann
我不打算将此作为答案发布。但是,当您向这些条件的流程添加复杂性时 - 它将变得不可读。只需将其拆分并进行多次调用print(),例如。 - user1017882
6个回答

9

回答您的实际问题:

是否有一种在 switch 语句中执行所有 case 的方法。

我想不到一种简洁的方法。


我建议稍微改变模式,将 task 改为标志枚举

[Flags]
public enum TaskOptions 
{
    Print    = 1,
    Save     = 2,
    SendMail = 4,
    //Note that these numbers go up in powers of two
}

您可以像下面这样做:
然后,您可以执行以下操作:
task = TaskOptions.Print | TaskOptions.Save;
if (task.HasFlag(TaskOptions.Print))
{ 
    print();
}
if (task.HasFlag(TaskOptions.Save))
{ 
    save();
}
if (task.HasFlag(TaskOptions.SendMail))
{ 
    sendMail();
}

您不再需要明确担心all


然后,如果您想要添加一个新选项

[Flags]
public enum TaskOptions 
{
    Print    = 1,
    Save     = 2,
    SendMail = 4,
    NewOption = 8,
}

task = TaskOptions.Print | TaskOptions.Save;
if (task.HasFlag(TaskOptions.Print))
{ 
    print();
}
if (task.HasFlag(TaskOptions.Save))
{ 
    save();
}
if (task.HasFlag(TaskOptions.SendMail))
{ 
    sendMail();
}
if (task.HasFlag(TaskOptions.NewOption))
{ 
    newOption();
}

Jaymee询问了有关|和&的澄清

请解释task = TaskOptions.Print | TaskOptions.Save的语法。对于这里的管道,我认为它是“或”,但这里没有评估!实际上,单个和号也是如此-以前从未以这种方式使用过

这些是按位运算符。它们逐位比较两个数字,并返回结果。

在我们的示例中,我们使用了4个标志,每个标志由布尔值表示。布尔值可以用1个bit表示。

让我们使用以下缩写:

Print = P
Save = S
SendMail = M
NewOption = N

8  4  2  1
N  M  S  P

我以task = TaskOptions.Print | TaskOptions.Save为例。它是一个用于定义任务选项的标志枚举。
0  0  0  1   P is declared as 1 in the enum. 
0  0  1  0   S is declared as 2 in the enum.
========== 
0  0  1  1   < I've "or'd" these numbers together. They now represent Print AND Save as one option. The number "3" (binary 0011) is equivalent to "print and save"

当我有数字"3",想要知道它是否含有特定标记时,需要使用与该标记进行&运算。

N  M  S  P
0  0  1  1  //This is P & S
0  0  0  1  //And we want to check if it has "P"
==========
0  0  0  1  < this returns non-zero. it contains the flag!

让我们对 N 做同样的操作

N  M  S  P
0  0  1  1  //This is P & S
1  0  0  0  //And we want to check if it has "N"
==========
0  0  0  0  < this returns zero. This state doesn't contain "N"

由David Arno编辑

补充一下,可以使用字典和for循环代替一系列的if语句:

private readonly Dictionary<TaskOptions, Action> actions =
    new Dictionary<TaskOptions, Action>
    {
        { TaskOptions.Print, Print },
        { TaskOptions.Save, Save },
        { TaskOptions.SendMail , SendMail }
    };

...

var task = TaskOptions.Print | TaskOptions.Save;
foreach (var enumValue in Enum.GetValues(typeof(TaskOptions)).Cast<TaskOptions>())
{
    if (task.HasFlag(enumValue))
    {
        actions[enumValue]();
    }
}

3
task.HasFlag(TaskOptions.NewOption) - Backs
非常棒的解释。非常感谢,点赞。 - user1017882
1
你可以通过使用Dictionary<TaskOptions, Action>来替换一系列的if语句来改进这个答案。除此之外,这是一个很好的答案。 - David Arno
1
你可以将所有任务连接在一起,例如 task = TaskOptions.Print | TaskOptions.Save | TaskOptions.SendMail,或者使用数学方法:task = 2^(numofTasks)-1。在这种情况下,所有的“位”都是1,对应于所有选项。 - Sven
但不幸的是,使用switch无法实现这一点,这是我从所有答案和评论中得出的结论。 :) - RMohammed
显示剩余11条评论

1

尝试添加一个具有特殊行为的单独类,因为这比在 switch 语句中进行修改更清晰:

class FallSwitch 
{
     SortedDictionary<int, Action> _cases = new SortedDictionary<int, Action>();

    public void AddCaseAction(int @case, Action action)
    {
        if (_cases.ContainsKey(@case)) throw ArgumentException("case already exists");
        _cases.Add(@case, action);
    }

    public void Execute(int startCase)
    {
        if (!_cases.ContainsKey(startCase)) throw ArgumentException("case doesn't exist");
        var tasks = _cases.Where(pair => pair.Key >= startCase);
        tasks.ForEach(pair => pair.Value());
    }
}

并像这样使用它:
var fs = new FallSwitch();
fs.AddCaseAction(0, () => Console.WriteLine("0"));
fs.AddCaseAction(1, () => Console.WriteLine("1"));
fs.AddCaseAction(2, () => Console.WriteLine("2"));
fs.AddCaseAction(6, () => Console.WriteLine("6"));

fs.Execute(1);

1

根据要求,将评论作为答案。

也许你应该考虑使用标志枚举并创建一个字典。您可以使用foreach循环遍历所有枚举值,并检查是否有条目可用并执行方法。因此,在这种情况下需要使用switch。

[Flags]
public enum TaskOptions 
{
    Print    = 1,
    Save     = 2,
    SendMail = 4
}

// In the clase where print(), save() and sendmail() are declared
private readonly Dictionary<TaskOptions, Action> _tasks;

public Ctor()
{
    _tasks = new Dictionary<TaskOptions, Action>
        {
            { TaskOptions.Print, this.print },
            { TaskOptions.Save, this.save },
            { TaskOptions.SendMail, this.sendmail }
        };
}

public void Do(TaskOptions tasksToRun)
{
    foreach (var action in from taskOption in Enum.GetValues(typeof(TaskOptions)).Cast<TaskOptions>()
                           where tasksToRun.HasFlag(taskOption)
                           orderby taskOption // only to specify it in the declared order
                           select this._tasks[taskOption])
    {
        action();
    }
}

但是你已经得到了一个使用干净的策略模式完成相同工作的答案。这个方法与之前的方法类似,只是使用委托代替类。

好的回答。不过有一点小问题:我认为另一个答案使用了GoF风格的命令模式,而不是策略模式。我也会对“干净”提出异议,因为委托是这两种模式更清晰的实现方式。 - David Arno

0

正如您所知,在C#中,不会隐式发生“fall-through”,而是通过扩展goto以允许将case标签作为目标标签,从而使“fall-through”变得明确。

只需使您的“all”值与第一个标签匹配,然后继续goto下一个值:

switch(task)
{
  case 1: case 0;//I don't know what "all" is, so I'm using 0 here
    print();
    if (task == 0) goto case 2;
    break;
  case 2:
    save();
    if (task == 0) goto case 3;
    break;
  case 3:
    sendmail();
    break;
}

1
@modiX在语言中从不使用“fall-through”的标准习惯用语?为什么?(请告诉我你不是基于Dijkstra在1968年写的某些东西。这个特定的四个字母的使用在1968年并不存在)。 - Jon Hanna
2
@JonHanna - Dijkstra 所说的有效性仍然存在。使用 goto 控制流程是可怕的。 - user1017882
啊,我明白了。那确实很复杂。我有25年的编程经验,但却没有注意到你将两个case语句放在同一行上可能会导致在第二种情况下task为0这个细微之处。 - jaket
3
在我看来,那是一个可怕的语言特性。 - jaket
我完全同意你的观点。事实上,鉴于你“确实”回答了问题,我还点了个赞。同时,针对这种类型的问题,我通常会寻找不同的解决方案。 - jaket
显示剩余8条评论

0
你可以尝试更面向对象的方法。定义一组类,每个类负责执行一种类型的任务。当您想运行多个任务时,只需将单个任务排队即可。使用switch语句非常不灵活。想象一下,有一天您添加了第四个任务,然后希望按顺序运行任务1、2和4。
 RunTasks(new[] {new PrintTask(), new SaveTask(), new SendMailTask()});

 void RunTasks(int[] tasks)
 {
     foreach (var task in tasks)
     {
         task.Run();
     }
 }

 interface ITask
 {
     void Run();
 }

 class PrintTask : ITask
 {
      public void Run()
      {
          ....
      }
  }

  // etc...

你在重新发明命令模式的轮子。C#已经有了“Action”,它更清晰地涵盖了这种模式,而无需创建大量高度重复的类。 - David Arno

0
您可以这样创建一个新的案例。
switch(task)
{
    case 1:
        print();
        break;
    case 2:
        save();
        break;
    case 3:
        sendmail();
        break;
    case 4:
        print();
        save();
        sendmail();
        break;
}

或者你可以将默认情况设置为执行所有的情况

 switch(task)
{
    case 1:
        print();
        break;
    case 2:
        save();
        break;
    case 3:
        sendmail();
        break;
    default:
        print();
        save();
        sendmail();
        break;
}

这两种方法都不是很优雅,但我认为它们可能是解决你问题最简单的方式。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接