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
。< / p>
如果我将最后一行更改为return result2.Value;
,它将进行编译,但如果result2
出错,则无效。
如何解决此错误并使此类型作为async
方法的返回类型?在返回async
的典型Task<T>
方法中,您可以使用语句return default(T);
,编译器会将T
包裹在Task<T>
中。在我的例子中,我希望它将Try<T>
包装在TryTask<T>
中,但编译器期望它应该包含T
。编译器使用什么方法来决定如何“包装”?
答案 0 :(得分:4)
如果我理解正确(没有规范就很难),根本问题是async
lambdas的类型推断,as described here由任务类提议的原作者Lucian Wischik提出。
在你的情况下,这意味着:
void F<T>(Func<TryTask<T>> func) { }
F(async () => Try.Success(42));
lambda返回Try<int>
,你希望编译器以某种方式弄清楚lambda的类型应该是Func<TryTask<int>>
。但根据上面链接的文件,没有好办法。
这对你的Bind
来说不是问题,但是语言设计师选择让方法和lambda行为一致,而不是让方法更强。
所以,据我所知,你想做的事情是不可能的。您可以考虑通过在the csharplang repo创建问题来与C#的设计人员分享您的用例,也许有人会想出如何解决问题并使其在未来的C#版本中运行。
答案 1 :(得分:2)
接受的答案是正确的,但您可能需要考虑language-ext library delegate based Try
implementation以及所有扩展方法的Async
变体,以及Try<A>
1}}将TryAsync<A>
转换为TryOption<A>
的扩展名。它还有TryOptionAsync<A>
和massive selection of functional types,可以返回ToAsync()
,Some
或None
。
Fail
它有一个https://gist.github.com/fjfish/3024308如果您正在实施类似// 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
);
的类型,那么您可能会觉得有用。