This was inspired by a conversation in chat, that started with the discussion of C#7.0 tuples and out parameter declarations, which led to the idea that there is no 'good1' way to return an error state in C# without throwing an exception.
Out of curiosity, I wondered what it would take to design a type that was transparent to the developer, but allowed them to safely return exceptions without having to unwind the stack.
For those who don't know, when you throw
/catch
Exception
objects in C# (or VB.NET, F#, any .NET language follows the same requirements), the most expensive part tends to be the stack. Throwing an exception is cheap, but catch
the exception and the stack has to unwind and reflect against itself to give you the information you need. This is by-design, of course. The language and framework designers wanted exceptions to mean that the program entered an 'exceptional state', that is, there is an issue that needs resolved.
The problem is that some methods don't really need to throw an exception on error, they could, instead, just return a pass/fail and then fill an out
parameter. The other option is to return a Tuple<bool, T>
, where T
is the return type.
Of course, this doesn't give us the ability to return an Exception
, just pass/fail. Sometimes we may want to return what went wrong.
So, alas, I get to the Failable<T>
struct that I created today. By including implicit
conversions to and from T
and Exception
, it allows us to simply return Exception
instead of throwing, creating a much cheaper management of error states.
The only caveat to this approach from a usability standpoint, is that one does not simply define an implicit conversion from null
. This means that Failable<string> value = null;
is invalid, but Failable<string> value = (Failable<string>)null;
is, as well as Failable<string> value = new Failable<string>(null);
.
If the framework/language designers ever open up implicit
conversions from null
, then this struct would be completely transparent.
public struct Failable<T> : IEquatable<Failable<T>>
{
public Exception Exception { get; }
public T Result { get; }
public bool Passed { get; }
private Failable(Exception exception, T result, bool passed)
{
Exception = exception;
Result = result;
Passed = passed;
}
public Failable(Exception exception)
: this(exception, default(T), false)
{
}
public Failable(T result)
: this(null, result, true)
{
}
public static implicit operator Failable<T>(Exception exception) => new Failable<T>(exception);
public static implicit operator Failable<T>(T result) => new Failable<T>(result);
public static implicit operator Exception(Failable<T> result) => result.Exception;
public static implicit operator T(Failable<T> result) => result.Result;
public override string ToString() => (Passed ? Result?.ToString() : Exception?.ToString()) ?? "null";
public override int GetHashCode() => Exception.GetHashCode() ^ Result.GetHashCode();
public bool Equals(Failable<T> other) => this == other;
public override bool Equals(object obj) => obj is Failable<T> && this == (Failable<T>)obj;
public static bool operator ==(Failable<T> a, Failable<T> b) => a.Exception == b.Exception && a.Result.Equals(b.Result);
public static bool operator !=(Failable<T> a, Failable<T> b) => a.Exception != b.Exception || !a.Result.Equals(b.Result);
public static readonly Failable<T> Empty = new Failable<T>();
}
Now, to demonstrate how this works I defined a very ugly method, so do not review it please, that goes through all the possible features of this struct:
static Failable<T> FailableTest<T>(bool pass, bool nullOrThrow, T result) { try { if (pass) { if (nullOrThrow) { // Both options are valid: // return new Failable<T>(null); return (Failable<T>)null; } else { return result; } } else { if (nullOrThrow) { throw new ArgumentException($"Throwing as expected, {nameof(pass)}:'{pass}', {nameof(nullOrThrow)}:'{nullOrThrow}'."); } else { return new ArgumentException($"Returning as expected, {nameof(pass)}:'{pass}', {nameof(nullOrThrow)}:'{nullOrThrow}'."); } } } catch (Exception e) { return e; } }
Our test cases are something on the order of:
Console.WriteLine("Pass : " + FailableTest(true, false, "1. String on pass").ToString()); Console.WriteLine("Fail : " + FailableTest(false, false, "2. String on pass").ToString()); Console.WriteLine("Null : " + FailableTest(true, true, "3. String on pass").ToString()); Console.WriteLine("Throw : " + FailableTest(false, true, "4. String on pass").ToString()); Console.WriteLine("Cast : " + (FailableTest(true, false, 15) - FailableTest(true, false, 5)));
Returning:
Pass : 1. String on pass Fail : System.ArgumentException: Returning as expected, pass:'False', nullOrThrow:'False'. Null : null Throw : System.ArgumentException: Throwing as expected, pass:'False', nullOrThrow:'True'. at GenericFailableTest.Program.FailableTest[T](Boolean pass, Boolean nullOrThrow, T result) in c:\users\ebrown\documents\visual studio 2017\Projects\Test CSharp Projects\GenericFailableTest\Program.cs:line 44 Cast : 10
The interesting thing about this is that the implicit operator T
operator allows you to just ignore this class altogether:
var str = FailableTest(true, false, "Some String");
And this is by design, which brings me to my main list of questions:
- Should there be a
T(Failable<T>)
operator? If so, should it beimplicit
? - Should there be an
Exception(Failable<T>)
operator? If so, should it beimplicit
? - Should the API include a
Failable(Tuple<bool, T>)
constructor that allows one to pass a tuple of(pass, value)
? A proper definition might be:
static Failable<int> TryParse(string input) { int result; if (int.TryParse(input, out result)) { return result; } else { return new ArgumentException($"The string '{input}' was not a valid integer."); } } var parseResult = new Failable<int>(); while (!parseResult.Passed) { parseResult = TryParse(Console.ReadLine()); } var value = parseResult.Result; // Do something with `value`
1: The term 'good' here is subjective, there are two major alternatives to throwing exceptions already present in the language:
1. Use out
parameters;
2. Return a Tuple
;
Either of these are 'good' in certain lights; I'm simply attempting to draw out another possible alternative.
if
s everywhere. Your implementation is just a simple beginning. \$\endgroup\$int.TryParse
method is used in above example, where you check thePassed
state instantly, as soon as method returns and work only withResult
from there. But once you start passing thoseFailable
-s up the stack (as normal exception would go) I can see it becoming a problem. \$\endgroup\$IEnumerable
of failables/results and only some of them failed and the others not or when theT
itself is anIEnumerable
. \$\endgroup\$Result<T, TErr>
and does just fine. Granted, it's used everywhere in the std lib so it has much more usage than this would in C#. \$\endgroup\$