C#7 - 为什么我不能从异步方法返回这个等待类型?

时间:2017-04-05 02:51:47

标签: c# asynchronous functional-programming async-await task

背景

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>,不使用普通的旧Taskasync 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>真正有用,我要做的第一件事是定义功能letbind,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。编译器使用什么方法来决定如何“包装”?

2 个答案:

答案 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()SomeNone

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 ); 的类型,那么您可能会觉得有用。