【问题标题】:Async method returning Task<T> with generic constraint in C#在 C# 中返回具有通用约束的 Task<T> 的异步方法
【发布时间】:2013-09-04 22:28:54
【问题描述】:

我在a project I'm working on 中实现了一个命令模式。这几乎是当前的结构:

public class Response
{
    public bool Success { get; private set; }

    public static Response CreateErrorResponse()
    {
        return new Response { Success = false };
    }
}

public interface ICommand<T> where T : Response
{
    Task<T> ExecuteAsync();
}

public abstract CommandBase : ICommand<T> where T: Response
{
    protected abstract Uri BuildUrl();
    protected abstract Task<T> HandleResponseAsync();

    public async override Task<T> ExecuteAsync()
    {
        var url = BuildUrl();
        var httpClient = new HttpClient();

        var response = await httpClient.GetAsync(url);
        return await HandleResponseAsync(response);
    }
}

我想处理 HttpClient 可能抛出的任何异常,所以我想将 CommandBase.ExecuteAsync 更改为类似这样的内容...

public async override Task<T> ExecuteAsync()
{
    var url = BuildUrl();
    var httpClient = new HttpClient();

    try
    {
        var response = await httpClient.GetAsync(url);
        return await HandleResponseAsync(response);
    }
    catch (HttpRequestException hex)
    {
        return Response.CreateErrorResponse(); // doesn't compile
    }
}

我得到的编译错误是“无法将类型响应转换为异步返回类型 T”。如in this question 所述,我不能使用T.CreateErrorResponse()

我该如何解决这个问题?

编辑反对票:无论您是否同意在这样的库中捕获异常,问题仍然存在!

【问题讨论】:

  • CreateErrorResponse() 的类型为 IResponse,但应为 Response
  • 这背后的想法是什么?你可以让它抛出异常,它会自动聚合到等待者。
  • @ClausJørgensen 是的。但是在这种特定情况下,IMO 将 HttpRequestExceptions 与库正在包装的 API 上的错误相同对待更为简洁,因为调用可能由于其他原因(服务过载、404 等)而失败。如果消费者想要异常,我将添加一个标志来禁用/启用该行为。
  • 然后包装异常。你不应该像这样包装异常,这是一个非常糟糕的 API 设计。使用任务时,您可以显式检查 .Error 属性以在异常失败时获取异常。您通过像这样包装它来防止消费者正确使用任务。
  • 我理解您的理由 - 关注点分离,单一职责 - 但我正在开发一个与我的库并行的应用程序。与我的图书馆一起工作,我发现自己想要这个作为一种选择。客户端代码已经检查了从库返回的 Response 对象的 Success 属性,因此不需要到处都需要 try/catch 代码会更整洁。这也是我打算这样做的唯一情况。提到 Tasks 的 Error 属性是什么意思?

标签: c# generics async-await command-pattern


【解决方案1】:

虽然我不确定这是最好的解决方案(或在您的特定用例中是否可行),但您可以做的是:

public class Response
{
    public bool Success { get; private set; }
    public ExceptionDispatchInfo ErrorInfo { get; private set; }
    public bool HasFailed
    {
        get { return !Success; }
    }

    public static T CreateErrorResponse<T>(ExceptionDispatchInfo errorInfo) where T : Response, new()
    {
        var response = new T();
        response.Success = false;
        response.ErrorInfo = errorInfo;
        return response;
    }
}

用法:

catch (HttpRequestException hex)
{
    return Response.CreateErrorResponse<T>(ExceptionDispatchInfo.Capture(hex)); // should compile (I did not check)
}

【讨论】:

    【解决方案2】:

    您可以将响应转换为 T。编辑:添加完整源代码

    public class Response
    {
        public bool Success { get; private set; }
    
        public static Response CreateErrorResponse()
        {
            return new Response { Success = false };
        }
    }
    
    public interface ICommand<T> where T : Response
    {
         Task<T> ExecuteAsync();
    }
    
    public abstract class CommandBase<T> : ICommand<T> where T: Response
    {
        protected abstract Uri BuildUrl();
        protected abstract Task<T> HandleResponseAsync();
    
        public async Task<T> ExecuteAsync()
    {
        var url = BuildUrl();
        var httpClient = new System.Net.Http.HttpClient();
    
        try
        {
            var response = await httpClient.GetAsync(url);
            return null;// await HandleResponseAsync(response);
        }
        catch (Exception hex)
        {
            return (T)Response.CreateErrorResponse(); // doesn't compile
        }
    }
    }
    
    public async override Task<T> ExecuteAsync()
    {
        var url = BuildUrl();
        var httpClient = new HttpClient();
    
        try
        {
            var response = await httpClient.GetAsync(url);
            return await HandleResponseAsync(response);
        }
        catch (HttpRequestException hex)
        {
            return (T)Response.CreateErrorResponse(); // compiles on liqpad
        }
    }
    

    【讨论】:

    • 但是您返回的实例不是 T。 IE。即使它编译,它现在也会导致运行时错误(InvalidCastException)。我怀疑这是一个可以接受的解决方案。
    • CreateErrorResponse() 返回 Response,并且 T 派生自 Response - 为此需要将 T 声明为协变。 Task 类型不是协变的,所以如果我不需要异步,这将起作用。
    猜你喜欢
    • 2022-06-21
    • 1970-01-01
    • 2017-06-04
    • 1970-01-01
    • 2014-08-14
    • 1970-01-01
    • 2023-03-26
    • 1970-01-01
    • 2021-10-15
    相关资源
    最近更新 更多