Пишу консольное приложение которое должно принимать более 5 параметров. При этом есть обязательные параметры есть не обязательные, одни параметры должны работать только с другими и т.д.. Я уже погряз в различных if..else if..else. Как облегчить себе жизнь? Есть ли рекомендации и стандартные приёмы по работе с аргументами командной строки?
Ответ
Если параметров много, лучше всего вынести их разбор в отдельный класс или набор классов. Когда if/else образуют сложную структуру, можно использовать «повышенную декларативность» C# и описывать структуру команд, например, так:
var commandLinePattern = Command.WithName('add')
.HasOption('-fileName')
| Command.WithName('help')
.HasParameter("CommandName");
Конечно, это потребует определённой изобретательности при проектировании, так что ниже изложу подробнее.
Следующим шагом мы опишем наши команды:
public interface ICommand
{
void Run();
}
public HelpCommand : ICommand
{
public string CommandName { get; set; }
public bool Verbose { get; set; }
public int PageSize { get; set; }
public void Run()
{
if (CommandName == "add")
{
Console.WriteLine("Help on the ADD command");
}
. . .
}
}
Шаблон командной строке (переменная commandLinePattern в первом примере) можно применить к конкретной строке параметров, чтобы построить экземпляр конкретного класса команды, и заполнить его свойства, использую рефлексию:
var command = commandLinePattern.Parse(args);
command.Run();
UPDATE
Итого, получается приблизительно такой код:
static class Program
{
private static readonly commandLinePatterm = Command.WithName('add')
.HasOption('-fileName')
.HasOption('-move')
| Command.WithName('help')
.Parameter("CommandName");
static void Main(string[] args)
{
var command = commandLinePattern.Parse(args);
command.Run();
}
}
UPDATE «для новичка»
Немного о том, как реализовать всю эту красоту. Наше чудо по внешнему виду и внутреннему устройству очень напоминает регулярные выражения: сначала мы описываем некий шаблон аргументов командной строки, и затем применяем его к реальным аргументам. Результатом такого применения будет объект, который можно будет выполнить. Назовём его командой
Название команда возникло не просто так — это один из паттернов проектирования, описанный в классическом труде банды четырёх.
Я не знаю, какие командные строки вам приходится разбирать, поэтому сделаю несколько предположений:
Программа может выполнить за один запуск одну и только одну команду. Команда идёт первой в командной строке. Команда не требует префиксов, таких как дефис или косая черта.
За командой могут следовать один или несколько параметров. Параметры могут быть именованными и безымянными. Именованные параметры имеют префикс, например, дефис. Именованные параметры могут иметь значение, в этом случае оно отделяется от имени двоеточием.
Приблизительно такие правила используются в большом количестве утилит, например, в архиваторах pkzip, arj и rar; в утилитах контроля версий git и hg — то есть этих двух правил действительно хвататет для широкого спектра задач.
arj add archive *.c -m0
arj — это имя программы, add — команда, archive (имя архива) и *.c (что архивировать) — безымянные параметры; -m — именованный параметр (опция), 0 — значение параметра.
В arj значение параметра не требуется отделять символом : или =. Мы пойдём другим путём, исключительно для из-за того, что пример демонстрационный.
Такой шаблон параметра командной строки может быть описан простым классом:
public class CommandLinePattern
{
public string Name { get; set; }
public List
public List
Например, команда help может иметь такой шаблон
program Help CommandName [-Verbose] [-PageSize:num]
Которому соответствует экзмепляр класса CommandLinePattern
var pattern = new CommandLinePattern
{
Name = "Help",
Parameters = new List
Нам нужно решить две задачи: построить такой шаблон, и применить его. В коде выше мы воспользовались статическим классом Command для того, чтобы начать конструировать шаблон:
public static class Command
{
public static CommandLinePattern WithName(string name)
{
return new CommandLinePattern
{
Name = name,
Parameters = new List
Остальные методы конструирования разместим непосредственно в классе CommandLinePattern
public CommandLinePattern HasOption(string name)
{
Options.Add(name);
return this;
}
public CommandLinePattern HasParameter(string name)
{
Parameters.Add(name);
return this;
}
Благодаря конструкции return this; мы можем строить объект последовательно, увязывая методы в цепочку:
var pattern = Command.WithName("Help")
.HasParameter("CommandName")
.HasOption("Verbose")
.HasOption("PageSize");
Таким образом мы описываем шаблоны команды. Теперь решим вторую задачу — разбор командной строки и создание объекта-команды.
В классе CommandLinePattern реализуем метод TryParse, который будет пробовать разобрать командную строку и в случае успеха создавать объект, реализующий такой интерфейс ICommand
public virtual bool TryParse(string[] args, out ICommand result)
{
result = null;
// Конечно, наш шаблон не может соответствовать пустой командной строке.
if (args.Length == 0)
return false;
// И он не может соответствовать какой-то другой команде.
if (args[0] != Name)
return false;
var properties = new Dictionary
for (int i = 1; i < args.Length; i++)
{
if (args[i].StartsWith("-"))
{
var parameterWithoutHyphen = args[i].Substring(1);
var nameValue = parameterWithoutHyphen.Split(':');
if (nameValue.Lenth == 1)
properties.Add(nameValue[0], null);
else
properties.Add(nameValue[0], nameValue[1]);
}
else
{
var name = Parameters[nextParameterIndex];
nextParameterIndex++;
var value = args[i];
properties.Add(name, value);
}
}
// Для команды с имененем Help мы найдём класс HelpCommand:
var className = Name + "Command";
var type = Type.GetType(className);
// И создадим его экземпляр:
result = (ICommand)Activator.CreateClass(type);
// Теперь значения всех параметров запишем в свойства
// только что созданного экземляра:
foreach (var property in properties)
{
var name = property.Key;
var value = property.Value;
type.GetProperty(name)
.SetValue(result, value);
}
return true;
}
public virtual ICommand Parse(string[] args)
{
ICommand result;
if (TryParse(args, out result))
return result;
throw new FormatException();
}
Имейте в виду, что такая запись значений будет работать только со строковыми свойствами — в реальной программе вам потребуется конвертировать строковые значения в типы свойств.
Теперь у нас уже всё готово. Мы можем написать наш разбор параметров, но «для красоты» сделаем ещё один финальный штрих.
Создадим класс, который позволит объединить два шаблона и проверять сначала левый, а затем правый:
public class OrCommandLinePattern : CommandLinePattern
{
private readonly CommandLinePattern left;
private readonly CommandLinePattern right;
public OrCommandLinePattern(CommandLinePattern left, CommandLinePattern right)
{
this.left = left;
this.right = right;
}
public override bool TryParse(string[] args, out ICommand result)
{
if (left.TryParse(args, result))
return true;
return right.TryParse(args, result);
}
}
В базовый класс добавим реализацию оператора ИЛИ
public static CommandLinePattern operator |(CommandLinePattern left, CommandLinePattern right)
{
return new OrCommandLinePattern(left, right);
}
Теперь мы можем объединить несколько паттернов с помощью вертикальной черты:
var commandLinePattern = Command.WithName('add')
.HasOption('-fileName')
| Command.WithName('help')
.HasParameter("CommandName");
И в конце запускаем парсинг с помощью вызова одного единственного метода:
var command = commandLinePattern.Parse(args);
Результатом работы парсера будет экземпляр класса, реализующего интерфейс ICommand с заполненными свойствами. Нам остаётся только запустить его:
command.Run();
Комментариев нет:
Отправить комментарий