【发布时间】:2017-08-30 11:07:58
【问题描述】:
背景
Try<T>
我正在使用的应用程序使用Try<T> 类型来处理函数样式的错误。 Try<T> 实例表示值或错误,类似于Nullable<T> 表示值或null 的方式。在函数的范围内可能会抛出异常,但是将异常“冒泡”到更高级别的组件会被通过返回值“管道”它们所取代。
这是Try<T> 实现的要点。 Error 类基本等价于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
我正在处理的应用程序也是高度异步的,并且使用标准的async/await 习惯用法。代码库专门使用Task<T>,不使用普通的旧Task 或async void 方法。在您通常会看到Task 的地方,使用Task<FSharp.Core.Unit>。
正如你想象的那样,许多异步操作可能会出错,因此Task<Try<T>> 类型被大量使用。这工作正常,但会导致很多视觉混乱。由于 C# 7 现在允许 async 方法返回自定义等待类型,我想使用此功能创建一个有效的类 Task<Try<T>> 可以从 async 方法返回。
TryTask<T>
所以我创建了一个自定义的可等待任务类(它实际上将大部分功能委托给 Task<Try<T>> 字段)和一个随附的 AsyncMethodBuilder 类。
[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);
}
问题
为了使TryTask<T> 真正有用,我要做的第一件事是定义函数式let、bind, 和map 高阶函数,它们将“解包”值并对其执行操作。这是一个例子:
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;
}
此方法将无法编译,并出现错误CS0029:无法将类型Try<T2> 隐式转换为T2,在最后一行的符号result2 上。
如果我将最后一行更改为return result2.Value;,它将编译,但如果result2 有错误,那将无效。
问题
我怎样才能绕过这个错误并让这个类型作为async 方法的返回类型工作?在返回Task<T> 的典型async 方法中,您可以使用语句return default(T);,编译器会为您将T 包装在Task<T> 中。就我而言,我希望它在TryTask<T> 中包装Try<T>,但编译器希望它应该在某些东西中包装T。编译器使用什么方法来决定如何进行这种“包装”?
【问题讨论】:
-
您是否尝试将
T2的值从Try中提取出来并返回? -
这将编译,但它会忽略
result2.HasValue == false的潜力。必须从函数返回Try<T>,以便可以将错误通过管道传递给函数调用链中的后续函数。 -
Try<T2>和TryTask<T2>一样吗? -
为了与正常的
async语法保持一致,您的方法声明需要返回TryTask<Try<T2>>。当然,这与您想要的语法冲突,但根据错误消息,这似乎是编译器想要的。尽管如此,我对你重新实现Task<T>的愿望感到困惑。毕竟,Task<T>已经可以包含结果或错误。TryTask<T>添加了什么?也许这只是我的头,但我不明白这一点。 -
@PeterDuniho
Try<T>类型背后的想法是在操作的返回类型中包含错误的可能性,而不是“抛出”异常,其中“捕获”对于调用代码是可选的。在某种意义上Task<T>是类似的,但它有点不方便,因为它的错误处理被设计为适合标准异常抛出。我想要一个始终返回有效的Try<T>结果(可能包含错误)并且从不抛出异常的任务。这统一了任务内外的错误处理,默认系统保持独立。
标签: c# asynchronous functional-programming async-await task