2010-10-01 3 views
10

Я пытаюсь найти решение этой проблемы. Это мой пример кода:с использованием набора строк в операторе 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 
    } 
    } 
} 

Этот код не работает, потому что строковые значения в коммутаторе не являются константами. Я хочу написать легко поддерживаемый код.
Мне нравится использовать что-то вроде массива, потому что мне нужно использовать те же значения где-то еще в цикле.
С int-значениями перечисление было бы идеальным, но я не нашел маленького решения для одной и той же строки со строками.

+1

Большинство решений Предлагайте перечисления, но имена перечислений имеют специальные требования к именованию. Если это вызывает проблемы, вы можете привязать «DescriptionAttribute» к каждому элементу перечисления, чтобы содержать дружественные имена (которые могут иметь пробелы или что-то еще), и могли бы искать эти имена при повторении перечисления в 'DifferentMethod'. – Brian

+0

@Brian, хорошая точка, и для получения этого атрибута вам понадобится поле: 'FieldInfo enumField = typeof (Команды) .GetField (enumValue.ToString());' –

+0

@Kirk Woll: следующие две строки после этого : 'DescriptionAttribute da = (DescriptionAttribute) Attribute.GetCustomAttribute (enumField, typeof (DescriptionAttribute)); if (da.Description! = null) description = da.Description; ' – Brian

ответ

19

Преобразовать Commands в перечислении:

enum Commands { ComandOne, CommandTwo, CommandThree, CommandFour } 

заявление Переключатель должен выглядеть следующим образом:

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 
    } 
} 

И ваш последний метод должен выглядеть следующим образом:

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

Я рекомендую предварительно вычислить 'Enum.GetValues ​​(typeof (Commands))' и хранить где-нибудь. это не дешевая операция. – Andrey

+1

@ Андре: Я не согласен. DifferentMethod() занимает всего около дюжины микросекунд каждый раз на моей машине. Предварительное вычисление перечисления стоит сделать, если профилирование находит его узким местом, которое нуждается в исправлении, и я был бы подозрительным, если бы правильное решение состояло в том, чтобы иметь статическую предварительно рассчитанную копию перечисления. Я предпочел бы сохранить такие оптимизации до нужного (т. Е. Вы профилировались, и это было узким местом); это делает код немного сложнее, поскольку команды хранятся как в виде перечисления, так и в виде набора строк. – Brian

+0

Не Enum.Parse() внутренне делает переключатель на строке? Производительность мудрая, назвав только этот метод, будет столь же дорогостоящей, как оригинальный метод. – manixrock

2

Как вы сказали, в коммутаторе допускаются только постоянные выражения. Вы обычно делаете это, определяя enum и используете это в своем коммутаторе.

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; 
    } 
    } 
} 

Enum.GetValues Используйте для перечисления значений перечислений в DifferentMethod.

+0

+1 Перечисления, конечно же, подходят для коллекций, как заданных значений. – BoltClock

+0

'DifferentMethod' больше не работает после изменения ... – Heinzi

+0

@Heinzi: Как указывает Кирк, это то, что для' Enum.GetValues' для. – Brian

5

Только вчера я создал решение для этого. В вашем случае enum s лучше, но вот мое решение для общей ситуации непостоянного коммутационного случая.

использования:

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"); 

определение:

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

Определение Dictionary<string, enum> и карту команду ввода в соответствующее значение перед входом коммутатора. Если совпадение не найдено, происходит обработка по умолчанию.

8

легко исправить в вашей специфический пример:

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

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

Другие варианты:

  1. Инлайн струнам.

    переключатель (Command) { случай "CommandOne": ... случай "CommandTwo": ... }

  2. Используйте перечисление вместо этого, как говорит KirkWoll. Это, пожалуй, самое чистое решение.

  3. В более сложных сценариях использование поиска, такого как Dictionary<string, Action> или Dictionary<string, Func<Foo>>, может обеспечить лучшую выразительность.

  4. Если случаи сложны, вы можете создать интерфейс ICommand. Это потребует сопоставления командной строки с конкретной конкретной реализацией, для которой вы используете простые конструкции (коммутатор/словари) или причудливое отражение (найдите ICommand реализаций с этим именем или с определенным украшением атрибутов).

+3

+1. OP, вероятно, лучше с решением Кирка, но это ближе к тому, чтобы показать способ сделать именно то, о чем он просит. – Brian

1

Вы можете сделать это наоборот и достичь своей цели.

Используйте Enum и его вызов GetNames, чтобы получить строковый массив.

Enum.GetNames(typeof (*YOURENUM*)); 

Для получения дополнительной информации. http://msdn.microsoft.com/en-us/library/system.enum.getnames.aspx

1

Большие ответы здесь и, возможно, ответить на ваш вопрос лучше, чем то, что я хочу упомянуть ...

В зависимости от того, насколько сложным основана ваша логика, вы можете рассмотреть вопрос об использовании шаблона стратегии, как это:

Refactoring a Switch statement

или

The Strategy Template Pattern

Опять же, скорее всего, более сложной, чем просил ваше решение, просто выбросить его там ...

5

Вы могли бы устранить switch заявление в целом путем создания IYourCommand объектов и загружать их в 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, я раньше не знал об ICommand. Благодаря! – FrustratedWithFormsDesigner

+0

@FrustratedWithFormsDesigner: Нет, нет. «ICommand» в этом контексте - мифический интерфейс. Вам нужно будет создать этот интерфейс и дать ему любое имя, которое вы предпочитаете. –

+0

@FrustratedWithFormsDesigner: Я отредактировал свой ответ, чтобы сделать его более понятным. –

3

Я вообще не люблю строки для такого рода вещи - это слишком легко попасть в неприятность с опечатками, различными оболочками и тому подобными - но по-видимому, именно поэтому вы хотите использовать переменную вместо строкового литерала. Если решения enum не подходят, использование consts должно выполнить вашу задачу.

EDIT: 28 Окт, 2013, чтобы исправить неправильное назначение

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 
     } 
    } 
}