I have the below method (it's an extension method but not relevant to this question) and I would like to use GroupBy
on the results of the method.
class MyClass
{
public async Task<string> GetRank()
{
return "X";
}
public async static Task Test()
{
List<MyClass> items = new List<MyClass>() { new MyClass() };
var grouped = items.GroupBy(async _ => (await _.GetRank()));
}
}
The type of grouped
is IGrouping<Task<string>, MyClass>
, however I need to group by the actual awaited result of the async method (string). Despite using await and making the lambda async, I still get IGrouping<Task<string>, ..>
instead of IGrouping<string, ...>
How to use GroupBy and group by a result of async Task<string>
method and get a grouping by string?
Here is an asynchronous version of GroupBy
. It expects a task as the result of keySelector
, and returns a task that can be awaited:
public static async Task<IGrouping<TKey, TSource>[]> GroupByAsync<TSource, TKey>(
this IEnumerable<TSource> source, Func<TSource, Task<TKey>> keySelector)
{
List<KeyValuePair<TKey, TSource>> entries = new();
if (source.TryGetNonEnumeratedCount(out int count)) entries.Capacity = count;
foreach (TSource item in source)
{
TKey key = await keySelector(item).ConfigureAwait(false);
entries.Add(new(key, item));
}
return entries.GroupBy(entry => entry.Key, entry => entry.Value).ToArray();
}
It can be used like this:
class MyClass
{
public async Task<string> GetRank()
{
await Task.Delay(100);
return "X";
}
public async static Task Test()
{
var items = new List<MyClass>() { new MyClass(), new MyClass() };
var grouped = items.GroupByAsync(async _ => (await _.GetRank()));
foreach (var grouping in await grouped)
{
Console.WriteLine($"Key: {grouping.Key}, Count: {grouping.Count()}");
}
}
}
Output:
Key: X, Count: 2
The keySelector
is invoked sequentially, for one item at a time. In case you are interested for a concurrent implementation, you could take a look at the 2nd revision of this answer.