Try<T>
The application I'm working in uses a type Try<T>
to handle errors in a functional style. A Try<T>
instance represents either a value or an error, similar to how a Nullable<T>
represents a value or null
. Within the scope of functions exceptions may be thrown, but the "bubbling up" of exceptions to higher level components is replaced by "piping" them through return values.
Here is the gist of the Try<T>
implementation. The Error
class is basically equivalent to Exception
.
public class Try<T> {
private readonly T value;
private readonly Error error;
public bool HasValue { get; }
public T Value {
get {
if (!HasValue) throw new InvalidOperationException();
return value;
}
}
public Error Error {
get {
if (HasValue) throw new InvalidOperationException();
return error;
}
}
internal Try(Error error) {
this.error = error;
}
internal Try(T value) {
this.value = value;
HasValue = true;
}
}
public static class Try {
public static Try<T> Success<T>(T value) => new Try<T>(value);
public static Try<T> Failure<T>(Error error) => new Try<T>(error);
}
async
The application I'm working on is also heavily asynchronous, and uses the standard async
/await
idiom. The codebase exclusively uses Task<T>
, with no use of plain old Task
or async void
methods. Where you would normally see Task
, Task<FSharp.Core.Unit>
is used instead.
As you might imagine, many asynchronous operations may error, and so the type Task<Try<T>>
gets used a lot. This works fine, but results in a lot of visual clutter. Since C# 7 now allows async
methods returning custom awaitable types, I would like to use this feature to make a class that is effectively Task<Try<T>>
which can be returned from async
methods.
TryTask<T>
So I've created a custom awaitable task-like class (which really delegates most functionality to a Task<Try<T>>
field), and an accompanying AsyncMethodBuilder
class.
[AsyncMethodBuilder(typeof(TryTaskBuilder<>))]
public class TryTask<T>
{
private readonly Task<Try<T>> _InnerTask;
public TryTask(Func<Try<T>> function)
{
if (function == null) throw new ArgumentNullException(nameof(function));
_InnerTask = new Task<Try<T>>(function);
}
internal TryTask(Task<Try<T>> task)
{
_InnerTask = task;
}
public void Start() => _InnerTask.Start();
public TaskStatus Status => _InnerTask.Status;
public Try<T> Result => _InnerTask.Result;
public TaskAwaiter<Try<T>> GetAwaiter() => _InnerTask.GetAwaiter();
public void Wait() => _InnerTask.Wait();
}
public static class TryTask
{
public static TryTask<T> Run<T>(Func<Try<T>> function)
{
var t = new TryTask<T>(function);
t.Start();
return t;
}
public static TryTask<T> FromValue<T>(T value) => new TryTask<T>(Task.FromResult(Try.Success(value)));
public static TryTask<T> FromError<T>(Error error) => new TryTask<T>(Task.FromResult(Try.Failure<T>(error)));
public static TryTask<T> FromResult<T>(Try<T> result) => new TryTask<T>(Task.FromResult(result));
public static TryTask<T> FromTask<T>(Task<Try<T>> task) => new TryTask<T>(task);
}
public class TryTaskBuilder<T>
{
private AsyncTaskMethodBuilder<Try<T>> _InnerBuilder;
public TryTaskBuilder()
{
_InnerBuilder = new AsyncTaskMethodBuilder<Try<T>>();
}
public static TryTaskBuilder<T> Create() =>
new TryTaskBuilder<T>();
public TryTask<T> Task =>
default(TryTask<T>);
public void Start<TStateMachine>(ref TStateMachine stateMachine)
where TStateMachine : IAsyncStateMachine =>
_InnerBuilder.Start(ref stateMachine);
public void SetStateMachine(IAsyncStateMachine stateMachine) =>
_InnerBuilder.SetStateMachine(stateMachine);
public void SetResult(Try<T> result) =>
_InnerBuilder.SetResult(result);
public void SetException(Exception exception) =>
_InnerBuilder.SetResult(exception.AsError<T>());
public void AwaitOnCompleted<TAwaiter, TStateMachine>(ref TAwaiter awaiter, ref TStateMachine stateMachine)
where TAwaiter : INotifyCompletion
where TStateMachine : IAsyncStateMachine =>
_InnerBuilder.AwaitOnCompleted(ref awaiter, ref stateMachine);
public void AwaitUnsafeOnCompleted<TAwaiter, TStateMachine>(ref TAwaiter awaiter, ref TStateMachine stateMachine)
where TAwaiter : ICriticalNotifyCompletion
where TStateMachine : IAsyncStateMachine =>
_InnerBuilder.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine);
}
To make TryTask<T>
really useful, the first thing I want to do is define functional let
, bind,
and map
higher-order functions that will "unwrap" values and perform operations with them. Here is an example:
public async static TryTask<T2> Bind<T1, T2>(
this TryTask<T1> source,
Func<T1, Try<T2>> binding)
{
Try<T1> result1 = await source;
Try<T2> result2 = result1.HasValue
? binding(result1.Value)
: Try.Failure<T2>(result1.Error);
return result2;
}
This method will not compile, with the error CS0029: Cannot implicitly convert type Try<T2>
to T2
on the symbol result2
in the last line.
If I change the last line to return result2.Value;
it will compile, but that will not be valid if result2
has an error.
How can I get around this error and get this type to work as the return type of async
methods? In typical async
methods returning Task<T>
, you can use the statement return default(T);
and the compiler will wrap that T
in a Task<T>
for you. In my case, I want it to wrap a Try<T>
in a TryTask<T>
, but the compiler expects it should wrap a T
in something. What method does the compiler use to decide how to do this "wrapping"?
If I understand this correctly (which is kind of hard without a specification), the root problem is type inference for async
lambdas, as described here by Lucian Wischik, the original author of the tasklike proposal.
In your case, that would mean something like:
void F<T>(Func<TryTask<T>> func) { }
F(async () => Try.Success(42));
The lambda returns Try<int>
and you want the compiler to somehow figure out from that that the type of the lambda should be Func<TryTask<int>>
. But according to the document linked above, there is no good way to do that.
This is not an issue with your Bind
, but the language designers chose to have methods and lambdas behave consistently, rather than making methods stronger.
So, as far as I know, what you want to do is not possible. You might consider sharing your use case with the designers of C# by creating an issue at the csharplang repo, maybe someone will figure out how to resolve the issues and to make this work in a future version of C#.
The accepted answer is correct, but you may want to consider the language-ext library that has a delegate based Try
implementation, as well as Async
variants for all of its extension methods, and a ToAsync()
extension that converts a Try<A>
to a TryAsync<A>
. It also has TryOption<A>
and TryOptionAsync<A>
for returning either a Some
, None
, or Fail
.
// Example of an action that could throw an exception
public Try<int> Foo() => () => 10;
// Synchronous Try
var result = Foo().IfFail(0);
// Synchronous Try
var result = Foo().Match(
Succ: x => x,
Fail: e => 0
);
// Asynchronous Try
var result = await Foo().IfFailAsync(0);
// Asynchronous Try
var result = await Foo().MatchAsync(
Succ: x => x,
Fail: e => 0
);
// Manually convert a Try to a TryAsync. All operations are
// then async by default
TryAsync<int> bar = Foo().ToAsync();
// Asynchronous Try
var result = await bar.IfFail(0);
// Asynchronous Try
var result = await bar.Match(
Succ: x => x,
Fail: e => 0
);
It has a massive selection of functional types which if you're implementing types like Try
you may find useful.
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