[C#] 用一种更优美的方式来替换掉又多又长的switch-case代码段

switch-case语句是我们编码过程中常用的一种分支语句。然而正所谓成也萧何败萧何,每当我们向一个已经拥有了成百上千行的switch-case代码段中添加新的case分支的时候,我们是否有过为代码的可读性和可维护性不断下降而头疼烦恼呢。

事实上,我们可以有很多方法来避免出现这种分支有多又长的switch-case代码段,从而写出更优美的代码。在.Net中我们可以非常简单地分解switch-case中的代码。


 

下面选择了一个比较常见的例子:模块采用switch-case来处理接收到的Command

假设我们现在定义了以下10个CommandID。

 1     /// <summary>
 2     /// Definition of commands.
 3     /// </summary>
 4     enum CommandID
 5     {
 6         Abs = 1,
 7         Sin = 2,
 8         Sinh = 3,
 9         Asin = 4,
10         Tan = 5,
11         Tanh = 6,
12         Atan = 7,
13         Cos = 8,
14         Cosh = 9,
15         Acos = 10
16     }
CommandID

下面我们定义了CommandHandler1类来处理这些命令。该类采用switch-case语句分别处理不同的CommandID。我们可以将每个CommandID的处理逻辑封装在各个函数中(这里偷懒,假设Math里面定义的几个方法就是我们封装的处理逻辑),然后在每个case中调用相应的函数即可。

 1     class CommandHandler1
 2     {
 3         /// <summary>
 4         /// Handle the command.
 5         /// </summary>
 6         /// <param name="cmdID">The command ID of the command to be handled.</param>
 7         /// <param name="cmdArg">The command argument of the command to be handled.</param>
 8         /// <returns>The handle result.</returns>
 9         public double HandleCommand(CommandID cmdID, double cmdArg)
10         {
11             double retValue;
12             switch (cmdID)
13             {
14                 case CommandID.Abs:
15                     retValue = Math.Abs(cmdArg);
16                     break;
17                 case CommandID.Sin:
18                     retValue = Math.Sin(cmdArg);
19                     break;
20                 case CommandID.Sinh:
21                     retValue = Math.Sinh(cmdArg);
22                     break;
23                 case CommandID.Asin:
24                     retValue = Math.Asin(cmdArg);
25                     break;
26                 case CommandID.Tan:
27                     retValue = Math.Tan(cmdArg);
28                     break;
29                 case CommandID.Tanh:
30                     retValue = Math.Tanh(cmdArg);
31                     break;
32                 case CommandID.Atan:
33                     retValue = Math.Atan(cmdArg);
34                     break;
35                 case CommandID.Cos:
36                     retValue = Math.Cos(cmdArg);
37                     break;
38                 case CommandID.Cosh:
39                     retValue = Math.Cosh(cmdArg);
40                     break;
41                 case CommandID.Acos:
42                     retValue = Math.Acos(cmdArg);
43                     break;
44                 default:
45                     retValue = this.HandleDefaultCommand(cmdArg);
46                     break;
47             }
48 
49             return retValue;
50         }
51 
52         /// <summary>
53         /// Handle the default command.
54         /// </summary>
55         /// <param name="cmdArg">The command argument of the default command.</param>
56         /// <returns>The handle result.</returns>
57         private double HandleDefaultCommand(double cmdArg)
58         {
59             return 0;
60         }
61     }
CommandHandler1

在CommandHandler1中,我们如果新增了一个命令,那么就需要增加一个处理新命令的方法,同时修改HandleCommand方法体,在其中添加一个case分支并调用新增的方法。

 

下面利用字典和委托CommandHandler1里面的switch-case代码段。我们新定义了一个类CommandHandler2,将处理每个CommandID的委托方法保存在一个字典表中(cmdHandlers),在HandleCommand方法体中,通过cmdID找到对应的委托方法来处理响应的cmdID。

 1     class CommandHandler2
 2     {
 3         /// <summary>
 4         /// The dictionary contains all the command handlers to handle the commands.
 5         /// </summary>
 6         private Dictionary<CommandID, Func<double, double>> cmdHandlers = new Dictionary<CommandID, Func<double, double>>
 7         {
 8             {CommandID.Abs, Math.Abs}, {CommandID.Sin, Math.Sin}, {CommandID.Sinh, Math.Sinh}, {CommandID.Asin, Math.Asin},
 9             {CommandID.Tan, Math.Tan}, {CommandID.Tanh, Math.Tanh}, {CommandID.Atan, Math.Atan}, {CommandID.Cos, Math.Cos},
10             {CommandID.Cosh, Math.Cosh}, {CommandID.Acos, Math.Acos}
11         };
12 
13         /// <summary>
14         /// Handle the command.
15         /// </summary>
16         /// <param name="cmdID">The command ID of the command to be handled.</param>
17         /// <param name="cmdArg">The command argument of the command to be handled.</param>
18         /// <returns>The handle result.</returns>
19         public double HandleCommand(CommandID cmdID, double cmdArg)
20         {
21             var cmdHandler = this.cmdHandlers.ContainsKey(cmdID) ? this.cmdHandlers[cmdID] : this.HandleDefaultCommand;
22             return cmdHandler(cmdArg);
23         }
24 
25         /// <summary>
26         /// Handle the default command.
27         /// </summary>
28         /// <param name="cmdArg">The command argument of the default command.</param>
29         /// <returns>The handle result.</returns>
30         private double HandleDefaultCommand(double cmdArg)
31         {
32             return 0;
33         }
34     }
CommandHandler2

当我们新增一个命令时,只需要增加一个处理新命令的方法,同时将这个新命令及其对应的委托方法添加到字典表中即可。


 

下面我们来看一下这两种方法的性能。在测试性能时,我们将所有的cmd处理方法全都替换成了HandleDefaultCommand。

  1     class CommandHandlerTest1
  2     {
  3         /// <summary>
  4         /// Handle the command.
  5         /// </summary>
  6         /// <param name="cmdID">The command ID of the command to be handled.</param>
  7         /// <param name="cmdArg">The command argument of the command to be handled.</param>
  8         /// <returns>The handle result.</returns>
  9         public double HandleCommand(CommandID cmdID, double cmdArg)
 10         {
 11             double retValue;
 12             switch (cmdID)
 13             {
 14                 case CommandID.Abs:
 15                     retValue = this.HandleDefaultCommand(cmdArg);
 16                     //retValue = Math.Abs(cmdArg);
 17                     break;
 18                 case CommandID.Sin:
 19                     retValue = this.HandleDefaultCommand(cmdArg);
 20                     //retValue = Math.Sin(cmdArg);
 21                     break;
 22                 case CommandID.Sinh:
 23                     retValue = this.HandleDefaultCommand(cmdArg);
 24                     //retValue = Math.Sinh(cmdArg);
 25                     break;
 26                 case CommandID.Asin:
 27                     retValue = this.HandleDefaultCommand(cmdArg);
 28                     //retValue = Math.Asin(cmdArg);
 29                     break;
 30                 case CommandID.Tan:
 31                     retValue = this.HandleDefaultCommand(cmdArg);
 32                     //retValue = Math.Tan(cmdArg);
 33                     break;
 34                 case CommandID.Tanh:
 35                     retValue = this.HandleDefaultCommand(cmdArg);
 36                     //retValue = Math.Tanh(cmdArg);
 37                     break;
 38                 case CommandID.Atan:
 39                     retValue = this.HandleDefaultCommand(cmdArg);
 40                     //retValue = Math.Atan(cmdArg);
 41                     break;
 42                 case CommandID.Cos:
 43                     retValue = this.HandleDefaultCommand(cmdArg);
 44                     //retValue = Math.Cos(cmdArg);
 45                     break;
 46                 case CommandID.Cosh:
 47                     retValue = this.HandleDefaultCommand(cmdArg);
 48                     //retValue = Math.Cosh(cmdArg);
 49                     break;
 50                 case CommandID.Acos:
 51                     retValue = this.HandleDefaultCommand(cmdArg);
 52                     //retValue = Math.Acos(cmdArg);
 53                     break;
 54                 default:
 55                     retValue = this.HandleDefaultCommand(cmdArg);
 56                     break;
 57             }
 58 
 59             return retValue;
 60         }
 61 
 62         /// <summary>
 63         /// Handle the default command.
 64         /// </summary>
 65         /// <param name="cmdArg">The command argument of the default command.</param>
 66         /// <returns>The handle result.</returns>
 67         private double HandleDefaultCommand(double cmdArg)
 68         {
 69             return 0;
 70         }
 71     }
 72 
 73     class CommandHandlerTest2
 74     {
 75         /// <summary>
 76         /// The dictionary contains all the command handlers to handle the commands.
 77         /// </summary>
 78         //private Dictionary<CommandID, Func<double, double>> cmdHandlers = new Dictionary<CommandID, Func<double, double>>
 79         //{
 80         //    {CommandID.Abs, Math.Abs}, {CommandID.Sin, Math.Sin}, {CommandID.Sinh, Math.Sinh}, {CommandID.Asin, Math.Asin},
 81         //    {CommandID.Tan, Math.Tan}, {CommandID.Tanh, Math.Tanh}, {CommandID.Atan, Math.Atan}, {CommandID.Cos, Math.Cos},
 82         //    {CommandID.Cosh, Math.Cosh}, {CommandID.Acos, Math.Acos}
 83         //};
 84         private Dictionary<CommandID, Func<double, double>> cmdHandlers;
 85 
 86         public CommandHandlerTest2()
 87         {
 88             cmdHandlers = new Dictionary<CommandID, Func<double, double>>
 89             {
 90                 {CommandID.Abs, this.HandleDefaultCommand}, {CommandID.Sin, this.HandleDefaultCommand},
 91                 {CommandID.Sinh, this.HandleDefaultCommand}, {CommandID.Asin, this.HandleDefaultCommand},
 92                 {CommandID.Tan, this.HandleDefaultCommand}, {CommandID.Tanh, this.HandleDefaultCommand},
 93                 {CommandID.Atan, this.HandleDefaultCommand}, {CommandID.Cos, this.HandleDefaultCommand},
 94                 {CommandID.Cosh, this.HandleDefaultCommand}, {CommandID.Acos, this.HandleDefaultCommand}
 95             };
 96         }
 97 
 98         /// <summary>
 99         /// Handle the command.
100         /// </summary>
101         /// <param name="cmdID">The command ID of the command to be handled.</param>
102         /// <param name="cmdArg">The command argument of the command to be handled.</param>
103         /// <returns>The handle result.</returns>
104         public double HandleCommand(CommandID cmdID, double cmdArg)
105         {
106             var cmdHandler = this.cmdHandlers.ContainsKey(cmdID) ? this.cmdHandlers[cmdID] : this.HandleDefaultCommand;
107             return cmdHandler(cmdArg);
108         }
109 
110         /// <summary>
111         /// Handle the default command.
112         /// </summary>
113         /// <param name="cmdArg">The command argument of the default command.</param>
114         /// <returns>The handle result.</returns>
115         private double HandleDefaultCommand(double cmdArg)
116         {
117             return 0;
118         }
119     }
120 
121     class Program
122     {
123         static void Main(string[] args)
124         {
125             List<CommandID> cmdList = new List<CommandID>()
126             {
127                 CommandID.Abs, CommandID.Sin, CommandID.Sinh, CommandID.Asin, CommandID.Tan,
128                 CommandID.Tanh, CommandID.Atan, CommandID.Cos, CommandID.Cosh, CommandID.Acos
129             };
130 
131             Stopwatch watch = new Stopwatch();
132 
133             watch.Start();
134             CommandHandlerTest1 test1 = new CommandHandlerTest1();
135             for (int i = 0; i < 1000000; i++)
136             {
137                 for (int j = 0; j < 10; j++)
138                 {
139                     test1.HandleCommand(cmdList[j], 0.1);
140                 }
141             }
142 
143             watch.Stop();
144             Console.WriteLine(watch.ElapsedMilliseconds);
145 
146             watch.Reset();
147             watch.Start();
148             CommandHandlerTest2 test2 = new CommandHandlerTest2();
149             for (int i = 0; i < 1000000; i++)
150             {
151                 for (int j = 0; j < 10; j++)
152                 {
153                     test2.HandleCommand(cmdList[j], 0.1);
154                 }
155             }
156 
157             watch.Stop();
158             Console.WriteLine(watch.ElapsedMilliseconds);
159 
160             Console.ReadLine();
161         }
162     }
Performance Test

原本认为采用字典表+委托的方法性能应该比switch-case高,但测试结果却令人失望(CommandHandler1比CommandHandler2反而高了近50%)。分析这里面可能的原因:

1. 通过委托调度处理方法比直接调用方法效率相对较低;

2. 编译器对switch-case的代码进行了一定的优化。

好在一般对这里的性能要求不是很高,100W次平均下来相差零点几微秒,而在又多又长的switch-case代码段中,一般是可以接受的。而对于比较简短的switch-case代码段,也就没有必要采用第二种方式去替换了。

当然这里所举的处理Command的例子,有更好的解决方案。由于这篇文章只是讲述如何用一种更优美的方式来替代switch-case,因此就不再详细描述了。

posted @ 2013-06-30 16:52  BurningFish  阅读(1827)  评论(1编辑  收藏  举报