Reputation: 6278
lets say I have an input string I need to format into a list of KeyValuePair<string,float>
entries. The format of the input string is
key:value;key:value;...
lets say I have this Linq code to do it
var orr = from pco in (overrrides ?? string.Empty).Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries)
let pair = pco.Split(new char[] { ':' }, StringSplitOptions.RemoveEmptyEntries)
select new KeyValuePair<string, float>(pair[0], float.Parse(pair[1]));
Now, if the input string is not properly formated the linq will fail on two possible points, index out of range on pair[]
and format exception on float.Parse
. Both of these exceptions will bobble up and mean absolutely nothing to the caller.
I know I have two workarounds (not use linq and loop like its 1990s or grab above exceptions and repackage), however I was wondering if I can somehow inject validation steps into linq query itself to throw my own exceptions if I detect an anomaly (pair.length<2
or pair[1]
not a number)?
Upvotes: 1
Views: 5429
Reputation: 6278
I guess what I thought of doing is impossible since throw
, as statement, cannot be used in expression lambda. The only way is to create an external function with side-effect and run it through the let
or where
statement that will apply it to every entry.
var pairIsValidOrDie = new Func<string[], bool>(pair => {
float amt;
if (pair.Length != 2 || !float.TryParse(pair[1], out amt)) throw new ArgumentException("ERROR: invalid price override string);
return true;
});
var orr = from pco in (overrrides ?? string.Empty).Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries)
let pair = pco.Split(new char[] { ':' }, StringSplitOptions.RemoveEmptyEntries)
where pairIsValidOrDie(pair)
select new KeyValuePair<string, float>(pair[0], float.Parse(pair[1]));
Upvotes: 1
Reputation: 14580
You could test the string first with a regular expression
Regex r = new Regex(@"^((\w:\d);)*$");
bool test = r.IsMatch(tosplit);
Upvotes: 0
Reputation: 1500785
One simple option is to change it to:
// I don't think I'd use ?? like this, but that's not the point of the question.
var unparsed = (overrrides ?? string.Empty).Split(new char[] { ';' },
StringSplitOptions.RemoveEmptyEntries);
var parsed = unparsed.Select(x => ParsePair(x));
...
static KeyValuePair<string, float> ParsePair(string text)
{
// Note that you could be more efficient using IndexOf/Substring
string[] bits = text.Split(new char[] { ':' }, StringSplitOptions.RemoveEmptyEntries);
if (bits.Length != 2)
{
throw new ArgumentException("Value should be a colon-separated key/float pair");
}
float value;
if (!float.TryParse(bits[1], out value))
{
throw new ArgumentException("Cannot parse " + bits[1] + " as a float");
}
return new KeyValuePair<string, float>(bits[0], value);
}
You're still using LINQ for the "sequence" part - you're just breaking the "how to handle a single value" part into a separate method. (You could do it as a big statement lambda, but I wouldn't.) Note that by doing so, you could test the ParsePair
method independently.
(You might get away with just .Select(ParsePair)
, depending on which version of C# you're using. Method group conversions and type inference aren't the best of friends though.)
Upvotes: 3