【问题标题】:How to treat List<Task<T>> polymorphically to handle exceptions如何以多态方式处理 List<Task<T>> 以处理异常
【发布时间】:2021-02-12 15:08:25
【问题描述】:

我正在尝试创建一种方法来记录任务列表的内部异常。问题是在某些地方我只有(A)List&lt;Task&gt;,而在其他地方我有(B)List&lt;Task&lt;Something&gt;&gt;。我尝试创建一个接受 A 的方法,但编译器不接受使用 B 调用该方法,因此我将 B 称为“MultipleTasksExceptionHandler(B as A)”,但在方法内部我收到 null。所以我必须创建几乎相同的方法,如下所示,它们可以工作,但是还有其他方法可以避免这种代码重复吗?

    public static void MultipleTasksExceptionHandler<T>(IList<Task<T>> tasks, ILogger logger)
    {
        var exceptions = tasks.Where(task => task.IsFaulted).SelectMany(x => x.Exception.InnerExceptions);

        foreach (var exception in exceptions)
        {
            logger.LogError(exception, exception.Message);
        }
    }

    public static void MultipleTasksExceptionHandler(IList<Task> tasks, ILogger logger)
    {
        var exceptions = tasks.Where(task => task.IsFaulted).SelectMany(x => x.Exception.InnerExceptions);

        foreach (var exception in exceptions)
        {
            logger.LogError(exception, exception.Message);
        }
    }

【问题讨论】:

    标签: c# polymorphism task-parallel-library .net-5 code-duplication


    【解决方案1】:

    使用协变集合,例如IEnumerable&lt;T&gt;IReadOnlyList&lt;T&gt;,而不是IList&lt;T&gt;

    public void MultipleTasksExceptionHandler(IEnumerable<Task> tasks, ILogger logger)
    {
        var exceptions = tasks.Where(task => task.IsFaulted).SelectMany(x => x.Exception.InnerExceptions);
    
        foreach (var exception in exceptions)
        {
            logger.LogError(exception, exception.Message);
        } 
    }
    

    不能将IList&lt;Task&lt;T&gt;&gt; 类型的对象分配给IList&lt;Task&gt; 类型的变量是正确的。如果允许这样做,您将能够执行list.Add(Task.CompletedTask),并在您的IList&lt;Task&lt;T&gt;&gt; 中插入一个非泛型Task,这显然是错误的。

    例如:

    IList<Task<int>> listOfTaskOfT = new List<Task<int>>();
    
    // This isn't allowed
    IList<Task> listOfTask = listOfTaskOfT;
    
    // ... if it was, you'd be able to do this:
    listOfTask.Add(new Task());
    Task<int> task = listOfTaskOfT[0];
    // But that item is a Task, not a Task<int>. Oops!
    

    但是,如果您只能将收藏中的东西取出,这不是问题。这称为generic covariance,泛型接口可以使用out 关键字将自己标记为协变。 IEnumerable&lt;out T&gt;IReadOnlyList&lt;out T&gt; 都这样做。

    然后您可以执行以下操作:

    IEnumerable<Task> tasks = new List<Task<T>>();
    

    ...前提是所涉及的所有泛型类型参数都是引用类型。

    (对于通用“逆变”接口也存在类似的功能,您只能使用 in 关键字添加项目)。


    您可以使用泛型获得类似的功能:

    public void MultipleTasksExceptionHandler<T>(IList<T> tasks, ILogger logger) where T : Task
    {
       var exceptions = tasks.Where(task => task.IsFaulted).SelectMany(x => x.Exception.InnerExceptions);
    
        foreach (var exception in exceptions)
        {
            logger.LogError(exception, exception.Message);
        } 
    }
    

    在这里,您无法将Task 添加到IList&lt;Task&lt;Whatever&gt;&gt; 中,因为TTask&lt;Whatever&gt;。这在您的情况下不是必需的,但如果您确实需要将项目插入您的集合中,这可能会很有用。

    【讨论】:

    • +1 表示Task.IsFaulted。我以前见过AggregateException,但这看起来更干净。
    • @DakotaMethvin 这只是问题中的代码......
    • 确实,我并没有判断 OP 在他们的方法中做了什么,只是回答他们的具体问题
    • @Servy 原来如此,我是瞎子。
    • 调用它 MultipleTasksExceptionHandler(processTasks, _logger);抛出:无法从用法中推断方法 'MultipleTasksExceptionHandler(IEnumerable>, ILogger)' 的类型参数。尝试明确指定类型参数。所以我添加类型 MultipleTasksExceptionHandler(processTasks, _logger);它抛出无法从 'System.Collections.Generic.List' 转换为 'System.Collections.Generic.IEnumerable >'
    【解决方案2】:

    您只是在迭代任务,从不以任何其他方式操作集合。如果您实际上不需要它的功能,则不应接受IList。改为接受IEnumerable&lt;Task&gt;,因为它是协变的,所以你的问题完全消失了。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2018-08-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-02-28
      • 2015-02-19
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多