zoukankan      html  css  js  c++  java
  • [C#] 用一种更优美的方式来替换掉又多又长的switchcase代码段

    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,因此就不再详细描述了。

  • 相关阅读:
    Windows——bat中的路径和工具栏运行bat的坑
    KBE——查询
    KBE实践——登录案例
    KBE_运作流程
    KBE_创建项目和基本常识
    python_面向对象
    程序员常见面试之 数据库 知识点小结(三)
    C#编程总结
    人人必知的10个 jQuery 小技巧
    Javascript生成二维码(QR)
  • 原文地址:https://www.cnblogs.com/haihai1203/p/3163187.html
Copyright © 2011-2022 走看看