In C#, I would like to have the option to handle errors in a more “functional” way, rather than always using the default exception-throwing model. In some scenarios, the throwing model is great, because it allows you to force your code to stop executing if something unexpected happens. However, the throwing model has several major drawbacks:
return
or throw
statement. If control returned to the caller from a throw
statement, control will immediately move to its caller if there is no appropriate try
/catch
in scope; whereas if control returned from a return
statement, control continues as normal. I know the implementation is way more complicated and subtle than that, but I think that’s an appropriate conceptual summary. This double system can be generally confusing, both at design time and runtime in different ways.System.Threading.Tasks.Task
. Any exceptions thrown by a Task
executing are stored in an AggregateException
and accessed through the Task.Exception
property by the calling code. So while the Task
s execution may be aborted, the calling code must look for errors stored in object properties, using normal C# control flow.int Divide(int x, int y)
could result in either an integer or a DivideByZeroException
, but the signature of this method does not suggest anything regarding errors. Conversely, I’ve often heard complaints about Java’s checked exceptions, where every specific exception type a method can throw must be added to its signature, which can get very wordy. To me the simplest middle ground would be a generic type like Nullable<T>
that contains either a value or an exception. A method like Divide
would then have this signature: Fallible<int> Divide(int x, int y)
. Any operations using the result would then need to deal with the error case. Methods could also take Fallible
parameters to allow easier chaining.Here is an implementation of Fallible
I sketched out:
public class Fallible<T> : IEquatable<Fallible<T>> {
#region Constructors
public Fallible() {
//value defaults to default(T)
//exception defaults to null
}
public Fallible(T value) : this() {
this.value = value;
}
public Fallible(Exception error) : this() {
if (error == null) throw new ArgumentNullException(nameof(error));
Error = error;
}
public Fallible(Func<T> getValue) : this() {
if (error == null) throw new ArgumentNullException(nameof(getValue));
try {
this.value = getValue();
}
catch(Exception x) {
Error = x;
}
}
#endregion
#region Properties
public T Value {
get {
if (!HasValue) throw new InvalidOperationException("Cannot get Value if HasValue is false.");
return value;
}
}
private T value;
public Exception Error { get; }
public bool HasValue => Error == null;
#endregion
#region Equality
public bool Equals(Fallible<T> other) => (other != null)
&& Equals(Error, other.Error)
&& Equals(Value, other.Value);
public override bool Equals(object obj) => Equals(obj as Fallible<T>);
public static bool operator ==(Fallible<T> a, Fallible<T> b) {
if (a == null) return b == null;
return a.Equals(b);
}
public static bool operator !=(Fallible<T> a, Fallible<T> b) {
if (a == null) return b != null;
return !a.Equals(b);
}
public override int GetHashCode() =>
HasValue
? Value.GetHashCode()
: Error.GetHashCode();
#endregion
public override string ToString() =>
HasValue
? $"Fallible{{{Value}}}"
: $"Fallible{{{Error.GetType()}: {Error.Message}}}";
}
And the questions:
Fallible<T>
for .NET? Perhaps a class in the Task Parallel Library, Reactive Extensions, or the F# core libraries?Discriminated Unions are a functional programming convenience that indicates that something is one of several different types of objects. For example, a User might be an unauthenticated user, a regular user, or an administrator.
In Visual Basic, errors fall into one of three categories: syntax errors, run-time errors, and logic errors.
Union types are common in functional languages, but have yet to make it to C# and similar. These types solve a simple and very common problem, data that is one of a finite set of possibilities.
There isn't anything like this built into the BCL, though FSharp.Core does include support for options, which provide another mechanism by which you can avoid exception handling.
The Language Ext project does contain a Try<T>
type which is very similar to what you're describing. In addition, it has a C# friendly Option<T>
with pseudo pattern matching for operations on the options.
The https://github.com/mcintyre321/OneOf project is a C# library intended to imitate F# discriminated unions in a generic way. You could use that to implement your own Result
type as a OneOf<ResultData, ErrorData>
(where you've defined the ResultData
and ErrorData
classes with whatever specific things your data model needs).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With