1

以下代码旨在通过允许您在一组“getter”(数据检索客户端)上通用调用相同的选择器并将所有结果组合在一起来减少冗余代码。最终结果集是可等待的,允许系统在等待所有选择器完成时不会阻塞。

public async Task<T> GetBulkAsync<T>(Func<IGetter, Task<T>> selector) where T : IEnumerable<T>
{
    Task<T>[] tasks = new Task<T>[this.getters.Count];
    for (int i = 0; i < this.getters.Count; i++)
    {
        tasks[i] = selector(this.getters[i]);
    }

    T[] results = await Task.WhenAll(tasks);

    return results.SelectMany<T, T>(r => r);
}

但是,我在这里的设计中出现了一个问题。在一种情况下,我只是想使用一个选择器来检索单个IData,然后有时选择器将检索一个IEnumerable<IData>或者可能是一个List<IData>完全不同的东西。

我曾考虑过使用反射来处理具有通用集合的特殊情况,但这很麻烦,并且需要对我想要处理的每种类型的集合进行额外的工作。

我关心IEnumerable<IData>(或真正关心IEnumerable<T>)的主要原因是因为我想使用 Linq 的 SelectMany 来展平大结果集中的所有结果。如果每个选择器都返回一个IEnumerable<T>,则需要将其缩减为一个IEnumerable<T>,而不是一个IEnumerable<IEnumerable<T>>

除了编写几个不同的泛型方法,限制集合类型并且不使用反射之外,是否有解决这个问题的好方法?

4

1 回答 1

0

不要依赖一个类型参数,而是使用两个并在最后提供一个转换/投影函数,如下所示:

public async Task<TResult> GetBulkAsync<T, TResult>(
    Func<IGetter, Task<T>> selector,
    Func<IEnumerable<T>, TResult> aggregateSelector) 
        where T : IEnumerable<T>
{
    Task<T>[] tasks = new Task<T>[this.getters.Count];
    for (int i = 0; i < this.getters.Count; i++)
    {
        tasks[i] = selector(this.getters[i]);
    }

    T[] results = await Task.WhenAll(tasks);

    return results.Select<T, TResult>(aggregateSelector);
}

aggregateSelector基本上会将每个转换IEnumerable<T>为一个新TResult实例,您将返回一个IEnumerable<TResult>.

但是,我建议不要这样做,因为您依赖组合函数中发生,而实际上并非如此。

相反,我认为您应该返回一系列序列,如下所示:

public async Task<IEnumerable<IEnumerable<T>>> GetBulkAsync<T>(
    Func<IGetter, Task<T>> selector) 
        where T : IEnumerable<T>
{
    Task<T>[] tasks = new Task<T>[this.getters.Count];
    for (int i = 0; i < this.getters.Count; i++)
    {
        tasks[i] = selector(this.getters[i]);
    }

    T[] results = await Task.WhenAll(tasks);

    return results.Select(t => t.Result):
}

通过这种方式,您可以以任何方式组合结果,而无需更改此方法。

于 2012-10-04T18:41:11.103 回答