#c# #arrays #string #switch-statement
#c# #массивы #строка #оператор 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
}
}
}
Этот код не работает, потому что строковые значения в switch не являются константами. Я хочу написать простой поддерживаемый код.
Мне нравится использовать что-то вроде массива, потому что мне нужно использовать те же значения где-то еще в цикле.
С int-значениями перечисление было бы идеальным, но я не нашел небольшого решения для того же самого со строками.
Комментарии:
1. Большинство решений предполагают перечисления, но к именам перечислений предъявляются особые требования. Если это вызывает проблемы, вы можете привязать
DescriptionAttribute
к каждому элементу перечисления, чтобы он содержал понятные имена (которые могут содержать пробелы или что-то еще), и могли бы искать эти имена при повторении перечисления внутриDifferentMethod
.2. @Brian, хорошая мысль, и для получения этого атрибута вам понадобится поле:
FieldInfo enumField = typeof(Commands).GetField(enumValue.ToString());
3. @Kirk Woll: следующие две строки после этого являются:
DescriptionAttribute da = (DescriptionAttribute)Attribute.GetCustomAttribute(enumField, typeof(DescriptionAttribute)); if (da.Description != null) description = da.Description;
Ответ №1:
Преобразовать Commands
в перечисление:
enum Commands { ComandOne, CommandTwo, CommandThree, CommandFour }
Оператор Switch должен выглядеть следующим образом:
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))
и где-нибудь сохранить. это недешевая операция.2. @Andrey: Я не согласен. DifferentMethod() занимает всего около дюжины микросекунд при каждом вызове на моем компьютере. Предварительное вычисление перечисления стоит выполнить, если профилирование обнаружит это как узкое место, которое нуждается в исправлении, и я был бы подозрителен, если бы правильным решением было иметь статическую предварительно вычисленную копию перечисления. Я бы предпочел сохранить такие оптимизации до тех пор, пока это не потребуется (т. Е. вы профилировали, и это было узким местом); это немного усложняет код, поскольку команды хранятся как в виде перечисления, так и в виде набора строк.
3. Разве Enum.Parse() внутренне не выполняет переключение строки? С точки зрения производительности вызов только этого метода был бы таким же дорогостоящим, как и исходный метод.
4. @manixrock, на случай, если ваш комментарий адресован мне, я хочу заверить вас, что мой ответ и предложение не были задуманы как микрооптимизация для повышения производительности кода операционной системы. Это было сделано для наглядности и безопасности статического типа.
5. @Брайан согласен. я должен был бы огорчиться: если это узкое место, то предварительно вычислите его.
Ответ №2:
Простое исправление в вашем конкретном примере:
switch(Array.IndexOf(Commands, Command))
{
case 0: ...
case 1: ...
default: //unknown command. Technically, this is case -1
}
Другие альтернативы:
-
Встроить строки.
switch(Команда) { регистр «CommandOne»: … пример «CommandTwo»: … }
-
Вместо этого используйте перечисление, как говорит КиркВолл. Это, вероятно, самое чистое решение.
-
В более сложных сценариях использование подстановки, такой как
Dictionary<string, Action>
илиDictionary<string, Func<Foo>>
, может обеспечить лучшую выразительность. -
Если случаи сложны, вы могли бы создать
ICommand
интерфейс. Для этого потребуется сопоставить командную строку с правильной конкретной реализацией, для чего вы используете простые конструкции (switch / dictionary) или необычное отражение (найдитеICommand
реализации с таким именем или с определенным оформлением атрибута).
Комментарии:
1. 1. В OP, вероятно, лучше использовать решение Кирка, но это ближе к тому, чтобы показать способ сделать именно то, о чем он просит.
Ответ №3:
Буквально вчера я создал решение для этого. В вашем случае enum
лучше, но вот мое решение для общей ситуации с неконстантным переключением.
обычаи:
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 amp;amp; x < 10, "small")
.Case(10, "ten")
.Default("big");
определение:
class Res<O, R>
{
public O value;
public bool succ;
public R resu<
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.resu<
}
}
static class Op
{
static public Res<O, R> Case<O, V, R>(this Res<O, R> o, V v, R r)
{
if (!o.succ amp;amp; 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 amp;amp; 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);
}
}
Ответ №4:
Вы могли бы полностью исключить 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();
}
Комментарии:
1. @FrustratedWithFormsDesigner: Нет, нет.
ICommand
в данном контексте это мифический интерфейс. Вам нужно будет создать этот интерфейс и присвоить ему любое имя, которое вы предпочитаете.2. @FrustratedWithFormsDesigner: Я отредактировал свой ответ, чтобы сделать его более понятным.
3. Ну ладно. Я думал, что там есть что-то новое и классное. Тем не менее, мне нравится этот дизайн. Действительно кажется лучшим выбором для конкретной задачи OP.
Ответ №5:
Обычно я не люблю строки для такого рода вещей — слишком легко попасть в беду из-за орфографических ошибок, разных оболочек и т.п. — но, по-видимому, именно поэтому вы хотите использовать переменную вместо строкового литерала. Если решения enum не подходят, использование consts должно достичь вашей цели.
РЕДАКТИРОВАТЬ: 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
}
}
}
Ответ №6:
Определите Dictionary<string, enum>
и сопоставьте команду ввода с соответствующим значением перед вводом переключателя. Если совпадение не найдено, выполняется обработка по умолчанию.
Ответ №7:
Как вы сказали, в switch разрешены только постоянные выражения. Обычно вы бы сделали это, определив enum
и используя это в своем switch.
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
для перечисления через значения enum в DifferentMethod
.
Комментарии:
1. Перечисления 1, безусловно, подходят для получения наборов похожих заданных значений.
2. @Heinzi: Как указывает Кирк, для этого
Enum.GetValues
и предназначен.3. @digEmAll, я изменил его на parse вместо приведения, хотя в основном ответом была демонстрация использования enum . Я не думаю, что нам нужно все излагать по буквам, люди тоже могут подумать сами.
4. Вы правы, люди могут думать самостоятельно, но нет ничего необычного в том, чтобы читать комментарии спрашивающих типа: «Я пробовал код, но он не компилируется». Поэтому я всегда предпочитаю писать код, который, по крайней мере, компилируется 😉
Ответ №8:
Вы можете сделать это наоборот и достичь своей цели.
Используйте Enum и его вызов GetNames, чтобы получить массив строк для перебора.
Enum.GetNames(typeof (*YOURENUM*));
Для получения дополнительной информации. http://msdn.microsoft.com/en-us/library/system.enum.getnames.aspx
Ответ №9:
Здесь отличные ответы и, вероятно, они лучше отвечают на ваш вопрос, чем то, что я собираюсь упомянуть…
В зависимости от того, насколько сложна ваша логика, вы можете рассмотреть возможность использования шаблона стратегии, подобного этому:
или
Опять же, скорее всего, это сложнее, чем предлагалось в вашем решении, просто выбросив его туда…