案例陈述的更好选择

时间:2010-05-21 07:09:00

标签: c# .net switch-statement

我目前有一个switch语句,运行大约300个奇数行。我知道这不是它可以获得的巨大,但我确信有更好的方法来解决这个问题。

switch语句采用Enum,用于确定与日志记录相关的某些属性。现在问题在于,很容易省略枚举值,并且不会给它一个值,因为它不在switch语句中。

是否有一个选项可以用来确保每个枚举都被使用并给定一组自定义值来完成它的工作?

编辑:


请求的代码示例:(这很简单,但显示我的意思。同样存在以下值的枚举。)

internal void GenerateStatusLog(LogAction ActionToLog)
{
    switch (ActionToLog)
    {
        case LogAction.None:
            {
                return;
            }
        case LogAction.LogThis:
            {
                ActionText = "Logging this Information";
                LogText = "Go for it.";

                break;
            }
    }

    // .. Do everything else
}

6 个答案:

答案 0 :(得分:5)

修改

我再次想到这一点,在SO中查看相关问题,我写了一些代码。我创建了一个名为AdvancedSwitch<T>的类,它允许您添加案例并公开一个方法来评估一个值,并允许您指定它应检查存在的值。

这就是我提出的:

public class AdvancedSwitch<T> where T : struct
{
    protected Dictionary<T, Action> handlers = new Dictionary<T, Action>();

    public void AddHandler(T caseValue, Action action)
    {
        handlers.Add(caseValue, action);
    }

    public void RemoveHandler(T caseValue)
    {
        handlers.Remove(caseValue);
    }

    public void ExecuteHandler(T actualValue)
    {
        ExecuteHandler(actualValue, Enumerable.Empty<T>());
    }

    public void ExecuteHandler(T actualValue, IEnumerable<T> ensureExistence)
    {
        foreach (var val in ensureExistence)
            if (!handlers.ContainsKey(val))
                throw new InvalidOperationException("The case " + val.ToString() + " is not handled.");

        handlers[actualValue]();
    }
}

您可以这样使用该类:

public enum TrafficColor { Red, Yellow, Green }

public static void Main()
{
    Console.WriteLine("Choose a traffic color: red, yellow, green?");
    var color = (TrafficColor)Enum.Parse(typeof(TrafficColor), Console.ReadLine());
    var result = string.Empty;

    // Creating the "switch"
    var mySwitch = new AdvancedSwitch<TrafficColor>();

    // Adding a single case
    mySwitch.AddHandler(TrafficColor.Green, delegate
    {
        result = "You may pass.";
    });

    // Adding multiple cases with the same action
    Action redAndYellowDelegate = delegate
    {
        result = "You may not pass.";
    };
    mySwitch.AddHandler(TrafficColor.Red, redAndYellowDelegate);
    mySwitch.AddHandler(TrafficColor.Yellow, redAndYellowDelegate);

    // Evaluating it
    mySwitch.ExecuteHandler(color, (TrafficColor[])Enum.GetValues(typeof(TrafficColor)));

    Console.WriteLine(result);
}

通过创建匿名代表,您可以轻松地将新案例添加到“切换块”中。 :)
并不是说你也可以使用lambda表达式和lambda块,例如() => { ... }而不是delegate { ... }

您可以轻松使用此类而不是长开关块。

原帖:

如果您使用Visual Studio,请始终使用swich代码段创建switch语句。键入switch两次按Tab键,它会为您自动生成所有可能性。

然后,在结尾处添加default个案例,这会引发异常,这样在测试你的应用时,你会立即注意到有一个未处理的案例。

我的意思是这样的:

switch (something)
{
    ...
    case YourEnum.SomeValue:
        ...
        break;
    default:
        throw new InvalidOperationException("Default case reached.");
}

答案 1 :(得分:4)

嗯,有default案例......除此之外没有编辑/编译时间构造。

然而,如果您选择在运行时进行策略,访问者和与其相关的其他模式可能是适当的。

示例代码有助于获得最佳答案。

编辑:谢谢你的样本。我仍然认为它需要一点充实,因为你不知道是否有一些参数只适用于某些case等。

操作通常用作Command模式的别名,并且您的Enum被调用LogAction这一事实表明每个值都带有行为 - 这就是暗示(您在{{1)中粘贴适当的代码}}或explicit(在特定的Command层次结构类中)。

因此,在我看来,使用Command模式是合适的(尽管您的示例不能证明它) - 即,有一个类(可能是使用构造函数重载或任何其他[工程机制]的层次结构)与请求关联的状态以及特定行为。然后,不是传递Enum值,而是为记录器创建一个适当的case实例,它只是调用它(可能会传递Command可以登录的Log Sink'容器')。否则,你在不同的地方戳了随机的参数子集。

SEEALSO相关帖子:

答案 2 :(得分:2)

一种可能的解决方案是使用SortedDictionary:

delegate void EnumHandler (args);
SortedDictionary <Enum, EnumHandler> handlers;

constructor
{
   handlers = new SortedDictionary <Enum, EnumHandler> ();
   fill in handlers
}

void SomeFunction (Enum enum)
{
  EnumHandler handler;

  if (handlers.TryGetValue (enum, out handler))
  {
     handler (args);
  }
  else
  {
    // not handled, report an error
  }
}

此方法允许您动态替换处理程序。您还可以使用List作为字典的值部分,并为每个枚举使用多个处理程序。

答案 3 :(得分:0)

尝试使用反射。

  • 使用包含相关值的属性装饰枚举选项并返回此值。
  • 创建静态类常量并使用反射将enum-option映射到常量名称

hope this will help

答案 4 :(得分:0)

有时将选项存储在地图中是一个很好的解决方案,您也可以将配置外部化到文件中,不确定它是否适用于您的应用程序。

答案 5 :(得分:0)

这里的

代码示例,最终的通用代码有点 EDIT 添加了一个额外的示例,消除了对角括号以牺牲一些最终的灵活性为代价。)

这个解决方案给你的一点就是性能良好 - 不如简单的switch语句好,但是每个case语句都变成了字典查找和方法调用,所以仍然很好。但是,由于使用了反映初始化的静态通用,第一次调用将会降低性能。

创建属性和泛型类型,如下所示:

[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
public class DynamicSwitchAttribute : Attribute
{
 public DynamicSwitchAttribute(Type enumType, params object[] targets)
 { Targets = new HashSet<object>(targets); EnumType = enumType; }
 public Type EnumType { get; private set; }
 public HashSet<object> Targets { get; private set; }
}

//this builds a cache of methods for a given TTarget type, with a 
//signature equal to TAction,
//keyed by values of the type TEnum.  All methods are expected to 
//be instance methods.
//this code can easily be modified to support static methods instead.
//what would be nice here is if we could enforce a generic constraint 
//on TAction : Delegate, but we can't.
public static class DynamicSwitch<TTarget, TEnum, TAction>
{
 //our lookup of actions against enum values.
 //note: no lock is required on this as it is built when the static 
 //class is initialised.

 private static Dictionary<TEnum, TAction> _actions = 
   new Dictionary<TEnum, TAction>();

 private static MethodInfo _tActionMethod;
 private static MethodInfo TActionMethod
 {
  get
  {
   if (_tActionMethod == null)
   {
    //one criticism of this approach might be that validation exceptions
    //will be thrown inside a TypeInitializationException.
    _tActionMethod = typeof(TAction).GetMethod("Invoke", 
      BindingFlags.Instance | BindingFlags.Public);

    if (_tActionMethod == null)
     throw new ArgumentException(/*elided*/);

    //verify that the first parameter type is compatible with our 
    //TTarget type.
    var methodParams = _tActionMethod.GetParameters();
    if (methodParams.Length == 0)
     throw new ArgumentException(/*elided*/);

    //now check that the first parameter is compatible with our type TTarget
    if (!methodParams[0].ParameterType.IsAssignableFrom(typeof(TTarget)))
     throw new ArgumentException(/*elided*/);
   }
   return _tActionMethod;
  }
 }

 static DynamicSwitch()
 {
  //examine the type TTarget to extract all public instance methods 
  //(you can change this to private instance if need be) which have a
  //DynamicSwitchAttribute defined.
  //we then project the attributes and the method into an anonymous type
  var possibleMatchingMethods = 
     from method in typeof(TTarget).
       GetMethods(BindingFlags.Public | BindingFlags.Instance)
     let attributes = method.GetCustomAttributes(
        typeof(DynamicSwitchAttribute), true).
        Cast<DynamicSwitchAttribute>().ToArray()
     where attributes!= null && attributes.Length == 1 
        && attributes[0].EnumType.Equals(typeof(TEnum))
     select new { Method = method, Attribute = attributes[0] };

  //create linq expression parameter expressions for each of the 
  //delegate type's parameters
  //these can be re-used for each of the dynamic methods we generate.
  ParameterExpression[] paramExprs = TActionMethod.GetParameters().
    Select((pinfo, index) =>
    Expression.Parameter(
      pinfo.ParameterType, pinfo.Name ?? string.Format("arg{0}"))
    ).ToArray();
  //pre-build an array of these parameter expressions that only 
  //include the actual parameters
  //for the method, and not the 'this' parameter.
  ParameterExpression[] realParamExprs = paramExprs.Skip(1).ToArray();

  //this has to be generated for each target method.
  MethodCallExpression methodCall = null;

  foreach (var match in possibleMatchingMethods)
  {
   if (!MethodMatchesAction(match.Method))
    continue;

   //right, now we're going to use System.Linq.Expressions to build 
   //a dynamic expression to invoke this method given an instance of TTarget.
   methodCall = 
     Expression.Call(
       Expression.Convert(
         paramExprs[0], typeof(TTarget)
       ),  
       match.Method, realParamExprs);

   TAction dynamicDelegate = Expression.
     Lambda<TAction>(methodCall, paramExprs).Compile();

   //now we have our method, we simply inject it into the dictionary, using 
   //all the unique TEnum values (from the attribute) as the keys
   foreach (var enumValue in match.Attribute.Targets.OfType<TEnum>())
   {
    if (_actions.ContainsKey(enumValue))
     throw new InvalidOperationException(/*elided*/);

    _actions[enumValue] = dynamicDelegate;
   }
  }
 }

 private static bool MethodMatchesAction(MethodInfo method)
 {
  //so we want to check that the target method matches our desired 
  //delegate type (TAction).
  //The way this is done is to fetch the delegate type's Invoke 
  //method (implicitly invoked when you invoke delegate(args)), and 
  //then we check the return type and parameters types of that
  //against the return type and args of the method we've been passed.

  //if the target method's return type is equal to or derived from the 
  //expected delegate's return type, then all is good.

  if (!_tActionMethod.ReturnType.IsAssignableFrom(method.ReturnType))
   return false;

  //now, the parameter lists of the method will not be equal in length, 
  //as our delegate explicitly includes the 'this' parameter, whereas 
  //instance methods do not.

  var methodParams = method.GetParameters();
  var delegateParams = TActionMethod.GetParameters();

  for (int i = 0; i < methodParams.Length; i++)
  {
   if (!methodParams[i].ParameterType.IsAssignableFrom(
        delegateParams[i + 1].ParameterType))
    return false;
  }
  return true;
 }


 public static TAction Resolve(TEnum value)
 {
  TAction result;

  if (!_actions.TryGetValue(value, out result))
   throw new ArgumentException("The value is not mapped");

  return result;
 }
}

现在在单元测试中执行此操作:

[TestMethod]
public void TestMethod1()
{
  Assert.AreEqual(1, 
    DynamicSwitch<UnitTest1, Blah, Func<UnitTest1, int>>.
      Resolve(Blah.BlahBlah)(this));

  Assert.AreEqual(125, 
    DynamicSwitch<UnitTest1, Blah, Func<UnitTest1, int>>.
      Resolve(Blah.Blip)(this));

 Assert.AreEqual(125, 
    DynamicSwitch<UnitTest1, Blah, Func<UnitTest1, int>>.
      Resolve(Blah.Bop)(this));
}

public enum Blah
{
 BlahBlah,
 Bloo,
 Blip,
 Bup,
 Bop
}


[DynamicSwitchAttribute(typeof(Blah), Blah.BlahBlah)]
public int Method()
{
 return 1;
}

[DynamicSwitchAttribute(typeof(Blah), Blah.Blip, Blah.Bop)]
public int Method2()
{
 return 125;
}

因此,给定TEnum的值,以及您首选的'action'类型(在您的代码中,您似乎只是简单地返回任何内容并修改类的内部状态),您只需查阅DynamicSwitch&lt;&gt; class,请求它解析目标方法,然后内联调用它(将调用该方法的目标对象作为第一个参数传递)。

我真的不期待任何投票 - 这是一个 MAD 解决方案,说实话(它确实具有能够应用于任何枚举类型的优势,甚至是谨慎的价值观输入int / float / double,以及支持任何委托类型) - 所以也许它是一个大锤!

修改

一旦你有这样的静态通用,就会出现角括号地狱 - 所以我们想尝试摆脱它们。很多时候,这是通过对方法参数等的类型推断来完成的 - 但我们在这里有一个问题我们不能轻易地推断出委托的签名而不重复方法调用即{{1} }。

但是,您似乎需要一个不带参数并返回void的方法,因此您可以通过将委托类型修复为Action来关闭此庞然大物泛型,并将流体API也添加到混合中(如果这是您的类型)事情):

(args) => return

现在将其添加到测试项目中:

public static class ActionSwitch
{
  public class SwitchOn<TEnum>
  {
    private TEnum Value { get; set; }

    internal SwitchOn(TEnum value)
    {
      Value = value;
    }

    public class Call<TTarget>{
      private TEnum Value { get; set; }
      private TTarget Target { get; set; }

      internal Call(TEnum value, TTarget target)
      {
        Value = value;
        Target = target;
        Invoke();
      }

      internal void Invoke(){
          DynamicSwitch<TTarget, TEnum, Action<TTarget>>.Resolve(Value)(Target);
      }
    }

    public Call<TTarget> On<TTarget>(TTarget target)
    {
      return new Call<TTarget>(Value, target);
    }
  }

  public static SwitchOn<TEnum> Switch<TEnum>(TEnum onValue)
  {
    return new SwitchOn<TEnum>(onValue);
  }
}

只有问题(除了解决方案的复杂性:))是每当你想在其他地方使用新类型的目标委托进行动态切换时,你必须重新构建这个静态包装器类型。您可以根据Action&lt; ...&gt;生成通用版本和Func&lt; ...&gt;包含TArg1,TArg( n )和TReturn(如果是Func&lt;&gt;)的代表 - 但是你最终会编写更多代码。

也许我会把它变成我博客上的一篇文章并完成所有这些 - 如果我有时间的话!