
正文
[C#] 用一种更优美的方式来替换掉又多又长的switch-case代码段
提示:扫一扫查出行【扫一扫了解最新限行尾号】
复制提示
switch-case语句是我们编码过程中常用的一种分支语句。然而正所谓成也萧何败萧何,每当我们向一个已经拥有了成百上千行的switch-case代码段中添加新的case分支的时候,我们是否有过为代码的可读性和可维护性不断下降而头疼烦恼呢。
事实上,我们可以有很多方法来避免出现这种分支有多又长的switch-case代码段,从而写出更优美的代码。在.Net中我们可以非常简单地分解switch-case中的代码。
下面选择了一个比较常见的例子:模块采用switch-case来处理接收到的Command
假设我们现在定义了以下10个CommandID。
/// <summary>
/// Definition of commands.
/// </summary>
enum CommandID
{
Abs = ,
Sin = ,
Sinh = ,
Asin = ,
Tan = ,
Tanh = ,
Atan = ,
Cos = ,
Cosh = ,
Acos =
}
CommandID
下面我们定义了CommandHandler1类来处理这些命令。该类采用switch-case语句分别处理不同的CommandID。我们可以将每个CommandID的处理逻辑封装在各个函数中(这里偷懒,假设Math里面定义的几个方法就是我们封装的处理逻辑),然后在每个case中调用相应的函数即可。
class CommandHandler1
{
/// <summary>
/// Handle the command.
/// </summary>
/// <param name="cmdID">The command ID of the command to be handled.</param>
/// <param name="cmdArg">The command argument of the command to be handled.</param>
/// <returns>The handle result.</returns>
public double HandleCommand(CommandID cmdID, double cmdArg)
{
double retValue;
switch (cmdID)
{
case CommandID.Abs:
retValue = Math.Abs(cmdArg);
break;
case CommandID.Sin:
retValue = Math.Sin(cmdArg);
break;
case CommandID.Sinh:
retValue = Math.Sinh(cmdArg);
break;
case CommandID.Asin:
retValue = Math.Asin(cmdArg);
break;
case CommandID.Tan:
retValue = Math.Tan(cmdArg);
break;
case CommandID.Tanh:
retValue = Math.Tanh(cmdArg);
break;
case CommandID.Atan:
retValue = Math.Atan(cmdArg);
break;
case CommandID.Cos:
retValue = Math.Cos(cmdArg);
break;
case CommandID.Cosh:
retValue = Math.Cosh(cmdArg);
break;
case CommandID.Acos:
retValue = Math.Acos(cmdArg);
break;
default:
retValue = this.HandleDefaultCommand(cmdArg);
break;
} return retValue;
} /// <summary>
/// Handle the default command.
/// </summary>
/// <param name="cmdArg">The command argument of the default command.</param>
/// <returns>The handle result.</returns>
private double HandleDefaultCommand(double cmdArg)
{
return ;
}
}
CommandHandler1
在CommandHandler1中,我们如果新增了一个命令,那么就需要增加一个处理新命令的方法,同时修改HandleCommand方法体,在其中添加一个case分支并调用新增的方法。
下面利用字典和委托CommandHandler1里面的switch-case代码段。我们新定义了一个类CommandHandler2,将处理每个CommandID的委托方法保存在一个字典表中(cmdHandlers),在HandleCommand方法体中,通过cmdID找到对应的委托方法来处理响应的cmdID。
class CommandHandler2
{
/// <summary>
/// The dictionary contains all the command handlers to handle the commands.
/// </summary>
private Dictionary<CommandID, Func<double, double>> cmdHandlers = new Dictionary<CommandID, Func<double, double>>
{
{CommandID.Abs, Math.Abs}, {CommandID.Sin, Math.Sin}, {CommandID.Sinh, Math.Sinh}, {CommandID.Asin, Math.Asin},
{CommandID.Tan, Math.Tan}, {CommandID.Tanh, Math.Tanh}, {CommandID.Atan, Math.Atan}, {CommandID.Cos, Math.Cos},
{CommandID.Cosh, Math.Cosh}, {CommandID.Acos, Math.Acos}
}; /// <summary>
/// Handle the command.
/// </summary>
/// <param name="cmdID">The command ID of the command to be handled.</param>
/// <param name="cmdArg">The command argument of the command to be handled.</param>
/// <returns>The handle result.</returns>
public double HandleCommand(CommandID cmdID, double cmdArg)
{
var cmdHandler = this.cmdHandlers.ContainsKey(cmdID) ? this.cmdHandlers[cmdID] : this.HandleDefaultCommand;
return cmdHandler(cmdArg);
} /// <summary>
/// Handle the default command.
/// </summary>
/// <param name="cmdArg">The command argument of the default command.</param>
/// <returns>The handle result.</returns>
private double HandleDefaultCommand(double cmdArg)
{
return ;
}
}
CommandHandler2
当我们新增一个命令时,只需要增加一个处理新命令的方法,同时将这个新命令及其对应的委托方法添加到字典表中即可。
下面我们来看一下这两种方法的性能。在测试性能时,我们将所有的cmd处理方法全都替换成了HandleDefaultCommand。
class CommandHandlerTest1
{
/// <summary>
/// Handle the command.
/// </summary>
/// <param name="cmdID">The command ID of the command to be handled.</param>
/// <param name="cmdArg">The command argument of the command to be handled.</param>
/// <returns>The handle result.</returns>
public double HandleCommand(CommandID cmdID, double cmdArg)
{
double retValue;
switch (cmdID)
{
case CommandID.Abs:
retValue = this.HandleDefaultCommand(cmdArg);
//retValue = Math.Abs(cmdArg);
break;
case CommandID.Sin:
retValue = this.HandleDefaultCommand(cmdArg);
//retValue = Math.Sin(cmdArg);
break;
case CommandID.Sinh:
retValue = this.HandleDefaultCommand(cmdArg);
//retValue = Math.Sinh(cmdArg);
break;
case CommandID.Asin:
retValue = this.HandleDefaultCommand(cmdArg);
//retValue = Math.Asin(cmdArg);
break;
case CommandID.Tan:
retValue = this.HandleDefaultCommand(cmdArg);
//retValue = Math.Tan(cmdArg);
break;
case CommandID.Tanh:
retValue = this.HandleDefaultCommand(cmdArg);
//retValue = Math.Tanh(cmdArg);
break;
case CommandID.Atan:
retValue = this.HandleDefaultCommand(cmdArg);
//retValue = Math.Atan(cmdArg);
break;
case CommandID.Cos:
retValue = this.HandleDefaultCommand(cmdArg);
//retValue = Math.Cos(cmdArg);
break;
case CommandID.Cosh:
retValue = this.HandleDefaultCommand(cmdArg);
//retValue = Math.Cosh(cmdArg);
break;
case CommandID.Acos:
retValue = this.HandleDefaultCommand(cmdArg);
//retValue = Math.Acos(cmdArg);
break;
default:
retValue = this.HandleDefaultCommand(cmdArg);
break;
} return retValue;
} /// <summary>
/// Handle the default command.
/// </summary>
/// <param name="cmdArg">The command argument of the default command.</param>
/// <returns>The handle result.</returns>
private double HandleDefaultCommand(double cmdArg)
{
return ;
}
} class CommandHandlerTest2
{
/// <summary>
/// The dictionary contains all the command handlers to handle the commands.
/// </summary>
//private Dictionary<CommandID, Func<double, double>> cmdHandlers = new Dictionary<CommandID, Func<double, double>>
//{
// {CommandID.Abs, Math.Abs}, {CommandID.Sin, Math.Sin}, {CommandID.Sinh, Math.Sinh}, {CommandID.Asin, Math.Asin},
// {CommandID.Tan, Math.Tan}, {CommandID.Tanh, Math.Tanh}, {CommandID.Atan, Math.Atan}, {CommandID.Cos, Math.Cos},
// {CommandID.Cosh, Math.Cosh}, {CommandID.Acos, Math.Acos}
//};
private Dictionary<CommandID, Func<double, double>> cmdHandlers; public CommandHandlerTest2()
{
cmdHandlers = new Dictionary<CommandID, Func<double, double>>
{
{CommandID.Abs, this.HandleDefaultCommand}, {CommandID.Sin, this.HandleDefaultCommand},
{CommandID.Sinh, this.HandleDefaultCommand}, {CommandID.Asin, this.HandleDefaultCommand},
{CommandID.Tan, this.HandleDefaultCommand}, {CommandID.Tanh, this.HandleDefaultCommand},
{CommandID.Atan, this.HandleDefaultCommand}, {CommandID.Cos, this.HandleDefaultCommand},
{CommandID.Cosh, this.HandleDefaultCommand}, {CommandID.Acos, this.HandleDefaultCommand}
};
} /// <summary>
/// Handle the command.
/// </summary>
/// <param name="cmdID">The command ID of the command to be handled.</param>
/// <param name="cmdArg">The command argument of the command to be handled.</param>
/// <returns>The handle result.</returns>
public double HandleCommand(CommandID cmdID, double cmdArg)
{
var cmdHandler = this.cmdHandlers.ContainsKey(cmdID) ? this.cmdHandlers[cmdID] : this.HandleDefaultCommand;
return cmdHandler(cmdArg);
} /// <summary>
/// Handle the default command.
/// </summary>
/// <param name="cmdArg">The command argument of the default command.</param>
/// <returns>The handle result.</returns>
private double HandleDefaultCommand(double cmdArg)
{
return ;
}
} class Program
{
static void Main(string[] args)
{
List<CommandID> cmdList = new List<CommandID>()
{
CommandID.Abs, CommandID.Sin, CommandID.Sinh, CommandID.Asin, CommandID.Tan,
CommandID.Tanh, CommandID.Atan, CommandID.Cos, CommandID.Cosh, CommandID.Acos
}; Stopwatch watch = new Stopwatch(); watch.Start();
CommandHandlerTest1 test1 = new CommandHandlerTest1();
for (int i = ; i < ; i++)
{
for (int j = ; j < ; j++)
{
test1.HandleCommand(cmdList[j], 0.1);
}
} watch.Stop();
Console.WriteLine(watch.ElapsedMilliseconds); watch.Reset();
watch.Start();
CommandHandlerTest2 test2 = new CommandHandlerTest2();
for (int i = ; i < ; i++)
{
for (int j = ; j < ; j++)
{
test2.HandleCommand(cmdList[j], 0.1);
}
} watch.Stop();
Console.WriteLine(watch.ElapsedMilliseconds); Console.ReadLine();
}
}
Performance Test
原本认为采用字典表+委托的方法性能应该比switch-case高,但测试结果却令人失望(CommandHandler1比CommandHandler2反而高了近50%)。分析这里面可能的原因:
1. 通过委托调度处理方法比直接调用方法效率相对较低;
2. 编译器对switch-case的代码进行了一定的优化。
好在一般对这里的性能要求不是很高,100W次平均下来相差零点几微秒,而在又多又长的switch-case代码段中,一般是可以接受的。而对于比较简短的switch-case代码段,也就没有必要采用第二种方式去替换了。
当然这里所举的处理Command的例子,有更好的解决方案。由于这篇文章只是讲述如何用一种更优美的方式来替代switch-case,因此就不再详细描述了。






