Grouping.cs 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525
  1. // Licensed to the .NET Foundation under one or more agreements.
  2. // The .NET Foundation licenses this file to you under the Apache 2.0 License.
  3. // See the LICENSE file in the project root for more information.
  4. using System;
  5. using System.Collections;
  6. using System.Collections.Generic;
  7. using System.Linq;
  8. using System.Runtime.ExceptionServices;
  9. using System.Threading;
  10. using System.Threading.Tasks;
  11. namespace System.Linq
  12. {
  13. public static partial class AsyncEnumerable
  14. {
  15. public static IAsyncEnumerable<IAsyncGrouping<TKey, TElement>> GroupBy<TSource, TKey, TElement>(this IAsyncEnumerable<TSource> source, Func<TSource, TKey> keySelector, Func<TSource, TElement> elementSelector, IEqualityComparer<TKey> comparer)
  16. {
  17. if (source == null)
  18. throw new ArgumentNullException(nameof(source));
  19. if (keySelector == null)
  20. throw new ArgumentNullException(nameof(keySelector));
  21. if (elementSelector == null)
  22. throw new ArgumentNullException(nameof(elementSelector));
  23. if (comparer == null)
  24. throw new ArgumentNullException(nameof(comparer));
  25. return new GroupedAsyncEnumerable<TSource, TKey, TElement>(source, keySelector, elementSelector, comparer);
  26. }
  27. public static IAsyncEnumerable<IAsyncGrouping<TKey, TElement>> GroupBy<TSource, TKey, TElement>(this IAsyncEnumerable<TSource> source, Func<TSource, TKey> keySelector, Func<TSource, TElement> elementSelector)
  28. {
  29. if (source == null)
  30. throw new ArgumentNullException(nameof(source));
  31. if (keySelector == null)
  32. throw new ArgumentNullException(nameof(keySelector));
  33. if (elementSelector == null)
  34. throw new ArgumentNullException(nameof(elementSelector));
  35. return source.GroupBy(keySelector, elementSelector, EqualityComparer<TKey>.Default);
  36. }
  37. public static IAsyncEnumerable<IAsyncGrouping<TKey, TSource>> GroupBy<TSource, TKey>(this IAsyncEnumerable<TSource> source, Func<TSource, TKey> keySelector, IEqualityComparer<TKey> comparer)
  38. {
  39. if (source == null)
  40. throw new ArgumentNullException(nameof(source));
  41. if (keySelector == null)
  42. throw new ArgumentNullException(nameof(keySelector));
  43. if (comparer == null)
  44. throw new ArgumentNullException(nameof(comparer));
  45. return new GroupedAsyncEnumerable<TSource, TKey>(source, keySelector, comparer);
  46. }
  47. public static IAsyncEnumerable<IAsyncGrouping<TKey, TSource>> GroupBy<TSource, TKey>(this IAsyncEnumerable<TSource> source, Func<TSource, TKey> keySelector)
  48. {
  49. if (source == null)
  50. throw new ArgumentNullException(nameof(source));
  51. if (keySelector == null)
  52. throw new ArgumentNullException(nameof(keySelector));
  53. return new GroupedAsyncEnumerable<TSource, TKey>(source, keySelector, EqualityComparer<TKey>.Default);
  54. }
  55. public static IAsyncEnumerable<TResult> GroupBy<TSource, TKey, TElement, TResult>(this IAsyncEnumerable<TSource> source, Func<TSource, TKey> keySelector, Func<TSource, TElement> elementSelector, Func<TKey, IAsyncEnumerable<TElement>, TResult> resultSelector, IEqualityComparer<TKey> comparer)
  56. {
  57. if (source == null)
  58. throw new ArgumentNullException(nameof(source));
  59. if (keySelector == null)
  60. throw new ArgumentNullException(nameof(keySelector));
  61. if (elementSelector == null)
  62. throw new ArgumentNullException(nameof(elementSelector));
  63. if (resultSelector == null)
  64. throw new ArgumentNullException(nameof(resultSelector));
  65. if (comparer == null)
  66. throw new ArgumentNullException(nameof(comparer));
  67. return source.GroupBy(keySelector, elementSelector, comparer)
  68. .Select(g => resultSelector(g.Key, g));
  69. }
  70. public static IAsyncEnumerable<TResult> GroupBy<TSource, TKey, TElement, TResult>(this IAsyncEnumerable<TSource> source, Func<TSource, TKey> keySelector, Func<TSource, TElement> elementSelector, Func<TKey, IAsyncEnumerable<TElement>, TResult> resultSelector)
  71. {
  72. if (source == null)
  73. throw new ArgumentNullException(nameof(source));
  74. if (keySelector == null)
  75. throw new ArgumentNullException(nameof(keySelector));
  76. if (elementSelector == null)
  77. throw new ArgumentNullException(nameof(elementSelector));
  78. if (resultSelector == null)
  79. throw new ArgumentNullException(nameof(resultSelector));
  80. return source.GroupBy(keySelector, elementSelector, EqualityComparer<TKey>.Default)
  81. .Select(g => resultSelector(g.Key, g));
  82. }
  83. public static IAsyncEnumerable<TResult> GroupBy<TSource, TKey, TResult>(this IAsyncEnumerable<TSource> source, Func<TSource, TKey> keySelector, Func<TKey, IAsyncEnumerable<TSource>, TResult> resultSelector, IEqualityComparer<TKey> comparer)
  84. {
  85. if (source == null)
  86. throw new ArgumentNullException(nameof(source));
  87. if (keySelector == null)
  88. throw new ArgumentNullException(nameof(keySelector));
  89. if (resultSelector == null)
  90. throw new ArgumentNullException(nameof(resultSelector));
  91. if (comparer == null)
  92. throw new ArgumentNullException(nameof(comparer));
  93. return new GroupedResultAsyncEnumerable<TSource, TKey, TResult>(source, keySelector, resultSelector, comparer);
  94. }
  95. public static IAsyncEnumerable<TResult> GroupBy<TSource, TKey, TResult>(this IAsyncEnumerable<TSource> source, Func<TSource, TKey> keySelector, Func<TKey, IAsyncEnumerable<TSource>, TResult> resultSelector)
  96. {
  97. if (source == null)
  98. throw new ArgumentNullException(nameof(source));
  99. if (keySelector == null)
  100. throw new ArgumentNullException(nameof(keySelector));
  101. if (resultSelector == null)
  102. throw new ArgumentNullException(nameof(resultSelector));
  103. return GroupBy(source, keySelector, resultSelector, EqualityComparer<TKey>.Default);
  104. }
  105. private static IEnumerable<IGrouping<TKey, TElement>> GroupUntil<TSource, TKey, TElement>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector, Func<TSource, TElement> elementSelector, IComparer<TKey> comparer)
  106. {
  107. var group = default(EnumerableGrouping<TKey, TElement>);
  108. foreach (var x in source)
  109. {
  110. var key = keySelector(x);
  111. if (group == null || comparer.Compare(group.Key, key) != 0)
  112. {
  113. group = new EnumerableGrouping<TKey, TElement>(key);
  114. yield return group;
  115. }
  116. group.Add(elementSelector(x));
  117. }
  118. }
  119. internal sealed class GroupedResultAsyncEnumerable<TSource, TKey, TResult> : AsyncIterator<TResult>, IIListProvider<TResult>
  120. {
  121. private readonly IAsyncEnumerable<TSource> source;
  122. private readonly Func<TSource, TKey> keySelector;
  123. private readonly Func<TKey, IAsyncEnumerable<TSource>, TResult> resultSelector;
  124. private readonly IEqualityComparer<TKey> comparer;
  125. Internal.Lookup<TKey, TSource> lookup;
  126. IEnumerator<TResult> enumerator;
  127. public GroupedResultAsyncEnumerable(IAsyncEnumerable<TSource> source, Func<TSource, TKey> keySelector, Func<TKey, IAsyncEnumerable<TSource>, TResult> resultSelector, IEqualityComparer<TKey> comparer)
  128. {
  129. if (source == null) throw new ArgumentNullException(nameof(source));
  130. if (keySelector == null) throw new ArgumentNullException(nameof(keySelector));
  131. if (resultSelector == null) throw new ArgumentNullException(nameof(resultSelector));
  132. this.source = source;
  133. this.keySelector = keySelector;
  134. this.resultSelector = resultSelector;
  135. this.comparer = comparer;
  136. }
  137. public override AsyncIterator<TResult> Clone()
  138. {
  139. return new GroupedResultAsyncEnumerable<TSource, TKey, TResult>(source, keySelector, resultSelector, comparer);
  140. }
  141. public override void Dispose()
  142. {
  143. if (enumerator != null)
  144. {
  145. enumerator.Dispose();
  146. enumerator = null;
  147. lookup = null;
  148. }
  149. base.Dispose();
  150. }
  151. protected override async Task<bool> MoveNextCore(CancellationToken cancellationToken)
  152. {
  153. switch (state)
  154. {
  155. case AsyncIteratorState.Allocated:
  156. lookup = await Internal.Lookup<TKey, TSource>.CreateAsync(source, keySelector, comparer, cancellationToken).ConfigureAwait(false);
  157. enumerator = lookup.ApplyResultSelector(resultSelector).GetEnumerator();
  158. state = AsyncIteratorState.Iterating;
  159. goto case AsyncIteratorState.Iterating;
  160. case AsyncIteratorState.Iterating:
  161. if (enumerator.MoveNext())
  162. {
  163. current = enumerator.Current;
  164. return true;
  165. }
  166. Dispose();
  167. break;
  168. }
  169. return false;
  170. }
  171. public async Task<TResult[]> ToArrayAsync(CancellationToken cancellationToken)
  172. {
  173. var lookup = await Internal.Lookup<TKey, TSource>.CreateAsync(source, keySelector, comparer, cancellationToken).ConfigureAwait(false);
  174. return lookup.ToArray(resultSelector);
  175. }
  176. public async Task<List<TResult>> ToListAsync(CancellationToken cancellationToken)
  177. {
  178. var lookup = await Internal.Lookup<TKey, TSource>.CreateAsync(source, keySelector, comparer, cancellationToken).ConfigureAwait(false);
  179. return lookup.ToList(resultSelector);
  180. }
  181. public async Task<int> GetCountAsync(bool onlyIfCheap, CancellationToken cancellationToken)
  182. {
  183. if (onlyIfCheap)
  184. {
  185. return -1;
  186. }
  187. var lookup = await Internal.Lookup<TKey, TSource>.CreateAsync(source, keySelector, comparer, cancellationToken).ConfigureAwait(false);
  188. return lookup.Count;
  189. }
  190. }
  191. internal sealed class GroupedAsyncEnumerable<TSource, TKey, TElement> : AsyncIterator<IAsyncGrouping<TKey, TElement>>, IIListProvider<IAsyncGrouping<TKey, TElement>>
  192. {
  193. private readonly IAsyncEnumerable<TSource> source;
  194. private readonly Func<TSource, TKey> keySelector;
  195. private readonly Func<TSource, TElement> elementSelector;
  196. private readonly IEqualityComparer<TKey> comparer;
  197. private Internal.Lookup<TKey, TElement> lookup;
  198. private IEnumerator<IGrouping<TKey, TElement>> enumerator;
  199. public GroupedAsyncEnumerable(IAsyncEnumerable<TSource> source, Func<TSource, TKey> keySelector, Func<TSource, TElement> elementSelector, IEqualityComparer<TKey> comparer)
  200. {
  201. if (source == null) throw new ArgumentNullException(nameof(source));
  202. if (keySelector == null) throw new ArgumentNullException(nameof(keySelector));
  203. if (elementSelector == null) throw new ArgumentNullException(nameof(elementSelector));
  204. this.source = source;
  205. this.keySelector = keySelector;
  206. this.elementSelector = elementSelector;
  207. this.comparer = comparer;
  208. }
  209. public override AsyncIterator<IAsyncGrouping<TKey, TElement>> Clone()
  210. {
  211. return new GroupedAsyncEnumerable<TSource, TKey, TElement>(source, keySelector, elementSelector, comparer);
  212. }
  213. public override void Dispose()
  214. {
  215. if (enumerator != null)
  216. {
  217. enumerator.Dispose();
  218. enumerator = null;
  219. lookup = null;
  220. }
  221. base.Dispose();
  222. }
  223. protected override async Task<bool> MoveNextCore(CancellationToken cancellationToken)
  224. {
  225. switch (state)
  226. {
  227. case AsyncIteratorState.Allocated:
  228. lookup = await Internal.Lookup<TKey, TElement>.CreateAsync(source, keySelector, elementSelector, comparer, cancellationToken).ConfigureAwait(false);
  229. enumerator = lookup.GetEnumerator();
  230. state = AsyncIteratorState.Iterating;
  231. goto case AsyncIteratorState.Iterating;
  232. case AsyncIteratorState.Iterating:
  233. if (enumerator.MoveNext())
  234. {
  235. current = (IAsyncGrouping<TKey, TElement>)enumerator.Current;
  236. return true;
  237. }
  238. Dispose();
  239. break;
  240. }
  241. return false;
  242. }
  243. public async Task<IAsyncGrouping<TKey, TElement>[]> ToArrayAsync(CancellationToken cancellationToken)
  244. {
  245. IIListProvider<IAsyncGrouping<TKey, TElement>> lookup = await Internal.Lookup<TKey, TElement>.CreateAsync(source, keySelector, elementSelector, comparer, cancellationToken).ConfigureAwait(false);
  246. return await lookup.ToArrayAsync(cancellationToken).ConfigureAwait(false);
  247. }
  248. public async Task<List<IAsyncGrouping<TKey, TElement>>> ToListAsync(CancellationToken cancellationToken)
  249. {
  250. IIListProvider<IAsyncGrouping<TKey, TElement>> lookup = await Internal.Lookup<TKey, TElement>.CreateAsync(source, keySelector, elementSelector, comparer, cancellationToken).ConfigureAwait(false);
  251. return await lookup.ToListAsync(cancellationToken).ConfigureAwait(false);
  252. }
  253. public async Task<int> GetCountAsync(bool onlyIfCheap, CancellationToken cancellationToken)
  254. {
  255. if (onlyIfCheap)
  256. {
  257. return -1;
  258. }
  259. var lookup = await Internal.Lookup<TKey, TElement>.CreateAsync(source, keySelector, elementSelector, comparer, cancellationToken).ConfigureAwait(false);
  260. return lookup.Count;
  261. }
  262. }
  263. internal sealed class GroupedAsyncEnumerable<TSource, TKey> : AsyncIterator<IAsyncGrouping<TKey, TSource>>, IIListProvider<IAsyncGrouping<TKey, TSource>>
  264. {
  265. private readonly IAsyncEnumerable<TSource> source;
  266. private readonly Func<TSource, TKey> keySelector;
  267. private readonly IEqualityComparer<TKey> comparer;
  268. private Internal.Lookup<TKey, TSource> lookup;
  269. private IEnumerator<IGrouping<TKey, TSource>> enumerator;
  270. public GroupedAsyncEnumerable(IAsyncEnumerable<TSource> source, Func<TSource, TKey> keySelector, IEqualityComparer<TKey> comparer)
  271. {
  272. if (source == null) throw new ArgumentNullException(nameof(source));
  273. if (keySelector == null) throw new ArgumentNullException(nameof(keySelector));
  274. this.source = source;
  275. this.keySelector = keySelector;
  276. this.comparer = comparer;
  277. }
  278. public override AsyncIterator<IAsyncGrouping<TKey, TSource>> Clone()
  279. {
  280. return new GroupedAsyncEnumerable<TSource, TKey>(source, keySelector, comparer);
  281. }
  282. public override void Dispose()
  283. {
  284. if (enumerator != null)
  285. {
  286. enumerator.Dispose();
  287. enumerator = null;
  288. lookup = null;
  289. }
  290. base.Dispose();
  291. }
  292. protected override async Task<bool> MoveNextCore(CancellationToken cancellationToken)
  293. {
  294. switch (state)
  295. {
  296. case AsyncIteratorState.Allocated:
  297. lookup = await Internal.Lookup<TKey, TSource>.CreateAsync(source, keySelector, comparer, cancellationToken).ConfigureAwait(false);
  298. enumerator = lookup.GetEnumerator();
  299. state = AsyncIteratorState.Iterating;
  300. goto case AsyncIteratorState.Iterating;
  301. case AsyncIteratorState.Iterating:
  302. if (enumerator.MoveNext())
  303. {
  304. current = (IAsyncGrouping<TKey, TSource>)enumerator.Current;
  305. return true;
  306. }
  307. Dispose();
  308. break;
  309. }
  310. return false;
  311. }
  312. public async Task<IAsyncGrouping<TKey, TSource>[]> ToArrayAsync(CancellationToken cancellationToken)
  313. {
  314. IIListProvider<IAsyncGrouping<TKey, TSource>> lookup = await Internal.Lookup<TKey, TSource>.CreateAsync(source, keySelector, comparer, cancellationToken).ConfigureAwait(false);
  315. return await lookup.ToArrayAsync(cancellationToken).ConfigureAwait(false);
  316. }
  317. public async Task<List<IAsyncGrouping<TKey, TSource>>> ToListAsync(CancellationToken cancellationToken)
  318. {
  319. IIListProvider<IAsyncGrouping<TKey, TSource>> lookup = await Internal.Lookup<TKey, TSource>.CreateAsync(source, keySelector, comparer, cancellationToken).ConfigureAwait(false);
  320. return await lookup.ToListAsync(cancellationToken).ConfigureAwait(false);
  321. }
  322. public async Task<int> GetCountAsync(bool onlyIfCheap, CancellationToken cancellationToken)
  323. {
  324. if (onlyIfCheap)
  325. {
  326. return -1;
  327. }
  328. var lookup = await Internal.Lookup<TKey, TSource>.CreateAsync(source, keySelector, comparer, cancellationToken).ConfigureAwait(false);
  329. return lookup.Count;
  330. }
  331. }
  332. }
  333. }
  334. // Note: The type here has to be internal as System.Linq has it's own public copy we're not using
  335. namespace System.Linq.Internal
  336. {
  337. /// Adapted from System.Linq.Grouping from .NET Framework
  338. /// Source: https://github.com/dotnet/corefx/blob/b90532bc97b07234a7d18073819d019645285f1c/src/System.Linq/src/System/Linq/Grouping.cs#L64
  339. internal class Grouping<TKey, TElement> : IGrouping<TKey, TElement>, IList<TElement>, IAsyncGrouping<TKey, TElement>
  340. {
  341. internal int _count;
  342. internal TElement[] _elements;
  343. internal int _hashCode;
  344. internal Grouping<TKey, TElement> _hashNext;
  345. internal TKey _key;
  346. internal Grouping<TKey, TElement> _next;
  347. IEnumerator IEnumerable.GetEnumerator()
  348. {
  349. return GetEnumerator();
  350. }
  351. public IEnumerator<TElement> GetEnumerator()
  352. {
  353. for (var i = 0; i < _count; i++)
  354. {
  355. yield return _elements[i];
  356. }
  357. }
  358. // DDB195907: implement IGrouping<>.Key implicitly
  359. // so that WPF binding works on this property.
  360. public TKey Key
  361. {
  362. get { return _key; }
  363. }
  364. int ICollection<TElement>.Count
  365. {
  366. get { return _count; }
  367. }
  368. bool ICollection<TElement>.IsReadOnly
  369. {
  370. get { return true; }
  371. }
  372. void ICollection<TElement>.Add(TElement item)
  373. {
  374. throw new NotSupportedException(Strings.NOT_SUPPORTED);
  375. }
  376. void ICollection<TElement>.Clear()
  377. {
  378. throw new NotSupportedException(Strings.NOT_SUPPORTED);
  379. }
  380. bool ICollection<TElement>.Contains(TElement item)
  381. {
  382. return Array.IndexOf(_elements, item, 0, _count) >= 0;
  383. }
  384. void ICollection<TElement>.CopyTo(TElement[] array, int arrayIndex)
  385. {
  386. Array.Copy(_elements, 0, array, arrayIndex, _count);
  387. }
  388. bool ICollection<TElement>.Remove(TElement item)
  389. {
  390. throw new NotSupportedException(Strings.NOT_SUPPORTED);
  391. }
  392. int IList<TElement>.IndexOf(TElement item)
  393. {
  394. return Array.IndexOf(_elements, item, 0, _count);
  395. }
  396. void IList<TElement>.Insert(int index, TElement item)
  397. {
  398. throw new NotSupportedException(Strings.NOT_SUPPORTED);
  399. }
  400. void IList<TElement>.RemoveAt(int index)
  401. {
  402. throw new NotSupportedException(Strings.NOT_SUPPORTED);
  403. }
  404. TElement IList<TElement>.this[int index]
  405. {
  406. get
  407. {
  408. if (index < 0 || index >= _count)
  409. {
  410. throw new ArgumentOutOfRangeException(nameof(index));
  411. }
  412. return _elements[index];
  413. }
  414. set { throw new NotSupportedException(Strings.NOT_SUPPORTED); }
  415. }
  416. internal void Add(TElement element)
  417. {
  418. if (_elements.Length == _count)
  419. {
  420. Array.Resize(ref _elements, checked(_count*2));
  421. }
  422. _elements[_count] = element;
  423. _count++;
  424. }
  425. internal void Trim()
  426. {
  427. if (_elements.Length != _count)
  428. {
  429. Array.Resize(ref _elements, _count);
  430. }
  431. }
  432. IAsyncEnumerator<TElement> IAsyncEnumerable<TElement>.GetEnumerator()
  433. {
  434. return this.ToAsyncEnumerable().GetEnumerator();
  435. }
  436. }
  437. }