如何实现可链接的异步扩展方法?

时间:2018-04-06 23:29:03

标签: c# .net asynchronous

我想知道如何编写可链接的异步扩展方法,而不需要调用者编写多个等待和嵌套括号。

实施例。假设您的目标是让调用者能够编写这种代码段:

var example = new MyCompilableClass();
await example.Compile().Run();  

(注意:我不是在编写编译器。我只是使用这些名称来说明一个必须先发生在另一个之前)。

为了支持上述内容,您需要创建两个接口:

public interface ICompilable
{
    Task<IRunnable> CreateExecutableImage();
}

public interface IRunnable
{
    Task Execute();
}

您将它们实现为异步:

class SourceCode : ICompilable
{
    public async Task<IRunnable> CreateExecutableImage()
    {
        await Stub.DoSomethingAsynchronous();
        return new ObjectCode();
    }
}

class ObjectCode : IRunnable
{
    public async Task Execute()
    {
        await Stub.DoSomethingAsynchronous();
    }
}

然后使用适当的类型约束编写两个扩展方法:

static class ExtensionMethods
{
    public static async Task<IRunnable> Compile<T>(this T This) where T : ICompilable
    {
        return await This.CreateExecutableImage();
    }

    public static async Task Run<T>(this T This) where T : IRunnable
    {
        await This.Execute();
    }
}

所以现在调用者尝试编译他的代码。但是我们在这一行上得到了一个错误:

await example.Compile().Run();  //Does not compile

以下是编译错误:

  

类型'System.Threading.Tasks.Task'不能在泛型类型或方法'ExtensionMethods.Run(T)'中用作类型参数'T'。没有从'System.Threading.Tasks.Task'到'Example.IRunnable'的隐式引用转换

我们可以用括号修复编译错误:

(await example.Compile()).Run();

......或两行代码:

var compiled = await example.Compile();
await compiled.Run();

......两者都有效。但是,如果您期待与LINQ一样的干净,可链接的语法,那似乎相当不幸。

是否有不同的方法来实现这些扩展方法,以便它们保持异步性质,但不需要丑陋的语法?

如果你想使用我的示例代码,这是一个Link to DotNetFiddle

1 个答案:

答案 0 :(得分:1)

一个简单的答案就是添加另一种将Task<T>转换为T的扩展方法,如下所示:

static class ExtensionMethods
{
    public static async Task Run<T>(this T This) where T : IRunnable
    {
        await This.Execute();
    }

    public static async Task Run<T>(this Task<T> This) where T : IRunnable
    {
        ////Await the task and pass it through to the original method
        await (await This).Execute();
    }
}

这将使调用者能够使用

await example.Compile().Run();

......虽然他可能不知道他将任务而不是结果传递给Run()(除非他真的考虑过它)。对他来说无关紧要。