mmix
mmix

Reputation: 6278

Throwing exceptions from Linq Query exception

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

Answers (3)

mmix
mmix

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

qujck
qujck

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

Jon Skeet
Jon Skeet

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

Related Questions