ToDictionary.cs 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316
  1. // Licensed to the .NET Foundation under one or more agreements.
  2. // The .NET Foundation licenses this file to you under the MIT License.
  3. // See the LICENSE file in the project root for more information.
  4. using System.Collections.Generic;
  5. using System.Threading;
  6. using System.Threading.Tasks;
  7. namespace System.Linq
  8. {
  9. public static partial class AsyncEnumerable
  10. {
  11. #if INCLUDE_SYSTEM_LINQ_ASYNCENUMERABLE_DUPLICATES
  12. // https://learn.microsoft.com/en-us/dotnet/api/system.linq.asyncenumerable.todictionaryasync?view=net-9.0-pp#system-linq-asyncenumerable-todictionaryasync-2(system-collections-generic-iasyncenumerable((-0))-system-func((-0-1))-system-collections-generic-iequalitycomparer((-1))-system-threading-cancellationtoken)
  13. // That one overload covers the next two methods, because it supplieds a default comparer.
  14. /// <summary>
  15. /// Creates a dictionary from an async-enumerable sequence according to a specified key selector function.
  16. /// </summary>
  17. /// <typeparam name="TSource">The type of the elements in the source sequence.</typeparam>
  18. /// <typeparam name="TKey">The type of the dictionary key computed for each element in the source sequence.</typeparam>
  19. /// <param name="source">An async-enumerable sequence to create a dictionary for.</param>
  20. /// <param name="keySelector">A function to extract a key from each element.</param>
  21. /// <param name="cancellationToken">The optional cancellation token to be used for cancelling the sequence at any time.</param>
  22. /// <returns>An async-enumerable sequence containing a single element with a dictionary mapping unique key values onto the corresponding source sequence's element.</returns>
  23. /// <exception cref="ArgumentNullException"><paramref name="source"/> or <paramref name="keySelector"/> is null.</exception>
  24. /// <remarks>The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior.</remarks>
  25. public static ValueTask<Dictionary<TKey, TSource>> ToDictionaryAsync<TSource, TKey>(this IAsyncEnumerable<TSource> source, Func<TSource, TKey> keySelector, CancellationToken cancellationToken = default) where TKey : notnull =>
  26. ToDictionaryAsync(source, keySelector, comparer: null, cancellationToken);
  27. /// <summary>
  28. /// Creates a dictionary from an async-enumerable sequence according to a specified key selector function, and a comparer.
  29. /// </summary>
  30. /// <typeparam name="TSource">The type of the elements in the source sequence.</typeparam>
  31. /// <typeparam name="TKey">The type of the dictionary key computed for each element in the source sequence.</typeparam>
  32. /// <param name="source">An async-enumerable sequence to create a dictionary for.</param>
  33. /// <param name="keySelector">A function to extract a key from each element.</param>
  34. /// <param name="comparer">An equality comparer to compare keys.</param>
  35. /// <param name="cancellationToken">The optional cancellation token to be used for cancelling the sequence at any time.</param>
  36. /// <returns>An async-enumerable sequence containing a single element with a dictionary mapping unique key values onto the corresponding source sequence's element.</returns>
  37. /// <exception cref="ArgumentNullException"><paramref name="source"/> or <paramref name="keySelector"/> or <paramref name="comparer"/> is null.</exception>
  38. /// <remarks>The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior.</remarks>
  39. public static ValueTask<Dictionary<TKey, TSource>> ToDictionaryAsync<TSource, TKey>(this IAsyncEnumerable<TSource> source, Func<TSource, TKey> keySelector, IEqualityComparer<TKey>? comparer, CancellationToken cancellationToken = default) where TKey : notnull
  40. {
  41. if (source == null)
  42. throw Error.ArgumentNull(nameof(source));
  43. if (keySelector == null)
  44. throw Error.ArgumentNull(nameof(keySelector));
  45. return Core(source, keySelector, comparer, cancellationToken);
  46. static async ValueTask<Dictionary<TKey, TSource>> Core(IAsyncEnumerable<TSource> source, Func<TSource, TKey> keySelector, IEqualityComparer<TKey>? comparer, CancellationToken cancellationToken)
  47. {
  48. var d = new Dictionary<TKey, TSource>(comparer);
  49. await foreach (var item in source.WithCancellation(cancellationToken).ConfigureAwait(false))
  50. {
  51. var key = keySelector(item);
  52. d.Add(key, item);
  53. }
  54. return d;
  55. }
  56. }
  57. #endif // INCLUDE_SYSTEM_LINQ_ASYNCENUMERABLE_DUPLICATES
  58. /// <summary>
  59. /// Creates a dictionary from an async-enumerable sequence by invoking a key-selector function on each element and awaiting the result.
  60. /// </summary>
  61. /// <typeparam name="TSource">The type of the elements in the source sequence.</typeparam>
  62. /// <typeparam name="TKey">The type of the dictionary key computed for each element in the source sequence.</typeparam>
  63. /// <param name="source">An async-enumerable sequence to create a dictionary for.</param>
  64. /// <param name="keySelector">An asynchronous function to extract a key from each element.</param>
  65. /// <param name="cancellationToken">The optional cancellation token to be used for cancelling the sequence at any time.</param>
  66. /// <returns>A ValueTask containing a dictionary mapping unique key values onto the corresponding source sequence's element.</returns>
  67. /// <exception cref="ArgumentNullException"><paramref name="source"/> or <paramref name="keySelector"/> is null.</exception>
  68. /// <remarks>The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior.</remarks>
  69. [GenerateAsyncOverload]
  70. [Obsolete("Use ToDictionaryAsync. IAsyncEnumerable LINQ is now in System.Linq.AsyncEnumerable, and the ToDictionaryAwaitAsync functionality now exists as overloads of ToDictionaryAsync.")]
  71. private static ValueTask<Dictionary<TKey, TSource>> ToDictionaryAwaitAsyncCore<TSource, TKey>(this IAsyncEnumerable<TSource> source, Func<TSource, ValueTask<TKey>> keySelector, CancellationToken cancellationToken = default) where TKey : notnull =>
  72. ToDictionaryAwaitAsyncCore<TSource, TKey>(source, keySelector, comparer: null, cancellationToken);
  73. /// <summary>
  74. /// Creates a dictionary from an async-enumerable sequence by invoking a key-selector function on each element and awaiting the result.
  75. /// </summary>
  76. /// <typeparam name="TSource">The type of the elements in the source sequence.</typeparam>
  77. /// <typeparam name="TKey">The type of the dictionary key computed for each element in the source sequence.</typeparam>
  78. /// <param name="source">An async-enumerable sequence to create a dictionary for.</param>
  79. /// <param name="keySelector">An asynchronous function to extract a key from each element.</param>
  80. /// <param name="comparer">An equality comparer to compare keys.</param>
  81. /// <param name="cancellationToken">The optional cancellation token to be used for cancelling the sequence at any time.</param>
  82. /// <returns>A ValueTask containing a dictionary mapping unique key values onto the corresponding source sequence's element.</returns>
  83. /// <exception cref="ArgumentNullException"><paramref name="source"/> or <paramref name="keySelector"/> or <paramref name="comparer"/> is null.</exception>
  84. /// <remarks>The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior.</remarks>
  85. [GenerateAsyncOverload]
  86. [Obsolete("Use ToDictionaryAsync. IAsyncEnumerable LINQ is now in System.Linq.AsyncEnumerable, and the ToDictionaryAwaitAsync functionality now exists as overloads of ToDictionaryAsync.")]
  87. private static ValueTask<Dictionary<TKey, TSource>> ToDictionaryAwaitAsyncCore<TSource, TKey>(this IAsyncEnumerable<TSource> source, Func<TSource, ValueTask<TKey>> keySelector, IEqualityComparer<TKey>? comparer, CancellationToken cancellationToken = default) where TKey : notnull
  88. {
  89. if (source == null)
  90. throw Error.ArgumentNull(nameof(source));
  91. if (keySelector == null)
  92. throw Error.ArgumentNull(nameof(keySelector));
  93. return Core(source, keySelector, comparer, cancellationToken);
  94. static async ValueTask<Dictionary<TKey, TSource>> Core(IAsyncEnumerable<TSource> source, Func<TSource, ValueTask<TKey>> keySelector, IEqualityComparer<TKey>? comparer, CancellationToken cancellationToken)
  95. {
  96. var d = new Dictionary<TKey, TSource>(comparer);
  97. await foreach (var item in source.WithCancellation(cancellationToken).ConfigureAwait(false))
  98. {
  99. var key = await keySelector(item).ConfigureAwait(false);
  100. d.Add(key, item);
  101. }
  102. return d;
  103. }
  104. }
  105. #if !NO_DEEP_CANCELLATION
  106. [GenerateAsyncOverload]
  107. [Obsolete("Use ToDictionaryAsync. IAsyncEnumerable LINQ is now in System.Linq.AsyncEnumerable, and the ToDictionaryAwaitWithCancellationAsync functionality now exists as overloads of ToDictionaryAsync.")]
  108. private static ValueTask<Dictionary<TKey, TSource>> ToDictionaryAwaitWithCancellationAsyncCore<TSource, TKey>(this IAsyncEnumerable<TSource> source, Func<TSource, CancellationToken, ValueTask<TKey>> keySelector, CancellationToken cancellationToken = default) where TKey : notnull =>
  109. ToDictionaryAwaitWithCancellationAsyncCore(source, keySelector, comparer: null, cancellationToken);
  110. [GenerateAsyncOverload]
  111. [Obsolete("Use ToDictionaryAsync. IAsyncEnumerable LINQ is now in System.Linq.AsyncEnumerable, and the ToDictionaryAwaitWithCancellationAsync functionality now exists as overloads of ToDictionaryAsync.")]
  112. private static ValueTask<Dictionary<TKey, TSource>> ToDictionaryAwaitWithCancellationAsyncCore<TSource, TKey>(this IAsyncEnumerable<TSource> source, Func<TSource, CancellationToken, ValueTask<TKey>> keySelector, IEqualityComparer<TKey>? comparer, CancellationToken cancellationToken = default) where TKey : notnull
  113. {
  114. if (source == null)
  115. throw Error.ArgumentNull(nameof(source));
  116. if (keySelector == null)
  117. throw Error.ArgumentNull(nameof(keySelector));
  118. return Core(source, keySelector, comparer, cancellationToken);
  119. static async ValueTask<Dictionary<TKey, TSource>> Core(IAsyncEnumerable<TSource> source, Func<TSource, CancellationToken, ValueTask<TKey>> keySelector, IEqualityComparer<TKey>? comparer, CancellationToken cancellationToken)
  120. {
  121. var d = new Dictionary<TKey, TSource>(comparer);
  122. await foreach (var item in source.WithCancellation(cancellationToken).ConfigureAwait(false))
  123. {
  124. var key = await keySelector(item, cancellationToken).ConfigureAwait(false);
  125. d.Add(key, item);
  126. }
  127. return d;
  128. }
  129. }
  130. #endif
  131. #if INCLUDE_SYSTEM_LINQ_ASYNCENUMERABLE_DUPLICATES
  132. // https://learn.microsoft.com/en-us/dotnet/api/system.linq.asyncenumerable.todictionaryasync?view=net-9.0-pp#system-linq-asyncenumerable-todictionaryasync-2(system-collections-generic-iasyncenumerable((-0))-system-func((-0-1))-system-collections-generic-iequalitycomparer((-1))-system-threading-cancellationtoken)
  133. // The method above provides the functionality for each of the next two methods, although because it does so with a
  134. // single method that provides a default null value for the comparer, it it's not a strict source-compatible
  135. // replacement. But there's not much we can do about that.
  136. /// <summary>
  137. /// Creates a dictionary from an async-enumerable sequence according to a specified key selector function, and an element selector function.
  138. /// </summary>
  139. /// <typeparam name="TSource">The type of the elements in the source sequence.</typeparam>
  140. /// <typeparam name="TKey">The type of the dictionary key computed for each element in the source sequence.</typeparam>
  141. /// <typeparam name="TElement">The type of the dictionary value computed for each element in the source sequence.</typeparam>
  142. /// <param name="source">An async-enumerable sequence to create a dictionary for.</param>
  143. /// <param name="keySelector">A function to extract a key from each element.</param>
  144. /// <param name="elementSelector">A transform function to produce a result element value from each element.</param>
  145. /// <param name="cancellationToken">The optional cancellation token to be used for cancelling the sequence at any time.</param>
  146. /// <returns>An async-enumerable sequence containing a single element with a dictionary mapping unique key values onto the corresponding source sequence's element.</returns>
  147. /// <exception cref="ArgumentNullException"><paramref name="source"/> or <paramref name="keySelector"/> or <paramref name="elementSelector"/> is null.</exception>
  148. /// <remarks>The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior.</remarks>
  149. public static ValueTask<Dictionary<TKey, TElement>> ToDictionaryAsync<TSource, TKey, TElement>(this IAsyncEnumerable<TSource> source, Func<TSource, TKey> keySelector, Func<TSource, TElement> elementSelector, CancellationToken cancellationToken = default) where TKey : notnull =>
  150. ToDictionaryAsync(source, keySelector, elementSelector, comparer: null, cancellationToken);
  151. /// <summary>
  152. /// Creates a dictionary from an async-enumerable sequence according to a specified key selector function, a comparer, and an element selector function.
  153. /// </summary>
  154. /// <typeparam name="TSource">The type of the elements in the source sequence.</typeparam>
  155. /// <typeparam name="TKey">The type of the dictionary key computed for each element in the source sequence.</typeparam>
  156. /// <typeparam name="TElement">The type of the dictionary value computed for each element in the source sequence.</typeparam>
  157. /// <param name="source">An async-enumerable sequence to create a dictionary for.</param>
  158. /// <param name="keySelector">A function to extract a key from each element.</param>
  159. /// <param name="elementSelector">A transform function to produce a result element value from each element.</param>
  160. /// <param name="comparer">An equality comparer to compare keys.</param>
  161. /// <param name="cancellationToken">The optional cancellation token to be used for cancelling the sequence at any time.</param>
  162. /// <returns>An async-enumerable sequence containing a single element with a dictionary mapping unique key values onto the corresponding source sequence's element.</returns>
  163. /// <exception cref="ArgumentNullException"><paramref name="source"/> or <paramref name="keySelector"/> or <paramref name="elementSelector"/> or <paramref name="comparer"/> is null.</exception>
  164. /// <remarks>The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior.</remarks>
  165. public static ValueTask<Dictionary<TKey, TElement>> ToDictionaryAsync<TSource, TKey, TElement>(this IAsyncEnumerable<TSource> source, Func<TSource, TKey> keySelector, Func<TSource, TElement> elementSelector, IEqualityComparer<TKey>? comparer, CancellationToken cancellationToken = default) where TKey : notnull
  166. {
  167. if (source == null)
  168. throw Error.ArgumentNull(nameof(source));
  169. if (keySelector == null)
  170. throw Error.ArgumentNull(nameof(keySelector));
  171. if (elementSelector == null)
  172. throw Error.ArgumentNull(nameof(elementSelector));
  173. return Core(source, keySelector, elementSelector, comparer, cancellationToken);
  174. static async ValueTask<Dictionary<TKey, TElement>> Core(IAsyncEnumerable<TSource> source, Func<TSource, TKey> keySelector, Func<TSource, TElement> elementSelector, IEqualityComparer<TKey>? comparer, CancellationToken cancellationToken)
  175. {
  176. var d = new Dictionary<TKey, TElement>(comparer);
  177. await foreach (var item in source.WithCancellation(cancellationToken).ConfigureAwait(false))
  178. {
  179. var key = keySelector(item);
  180. var value = elementSelector(item);
  181. d.Add(key, value);
  182. }
  183. return d;
  184. }
  185. }
  186. #endif // INCLUDE_SYSTEM_LINQ_ASYNCENUMERABLE_DUPLICATES
  187. /// <summary>
  188. /// Creates a dictionary from an async-enumerable sequence using the specified asynchronous key and element selector functions.
  189. /// </summary>
  190. /// <typeparam name="TSource">The type of the elements in the source sequence.</typeparam>
  191. /// <typeparam name="TKey">The type of the dictionary key computed for each element in the source sequence.</typeparam>
  192. /// <typeparam name="TElement">The type of the dictionary value computed for each element in the source sequence.</typeparam>
  193. /// <param name="source">An async-enumerable sequence to create a dictionary for.</param>
  194. /// <param name="keySelector">An asynchronous function to extract a key from each element.</param>
  195. /// <param name="elementSelector">An asynchronous transform function to produce a result element value from each element.</param>
  196. /// <param name="cancellationToken">The optional cancellation token to be used for cancelling the sequence at any time.</param>
  197. /// <returns>A ValueTask containing a dictionary mapping unique key values onto the corresponding source sequence's element.</returns>
  198. /// <exception cref="ArgumentNullException"><paramref name="source"/> or <paramref name="keySelector"/> or <paramref name="elementSelector"/> is null.</exception>
  199. /// <remarks>The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior.</remarks>
  200. [GenerateAsyncOverload]
  201. [Obsolete("Use ToDictionaryAsync. IAsyncEnumerable LINQ is now in System.Linq.AsyncEnumerable, and the ToDictionaryAwaitAsync functionality now exists as overloads of ToDictionaryAsync.")]
  202. private static ValueTask<Dictionary<TKey, TElement>> ToDictionaryAwaitAsyncCore<TSource, TKey, TElement>(this IAsyncEnumerable<TSource> source, Func<TSource, ValueTask<TKey>> keySelector, Func<TSource, ValueTask<TElement>> elementSelector, CancellationToken cancellationToken = default) where TKey : notnull =>
  203. ToDictionaryAwaitAsyncCore<TSource, TKey, TElement>(source, keySelector, elementSelector, comparer: null, cancellationToken);
  204. /// <summary>
  205. /// Creates a dictionary from an async-enumerable sequence using the specified asynchronous key and element selector functions.
  206. /// </summary>
  207. /// <typeparam name="TSource">The type of the elements in the source sequence.</typeparam>
  208. /// <typeparam name="TKey">The type of the dictionary key computed for each element in the source sequence.</typeparam>
  209. /// <typeparam name="TElement">The type of the dictionary value computed for each element in the source sequence.</typeparam>
  210. /// <param name="source">An async-enumerable sequence to create a dictionary for.</param>
  211. /// <param name="keySelector">An asynchronous function to extract a key from each element.</param>
  212. /// <param name="elementSelector">An asynchronous transform function to produce a result element value from each element.</param>
  213. /// <param name="comparer">An equality comparer to compare keys.</param>
  214. /// <param name="cancellationToken">The optional cancellation token to be used for cancelling the sequence at any time.</param>
  215. /// <returns>A ValueTask containing a dictionary mapping unique key values onto the corresponding source sequence's element.</returns>
  216. /// <exception cref="ArgumentNullException"><paramref name="source"/> or <paramref name="keySelector"/> or <paramref name="elementSelector"/> or <paramref name="comparer"/> is null.</exception>
  217. /// <remarks>The return type of this operator differs from the corresponding operator on IEnumerable in order to retain asynchronous behavior.</remarks>
  218. [GenerateAsyncOverload]
  219. [Obsolete("Use ToDictionaryAsync. IAsyncEnumerable LINQ is now in System.Linq.AsyncEnumerable, and the ToDictionaryAwaitAsync functionality now exists as overloads of ToDictionaryAsync.")]
  220. private static ValueTask<Dictionary<TKey, TElement>> ToDictionaryAwaitAsyncCore<TSource, TKey, TElement>(this IAsyncEnumerable<TSource> source, Func<TSource, ValueTask<TKey>> keySelector, Func<TSource, ValueTask<TElement>> elementSelector, IEqualityComparer<TKey>? comparer, CancellationToken cancellationToken = default) where TKey : notnull
  221. {
  222. if (source == null)
  223. throw Error.ArgumentNull(nameof(source));
  224. if (keySelector == null)
  225. throw Error.ArgumentNull(nameof(keySelector));
  226. if (elementSelector == null)
  227. throw Error.ArgumentNull(nameof(elementSelector));
  228. return Core(source, keySelector, elementSelector, comparer, cancellationToken);
  229. static async ValueTask<Dictionary<TKey, TElement>> Core(IAsyncEnumerable<TSource> source, Func<TSource, ValueTask<TKey>> keySelector, Func<TSource, ValueTask<TElement>> elementSelector, IEqualityComparer<TKey>? comparer, CancellationToken cancellationToken)
  230. {
  231. var d = new Dictionary<TKey, TElement>(comparer);
  232. await foreach (var item in source.WithCancellation(cancellationToken).ConfigureAwait(false))
  233. {
  234. var key = await keySelector(item).ConfigureAwait(false);
  235. var value = await elementSelector(item).ConfigureAwait(false);
  236. d.Add(key, value);
  237. }
  238. return d;
  239. }
  240. }
  241. #if !NO_DEEP_CANCELLATION
  242. [GenerateAsyncOverload]
  243. [Obsolete("Use ToDictionaryAsync. IAsyncEnumerable LINQ is now in System.Linq.AsyncEnumerable, and the ToDictionaryAwaitWithCancellationAsync functionality now exists as overloads of ToDictionaryAsync.")]
  244. private static ValueTask<Dictionary<TKey, TElement>> ToDictionaryAwaitWithCancellationAsyncCore<TSource, TKey, TElement>(this IAsyncEnumerable<TSource> source, Func<TSource, CancellationToken, ValueTask<TKey>> keySelector, Func<TSource, CancellationToken, ValueTask<TElement>> elementSelector, CancellationToken cancellationToken = default) where TKey : notnull =>
  245. ToDictionaryAwaitWithCancellationAsyncCore(source, keySelector, elementSelector, comparer: null, cancellationToken);
  246. [GenerateAsyncOverload]
  247. [Obsolete("Use ToDictionaryAsync. IAsyncEnumerable LINQ is now in System.Linq.AsyncEnumerable, and the ToDictionaryAwaitWithCancellationAsync functionality now exists as overloads of ToDictionaryAsync.")]
  248. private static ValueTask<Dictionary<TKey, TElement>> ToDictionaryAwaitWithCancellationAsyncCore<TSource, TKey, TElement>(this IAsyncEnumerable<TSource> source, Func<TSource, CancellationToken, ValueTask<TKey>> keySelector, Func<TSource, CancellationToken, ValueTask<TElement>> elementSelector, IEqualityComparer<TKey>? comparer, CancellationToken cancellationToken = default) where TKey : notnull
  249. {
  250. if (source == null)
  251. throw Error.ArgumentNull(nameof(source));
  252. if (keySelector == null)
  253. throw Error.ArgumentNull(nameof(keySelector));
  254. if (elementSelector == null)
  255. throw Error.ArgumentNull(nameof(elementSelector));
  256. return Core(source, keySelector, elementSelector, comparer, cancellationToken);
  257. static async ValueTask<Dictionary<TKey, TElement>> Core(IAsyncEnumerable<TSource> source, Func<TSource, CancellationToken, ValueTask<TKey>> keySelector, Func<TSource, CancellationToken, ValueTask<TElement>> elementSelector, IEqualityComparer<TKey>? comparer, CancellationToken cancellationToken)
  258. {
  259. var d = new Dictionary<TKey, TElement>(comparer);
  260. await foreach (var item in source.WithCancellation(cancellationToken).ConfigureAwait(false))
  261. {
  262. var key = await keySelector(item, cancellationToken).ConfigureAwait(false);
  263. var value = await elementSelector(item, cancellationToken).ConfigureAwait(false);
  264. d.Add(key, value);
  265. }
  266. return d;
  267. }
  268. }
  269. #endif
  270. }
  271. }