如何以多态方式对待 List<Task<T>> 以处理异常

How to treat List<Task<T>> polymorphically to handle exceptions

我正在尝试创建一种方法来记录任务列表的内部异常。问题是在某些地方我只有 (A) List<Task> 而在其他地方我有 (B) List<Task<Something>>。我试图创建一个接受 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);
        }
    }

使用协变 collection,例如 IEnumerable<T>IReadOnlyList<T>,而不是 IList<T>:

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<Task<T>> 类型的 object 分配给 IList<Task> 类型的变量,这是正确的。如果这被允许,你将能够做 list.Add(Task.CompletedTask),并在你的 IList<Task<T>> 中插入一个 non-generic 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!

但是,如果您只能从 collection 中取出 的东西,这不是问题。这叫generic covariance, and generic interfaces can mark themselves as covariant using the out keyword. IEnumerable<out T> and IReadOnlyList<out T>都这样做。

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

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);
    } 
}

在这里,由于 TTask<Whatever>,您无法将 Task 添加到 IList<Task<Whatever>> 中。这在您的情况下不是必需的,但如果您确实需要将项目插入到 collection.

中,这可能会很有用

您只会迭代任务,绝不会以任何其他方式操纵集合。如果您实际上不需要它的功能,则不应接受 IList。相反,接受 IEnumerable<Task>,因为它是协变的,所以你的问题就完全消失了。