2010-10-01 10 views
10

Estoy tratando de encontrar una solución para este problema. Este es mi código de ejemplo:usando la colección de cadenas en una instrucción switch

class Program 
{ 
    private string Command; 

    private static string[] Commands = { "ComandOne", "CommandTwo", "CommandThree", "CommandFour" }; 


    static void Main(string[] args) 
    { 
    Command = args[0]; 
    switch(Command) 
    { 
     case Commands[0]: //do something 
     break; 
     case Commands[1]: //do something else 
     break; 
     case Commands[2]: //do something totally different 
     break; 
     case Commands[3]: //do something boring 
     break; 
     default: //do your default stuff 
     break; 
    } 
    } 

    void DifferentMethod() 
    { 
    foreach(string c in Commands) 
    { 
     //do something funny 
    } 
    } 
} 

Este código no funciona porque los valores de cadena en el interruptor no son constantes. Quiero escribir un código fácil de mantener.
Me gusta usar algo así como una matriz porque necesito usar los mismos valores en otro lugar en un bucle.
Con int-values, una enumeración sería perfecta, pero no encontré una solución pequeña para lo mismo con cadenas.

+1

mayoría de las soluciones sugieren enumeraciones, pero los nombres de enumeración tienen requisitos especiales de nomenclatura. Si esto causa problemas, puede vincular un 'DescriptionAttribute' a cada elemento de enumeración para que contenga un nombre descriptivo (que podría tener espacios o lo que sea), y podría buscar esos nombres al iterar sobre la enumeración dentro de' DifferentMethod'. – Brian

+0

@Brian, buen punto, y para obtener ese atributo, necesitará el campo: 'FieldInfo enumField = typeof (Comandos) .GetField (enumValue.ToString());' –

+0

@Kirk Woll: Las siguientes dos líneas después de ese ser : 'DescriptionAttribute da = (DescriptionAttribute) Attribute.GetCustomAttribute (enumField, typeof (DescriptionAttribute)); if (da.Description! = null) description = da.Description; ' – Brian

Respuesta

19

Convertir Commands en una enumeración:

enum Commands { ComandOne, CommandTwo, CommandThree, CommandFour } 

sentencia switch debe verse como:

static void Main(string[] args) 
{ 
    Command = (Commands)Enum.Parse(typeof(Commands), args[0]); 
    switch(Command) 
    { 
     case Commands.CommandOne: 
      //do something 
      break; 
     case Commands.CommandTwo: 
      //do something else 
      break; 
     ... 
     default: 
      // default stuff 
    } 
} 

Y su último método debe ser similar:

void DifferentMethod() 
{ 
    foreach(var c in Enum.GetValues(typeof(Commands))) 
    { 
     string s = c.ToString(); 
     //do something funny 
    } 
} 
+1

recomiendo calcular previamente 'Enum.GetValues ​​(typeof (Commands))' y almacenar en alguna parte. no es una operación barata. – Andrey

+1

@Andrey: No estoy de acuerdo. DifferentMethod() solo tarda aproximadamente una docena de microsegundos en cada llamada en mi máquina. Vale la pena hacer un cálculo previo de la enumeración si el análisis lo encuentra como un cuello de botella que debe corregirse, y sospecho que la solución adecuada es tener una copia estática de la enumeración previamente calculada. Prefiero guardar dichas optimizaciones hasta que sea necesario (es decir, que haya realizado un perfil y este haya sido el cuello de botella); hace que el código sea un poco más complicado para tener comandos almacenados como una enumeración y como una colección de cadenas. – Brian

+0

¿No está Enum.Parse() internamente haciendo un cambio en la cadena? En cuanto a rendimiento, llamar solo a ese método sería tan caro como el método original. – manixrock

2

Como dijiste, solo se permiten las expresiones constantes en un interruptor. Normalmente harías esto definiendo un enum y lo usarías en tu interruptor.

class Program 
{ 
    private enum Command 
    { 
    CommandOne = 1, 
    CommandTwo = 2, 
    CommandThree = 3 
    } 

    static void Main(string[] args) 
    { 
    var command = Enum.Parse(typeof(Commands), args[0]); 
    switch(command) 
    { 
     case Command.CommandOne: //do something 
     break; 
     case Command.CommandTwo: //do something else 
     break; 
     case Command.CommandThree: //do something totally different 
     break; 
     default: //do your default stuff 
     break; 
    } 
    } 
} 

Uso Enum.GetValues para enumerar los valores de enumeración en DifferentMethod.

+0

+1 Los enum son ciertamente el camino a seguir para colecciones de valores preestablecidos similares. – BoltClock

+0

'DifferentMethod' ya no funciona después del cambio ... – Heinzi

+0

@Heinzi: como indica Kirk, para eso está 'Enum.GetValues'. – Brian

5

Ayer mismo creé una solución para ello. En su caso, enum s son mejores, pero esta es mi solución para la situación general del interruptor sin const.

usos:

static string DigitToStr(int i) 
    { 
     return i 
      .Case(1, "one") 
      .Case(2, "two") 
      .Case(3, "three") 
      .Case(4, "four") 
      .Case(5, "five") 
      .Case(6, "six") 
      .Case(7, "seven") 
      .Case(8, "eight") 
      .Case(9, "nine") 
      .Default(""); 
    } 

     int a = 1, b = 2, c = 3; 
     int d = (4 * a * c - b * 2); 
     string res = true 
      .Case(d < 0, "No roots") 
      .Case(d == 0, "One root") 
      .Case(d > 0, "Two roots") 
      .Default(_ => { throw new Exception("Impossible!"); }); 

     string res2 = d 
      .Case(x => x < 0, "No roots") 
      .Case(x => x == 0, "One root") 
      .Case(x => x > 0, "Two roots") 
      .Default(_ => { throw new Exception("Impossible!"); }); 

     string ranges = 11 
      .Case(1, "one") 
      .Case(2, "two") 
      .Case(3, "three") 
      .Case(x => x >= 4 && x < 10, "small") 
      .Case(10, "ten") 
      .Default("big"); 

definición:

class Res<O, R> 
{ 
    public O value; 
    public bool succ; 
    public R result; 

    public Res() 
    { 

    } 

    static public implicit operator R(Res<O, R> v) 
    { 
     if (!v.succ) 
      throw new ArgumentException("No case condition is true and there is no default block"); 
     return v.result; 
    } 
} 

static class Op 
{ 
    static public Res<O, R> Case<O, V, R>(this Res<O, R> o, V v, R r) 
    { 
     if (!o.succ && Equals(o.value, v)) 
     { 
      o.result = r; 
      o.succ = true; 
     } 
     return o; 
    } 

    static public Res<O, R> Case<O, V, R>(this O o, V v, R r) 
    { 
     return new Res<O, R>() 
     { 
      value = o, 
      result = r, 
      succ = Equals(o, v), 
     }; 
    } 

    static public Res<O, R> Case<O, R>(this Res<O, R> o, Predicate<O> cond, R r) 
    { 
     if (!o.succ && cond(o.value)) 
     { 
      o.result = r; 
      o.succ = true; 
     } 
     return o; 
    } 

    static public Res<O, R> Case<O, R>(this O o, Predicate<O> cond, R r) 
    { 
     return new Res<O, R>() 
     { 
      value = o, 
      result = r, 
      succ = cond(o), 
     }; 
    } 

    private static bool Equals<O, V>(O o, V v) 
    { 
     return o == null ? v == null : o.Equals(v); 
    } 

    static public R Default<O, R>(this Res<O, R> o, R r) 
    { 
     return o.succ 
      ? o.result 
      : r; 
    } 

    static public R Default<O, R>(this Res<O, R> o, Func<O, R> def) 
    { 
     return o.succ ? o.result : def(o.value); 
    } 
} 
2

definir un Dictionary<string, enum> y mapa del comando de entrada en el valor adecuado antes de entrar en el interruptor. Si no se encuentra la coincidencia, se produce el procesamiento predeterminado.

8

una solución fácil en su específico ejemplo:

switch(Array.IndexOf(Commands, Command)) 
{ 
    case 0: ... 
    case 1: ... 

    default: //unknown command. Technically, this is case -1 
} 

Otras alternativas:

  1. Inline las cuerdas.

    interruptor (Comando) { caso "CommandOne": ... caso "CommandTwo": ... }

  2. Uso de una enumeración en su lugar, como dice KirkWoll. Esta es probablemente la solución más limpia.

  3. En escenarios más complejos, con una búsqueda como Dictionary<string, Action> o Dictionary<string, Func<Foo>> podría proporcionar una mejor expresividad.

  4. Si los casos son complejos, puede crear una interfaz ICommand. Esto requerirá mapear la secuencia de comandos a la implementación concreta correcta, para lo cual se usan construcciones simples (conmutadores/diccionarios) o reflecciones complejas (encuentre implementaciones de ICommand con ese nombre, o con una cierta decoración de atributo).

+3

+1. El OP probablemente esté mejor con la solución de Kirk, pero está más cerca de mostrar la manera de hacer exactamente lo que está pidiendo. – Brian

1

Grandes respuestas aquí y probablemente responder a su pregunta mejor que lo que voy a mencionar ...

Dependiendo de la complejidad de la lógica se basa, usted puede considerar el uso de un patrón de estrategia como esta:

Refactoring a Switch statement

o

The Strategy Template Pattern

Una vez más, muy probablemente más complicado que su solución preguntó, simplemente tirarlo por ahí ...

5

Se podría eliminar la declaración switch por completo mediante la creación de objetos IYourCommand y cargarlos en un Dictionary<string, IYourCommand>.

class Program 
{ 
    private Dictionary<string, IYourCommand> Command = new Dictionary<string, IYourCommand> 
    { 
     { "CommandOne", new CommandOne() }, 
     { "CommandTwo", new CommandTwo() }, 
     { "CommandThree", new CommandThree() }, 
     { "CommandFour", new CommandFour() }, 
    }; 

    public static void Main(string[] args) 
    { 
    if (Command.ContainsKey(args[0])) 
    { 
     Command[args[0]].DoSomething(); 
    } 
    } 
} 

public interface IYourCommand 
{ 
    void DoSomething(); 
} 
+0

+1, no sabía sobre ICommand antes. ¡Gracias! – FrustratedWithFormsDesigner

+0

@FrustratedWithFormsDesigner: No, no. 'ICommand' en este contexto es una interfaz mítica. Deberá crear esa interfaz y darle el nombre que prefiera. –

+0

@FrustratedWithFormsDesigner: Edité mi respuesta para que quede más claro. –

3

por lo general no les gusta cuerdas para este tipo de cosas - es demasiado fácil de conseguir en problemas con faltas de ortografía, los diferentes carcasas y similares - pero se supone que es por eso que desea utilizar una variable en lugar de cadena literal. Si las soluciones enum no son adecuadas, el uso de consts debería cumplir su objetivo.

EDITAR: Oct 28, 2013 para fijar una asignación incorrecta

class Program 
{ 
    private string Command; 

    const string command1 = "CommandOne"; 
    const string command2 = "CommandTwo"; 
    const string command3 = "CommandThree"; 
    const string command4 = "CommandFour"; 

    private static string[] Commands = { command1, command2, command3, command4 }; 

    static void Main(string[] args) 
    { 
     string Command = args[0]; 
     switch (Command) 
     { 
      case command1: //do something 
       break; 
      case command2: //do something else 
       break; 
      case command3: //do something totally different 
       break; 
      case command4: //do something boring 
       break; 
      default: //do your default stuff 
       break; 
     } 
    } 

    void DifferentMethod() 
    { 
     foreach (string c in Commands) 
     { 
      //do something funny 
     } 
    } 
} 
Cuestiones relacionadas