I'm trying to write a dynamic sort of command line p开发者_StackOverflow社区rocessor where I have a dictionary with keys being possible parameters, and the member being an Action where the string is the text between the parameters passed on the command line. Want to be able to add parameters just by adding the params array, and writing the action in the dictionary.
Yes I realize this is a pointless exercise in overcomplicating implementation to simplify maintenance. Mostly just trying to stress myself to learn more linq.
Here's my dictionary:
private static Dictionary<string[], Action<string>> _commandLineParametersProcessor = new Dictionary<string[], Action<string>>()
{
{
new string[] {"-l", "--l", "-log", "--log"},
(logFile) =>
{
_blaBla.LogFilePath = logFile;
}
},
{
new string[] { "-s", "--s", "-server", "--server" },
(server) =>
{
ExecuteSomething(server);
_blaBla.Server = server;
}
}
};
What's the most elegant mechanism to take string[] args and not just correlate the members that fall within any of the dictionary key arrays, but Aggregate((x,y) => string.Format("{0} {1}", x, y)) the sequence of elements (was thinking TakeWhile() fits in here somehow) inbetween the args[] members that would be Contain()ed in any of the keys arrays, and handing them into the action of the respective key's value member.
We have all written these little command line processors countless times, and while obviously a simple loop and switch is always more than adequate, this is again as I said an exercise trying to stress my linq skills. So please no complaints that I'm overengineering, that part is obvious.
Update: To make this maybe a little easier, here is a non-linq way of doing what I'm looking for (may be imperfect, this is just winging it):
Action<string> currentAction;
string currentActionParameter;
for(int i = 0; i < e.Args.Length; i++)
{
bool isParameterSwitch = _commandLineParametersProcessor.Keys.Any((parameterChoices) => parameterChoices.Contains(e.Args[i]));
if (isParameterSwitch)
{
if (!string.IsNullOrEmpty(currentActionParameter) && currentAction != null)
{
currentAction(currentActionParameter);
currentAction = null;
currentActionParameter = "";
}
currentAction = _commandLineParametersProcessor[_commandLineParametersProcessor.Keys.Single((parameterChoices) => parameterChoices.Contains(e.Args[i]))];
}
else
{
currentActionParameter = string.Format("{0} {1}", currentActionParameter, e.Args[i]);
}
}
This is not an altogether bad approach, I just wonder if anyone can maybe simplify it a little using linq or otherwise, though this may be the simplest form i guess..
Borrowing half of Adam Robinson's answer (+1 btw), but realizing that the Dictionary will never be accessed by key, and you just want to run the Actions instead of building up a string...
var inputCommands = args
.Select((value, idx) => new { Value = value, Group = idx / 2 })
.GroupBy(x => x.Group)
.Select(g => new
{
Command = g.First().Value,
Argument = g.Last().Value
}).ToList();
inputCommands.ForEach(x =>
{
Action<string> theAction =
(
from kvp in commands
where kvp.Key.Contains(x.Command)
select kvp.Value
).FirstOrDefault();
if (theAction != null)
{
theAction(x.Argument);
}
}
kvp.Key.Contains
really defeats the whole point of Dictionary. I'd re-design that to be a Dictionary<string, Action<string>>
. Then you could say
inputCommands.ForEach(x =>
{
if (commands.ContainsKey(x.Command))
{
commands[x.Command](x.Argument);
}
}
PS: I can recall much more obtuse C# code that I have written than this.
I must admit the possibility that you want to collect the actions, instead of running them. Here is that code:
var todo =
(
from x in inputCommands
let theAction =
(
from kvp in commands
where kvp.Key.Contains(x.Command)
select kvp.Value
).FirstOrDefault()
where theAction != null
select new { TheAction = theAction, Argument = x.Argument }
).ToList();
Assuming you know that every command has a corresponding argument (so 'args' will always be in the format of
cmd arg (repeated)
You could do something ridiculous like this...
var output = args.Select((value, idx) => new { Value = value, Group = idx / 2 })
.GroupBy(x => x.Group)
.Select(g => new
{
Command = commands.FirstOrDefault(kvp =>
kvp.Key.Contains(g.First().Value)).Value,
Argument = g.Last().Value
})
.Where(c => c.Command != null)
.Aggregate(
new StringBuilder(),
(builder, value) =>
{
builder.AppendLine(value.Command(value.Argument));
return builder;
}).ToString();
But that is, frankly, the most obtuse bit of C# that I can recall ever writing, and not a very good way to teach yourself LINQ. Nonetheless, it will do what you're asking.
EDIT
Just realized (thanks to David B) that your key is a string[]
, not just a string
, so I added some even more obtuse code that deals with that.
精彩评论