A data flow helper class
One problem I encounter when processing lists is exception handling. I prefer to write code that "chains" calls transforming the data:
var results = list .Select(DoThing1) .Select(DoThing2) // ... .Select(DoThingN) .ToList();
The problem with something like this is that, if any of the calls throws an exception, processing stops for the whole list. Handling that requires that I move the "chain" to a new method and handle exceptions there:
var results = list.Select(InnerMethod).ToList(); // ... private ResultN InnerMethod(Input input) { try { var r1 = DoThing1(input); var r2 = DoThing2(r1); // ... var rn = DoThingN(rn_1); return rn; } catch(Exception ex) { // do something with ex, like logging return ?? // can't throw, I want to continue processing the rest of the list } }
Now I have two problems :) One is that the code just looks uglier, so maybe most people can ignore that. (I have OCD with regards to this - code that "looks bad" drives me nuts.) The more important issue is that I need to decide on an "empty" ResultN value to return from the inner method and then I need to be able to filter those out of the overall results. That can get ugly really quickly.
By analogy with what I read about other languages (I think GO uses this approach - I've never studied the language but I believe I first encountered the idea in some articles about it), I decided to write an "either a good value or an exception" helper class. On further reflection I changed that to a struct
because I don't want to check that the value is not null
. Once I thought of that I also decided that null
is not a "good value", so any attempt to pass it as such will result in an ArgumentNullException
instead in the "or an exception" part. I hope things will become clearer from the code:
public struct Result<T> { public bool HasValue { get; } public T Value { get { if (!HasValue) throw Exception; return value; } } public Exception Exception => HasValue ? null : exception ?? NULL_EXCEPTION; public Result(T value) { // do not accept null if (value == null) { HasValue = false; this.value = default(T); exception = new ArgumentNullException(); } else { HasValue = true; this.value = value; exception = null; } } public Result(Exception ex) { HasValue = false; value = default(T); exception = ex; } // // ReSharper disable once StaticMemberInGenericType private static readonly Exception NULL_EXCEPTION = new ArgumentNullException(); private readonly T value; private readonly Exception exception; }
I also added an Apply
extension method - the reason it's an extension method instead of a method in the original struct
is because of the additional generic type TR
; it just looked wrong there. (I did mention my OCD, right?)
public static class ResultExtensions { public static Result<TR> Apply<T, TR>(this Result<T> it, Func<T, TR> selector) { return it.HasValue ? Try(() => selector(it.Value)) : new Result<TR>(it.Exception); } public static IEnumerable<Result<TR>> Select<T, TR>(this IEnumerable<Result<T>> list, Func<T, TR> selector) { return list.Select(it => it.Apply(selector)); } // private static Result<TR> Try<TR>(Func<TR> func) { try { return new Result<TR>(func()); } catch (Exception ex) { return new Result<TR>(ex); } } }
While I didn't write this in a TDD fashion, I have added some asserts to a console application to make sure I got back the expected results:
static class Program { static void Main() { var r1 = Divide(5, 2); Debug.Assert(Print(r1) == "HasValue = True Value = 2 Exception = "); var r2 = Divide(5, 0); Debug.Assert(Print(r2) == "HasValue = False Value = (invalid) Exception = Attempted to divide by zero."); var r3 = new Result<int?>(3); Debug.Assert(Print(r3) == "HasValue = True Value = 3 Exception = "); var r4 = new Result<int?>((int?) null); Debug.Assert(Print(r4) == "HasValue = False Value = (invalid) Exception = Value cannot be null."); var r5 = new Result<object>(null); Debug.Assert(Print(r4) == "HasValue = False Value = (invalid) Exception = Value cannot be null."); // using the default constructor var r6 = new Result<int>(); Debug.Assert(Print(r6) == "HasValue = False Value = (invalid) Exception = Value cannot be null."); // trying to access the Value property without checking first will result in an exception try { Console.WriteLine(r5.Value); } catch { Console.WriteLine("Oops."); } // we can now chain selectors // case 1: all good var i1 = new Result<int?>(5); var ri1 = i1 .Apply(it => 100 / it) .Apply(it => 200 / it) .Apply(it => 10 / it); Debug.Assert(Print(ri1) == "HasValue = True Value = 1 Exception = "); // case 2: something bad happens var i2 = new Result<int>(200); var ri2 = i2 .Apply(it => 100 / it) .Apply(it => 200 / it) .Apply(it => 10 / it); Debug.Assert(Print(ri2) == "HasValue = False Value = (invalid) Exception = Attempted to divide by zero."); // finally, the target use case: processing a list without aborting due to exceptions var list1 = new List<int> { 10, 20, 0, 30, 40 }; var list2 = list1 .Select(it => new Result<int>(it)) .Select(it => it / 2) .Select(it => 10 / it) .Select(it => 100 / it) .ToList(); var good = list2.Where(it => it.HasValue).ToList(); var bad = list2.Where(it => !it.HasValue).ToList(); Debug.Assert(good.Count == 2); Debug.Assert(bad.Count == 3); } private static Result<int> Divide(int a, int b) { try { return new Result<int>(a / b); } catch (Exception ex) { return new Result<int>(ex); } } private static string Print<T>(Result<T> r) { return $"HasValue = {r.HasValue} Value = {(r.HasValue ? r.Value + "" : "(invalid)")} Exception = {r.Exception?.Message}"; } }
Note that the addition of the Select
extension method, I didn't have to write the last example as
var list2 = list1 .Select(it => new Result<int>(it)) .Select(it => it.Apply(x => x / 2)) .Select(it => it.Apply(x => 10 / x)) .Select(it => it.Apply(x => 100 / x)) .ToList();
Avoiding boilerplate code is good; so is the fact that the inner lambda doesn't have to know anything about the Result<T>
type and yet, any crash in it doesn't abort processing the entire list.
Comments