/Ix/NET/System.Interactive/EnumerableEx.Buffering.cs

http://rx.codeplex.com · C# · 647 lines · 436 code · 92 blank · 119 comment · 68 complexity · b7e256ab3bb0804ed312575c4947c58c MD5 · raw file

  1. // Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
  2. using System;
  3. using System.Collections.Generic;
  4. using System.Diagnostics;
  5. namespace System.Linq
  6. {
  7. public static partial class EnumerableEx
  8. {
  9. /// <summary>
  10. /// Creates a buffer with a shared view over the source sequence, causing each enumerator to fetch the next element from the source sequence.
  11. /// </summary>
  12. /// <typeparam name="TSource">Source sequence element type.</typeparam>
  13. /// <param name="source">Source sequence.</param>
  14. /// <returns>Buffer enabling each enumerator to retrieve elements from the shared source sequence.</returns>
  15. /// <example>
  16. /// var rng = Enumerable.Range(0, 10).Share();
  17. ///
  18. /// var e1 = rng.GetEnumerator(); // Both e1 and e2 will consume elements from
  19. /// var e2 = rng.GetEnumerator(); // the source sequence.
  20. ///
  21. /// Assert.IsTrue(e1.MoveNext());
  22. /// Assert.AreEqual(0, e1.Current);
  23. ///
  24. /// Assert.IsTrue(e1.MoveNext());
  25. /// Assert.AreEqual(1, e1.Current);
  26. ///
  27. /// Assert.IsTrue(e2.MoveNext()); // e2 "steals" element 2
  28. /// Assert.AreEqual(2, e2.Current);
  29. ///
  30. /// Assert.IsTrue(e1.MoveNext()); // e1 can't see element 2
  31. /// Assert.AreEqual(3, e1.Current);
  32. /// </example>
  33. public static IBuffer<TSource> Share<TSource>(this IEnumerable<TSource> source)
  34. {
  35. if (source == null)
  36. throw new ArgumentNullException("source");
  37. return new SharedBuffer<TSource>(source.GetEnumerator());
  38. }
  39. /// <summary>
  40. /// Shares the source sequence within a selector function where each enumerator can fetch the next element from the source sequence.
  41. /// </summary>
  42. /// <typeparam name="TSource">Source sequence element type.</typeparam>
  43. /// <typeparam name="TResult">Result sequence element type.</typeparam>
  44. /// <param name="source">Source sequence.</param>
  45. /// <param name="selector">Selector function with shared access to the source sequence for each enumerator.</param>
  46. /// <returns>Sequence resulting from applying the selector function to the shared view over the source sequence.</returns>
  47. public static IEnumerable<TResult> Share<TSource, TResult>(this IEnumerable<TSource> source, Func<IEnumerable<TSource>, IEnumerable<TResult>> selector)
  48. {
  49. if (source == null)
  50. throw new ArgumentNullException("source");
  51. if (selector == null)
  52. throw new ArgumentNullException("selector");
  53. return Create<TResult>(() => selector(source.Share()).GetEnumerator());
  54. }
  55. class SharedBuffer<T> : IBuffer<T>
  56. {
  57. private IEnumerator<T> _source;
  58. private bool _disposed;
  59. public SharedBuffer(IEnumerator<T> source)
  60. {
  61. _source = source;
  62. }
  63. public IEnumerator<T> GetEnumerator()
  64. {
  65. if (_disposed)
  66. throw new ObjectDisposedException("");
  67. return GetEnumerator_();
  68. }
  69. private IEnumerator<T> GetEnumerator_()
  70. {
  71. while (true)
  72. {
  73. if (_disposed)
  74. throw new ObjectDisposedException("");
  75. var hasValue = default(bool);
  76. var current = default(T);
  77. lock (_source)
  78. {
  79. hasValue = _source.MoveNext();
  80. if (hasValue)
  81. current = _source.Current;
  82. }
  83. if (hasValue)
  84. yield return current;
  85. else
  86. break;
  87. }
  88. }
  89. System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
  90. {
  91. if (_disposed)
  92. throw new ObjectDisposedException("");
  93. return GetEnumerator();
  94. }
  95. public void Dispose()
  96. {
  97. lock (_source)
  98. {
  99. if (!_disposed)
  100. {
  101. _source.Dispose();
  102. _source = null;
  103. }
  104. _disposed = true;
  105. }
  106. }
  107. }
  108. /// <summary>
  109. /// Creates a buffer with a view over the source sequence, causing each enumerator to obtain access to the remainder of the sequence from the current index in the buffer.
  110. /// </summary>
  111. /// <typeparam name="TSource">Source sequence element type.</typeparam>
  112. /// <param name="source">Source sequence.</param>
  113. /// <returns>Buffer enabling each enumerator to retrieve elements from the shared source sequence, starting from the index at the point of obtaining the enumerator.</returns>
  114. /// <example>
  115. /// var rng = Enumerable.Range(0, 10).Publish();
  116. ///
  117. /// var e1 = rng.GetEnumerator(); // e1 has a view on the source starting from element 0
  118. ///
  119. /// Assert.IsTrue(e1.MoveNext());
  120. /// Assert.AreEqual(0, e1.Current);
  121. ///
  122. /// Assert.IsTrue(e1.MoveNext());
  123. /// Assert.AreEqual(1, e1.Current);
  124. ///
  125. /// var e2 = rng.GetEnumerator();
  126. ///
  127. /// Assert.IsTrue(e2.MoveNext()); // e2 has a view on the source starting from element 2
  128. /// Assert.AreEqual(2, e2.Current);
  129. ///
  130. /// Assert.IsTrue(e1.MoveNext()); // e1 continues to enumerate over its view
  131. /// Assert.AreEqual(2, e1.Current);
  132. /// </example>
  133. public static IBuffer<TSource> Publish<TSource>(this IEnumerable<TSource> source)
  134. {
  135. if (source == null)
  136. throw new ArgumentNullException("source");
  137. return new PublishedBuffer<TSource>(source.GetEnumerator());
  138. }
  139. /// <summary>
  140. /// Publishes the source sequence within a selector function where each enumerator can obtain a view over a tail of the source sequence.
  141. /// </summary>
  142. /// <typeparam name="TSource">Source sequence element type.</typeparam>
  143. /// <typeparam name="TResult">Result sequence element type.</typeparam>
  144. /// <param name="source">Source sequence.</param>
  145. /// <param name="selector">Selector function with published access to the source sequence for each enumerator.</param>
  146. /// <returns>Sequence resulting from applying the selector function to the published view over the source sequence.</returns>
  147. public static IEnumerable<TResult> Publish<TSource, TResult>(this IEnumerable<TSource> source, Func<IEnumerable<TSource>, IEnumerable<TResult>> selector)
  148. {
  149. if (source == null)
  150. throw new ArgumentNullException("source");
  151. if (selector == null)
  152. throw new ArgumentNullException("selector");
  153. return Create<TResult>(() => selector(source.Publish()).GetEnumerator());
  154. }
  155. class PublishedBuffer<T> : IBuffer<T>
  156. {
  157. private IEnumerator<T> _source;
  158. private RefCountList<T> _buffer;
  159. private bool _stopped;
  160. private Exception _error;
  161. private bool _disposed;
  162. public PublishedBuffer(IEnumerator<T> source)
  163. {
  164. _buffer = new RefCountList<T>(0);
  165. _source = source;
  166. }
  167. public IEnumerator<T> GetEnumerator()
  168. {
  169. if (_disposed)
  170. throw new ObjectDisposedException("");
  171. var i = default(int);
  172. lock (_source)
  173. {
  174. i = _buffer.Count;
  175. _buffer.ReaderCount++;
  176. }
  177. return GetEnumerator_(i);
  178. }
  179. private IEnumerator<T> GetEnumerator_(int i)
  180. {
  181. try
  182. {
  183. while (true)
  184. {
  185. if (_disposed)
  186. throw new ObjectDisposedException("");
  187. var hasValue = default(bool);
  188. var current = default(T);
  189. lock (_source)
  190. {
  191. if (i >= _buffer.Count)
  192. {
  193. if (!_stopped)
  194. {
  195. try
  196. {
  197. hasValue = _source.MoveNext();
  198. if (hasValue)
  199. current = _source.Current;
  200. }
  201. catch (Exception ex)
  202. {
  203. _stopped = true;
  204. _error = ex;
  205. _source.Dispose();
  206. }
  207. }
  208. if (_stopped)
  209. {
  210. if (_error != null)
  211. throw _error;
  212. else
  213. break;
  214. }
  215. if (hasValue)
  216. {
  217. _buffer.Add(current);
  218. }
  219. }
  220. else
  221. {
  222. hasValue = true;
  223. }
  224. }
  225. if (hasValue)
  226. yield return _buffer[i];
  227. else
  228. break;
  229. i++;
  230. }
  231. }
  232. finally
  233. {
  234. if (_buffer != null)
  235. _buffer.Done(i + 1);
  236. }
  237. }
  238. System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
  239. {
  240. if (_disposed)
  241. throw new ObjectDisposedException("");
  242. return GetEnumerator();
  243. }
  244. public void Dispose()
  245. {
  246. lock (_source)
  247. {
  248. if (!_disposed)
  249. {
  250. _source.Dispose();
  251. _source = null;
  252. _buffer.Clear();
  253. _buffer = null;
  254. }
  255. _disposed = true;
  256. }
  257. }
  258. }
  259. /// <summary>
  260. /// Creates a buffer with a view over the source sequence, causing each enumerator to obtain access to all of the sequence's elements without causing multiple enumerations over the source.
  261. /// </summary>
  262. /// <typeparam name="TSource">Source sequence element type.</typeparam>
  263. /// <param name="source">Source sequence.</param>
  264. /// <returns>Buffer enabling each enumerator to retrieve all elements from the shared source sequence, without duplicating source enumeration side-effects.</returns>
  265. /// <example>
  266. /// var rng = Enumerable.Range(0, 10).Do(x => Console.WriteLine(x)).Memoize();
  267. ///
  268. /// var e1 = rng.GetEnumerator();
  269. ///
  270. /// Assert.IsTrue(e1.MoveNext()); // Prints 0
  271. /// Assert.AreEqual(0, e1.Current);
  272. ///
  273. /// Assert.IsTrue(e1.MoveNext()); // Prints 1
  274. /// Assert.AreEqual(1, e1.Current);
  275. ///
  276. /// var e2 = rng.GetEnumerator();
  277. ///
  278. /// Assert.IsTrue(e2.MoveNext()); // Doesn't print anything; the side-effect of Do
  279. /// Assert.AreEqual(0, e2.Current); // has already taken place during e1's iteration.
  280. ///
  281. /// Assert.IsTrue(e1.MoveNext()); // Prints 2
  282. /// Assert.AreEqual(2, e1.Current);
  283. /// </example>
  284. public static IBuffer<TSource> Memoize<TSource>(this IEnumerable<TSource> source)
  285. {
  286. if (source == null)
  287. throw new ArgumentNullException("source");
  288. return new MemoizedBuffer<TSource>(source.GetEnumerator());
  289. }
  290. /// <summary>
  291. /// Memoizes the source sequence within a selector function where each enumerator can get access to all of the sequence's elements without causing multiple enumerations over the source.
  292. /// </summary>
  293. /// <typeparam name="TSource">Source sequence element type.</typeparam>
  294. /// <typeparam name="TResult">Result sequence element type.</typeparam>
  295. /// <param name="source">Source sequence.</param>
  296. /// <param name="selector">Selector function with memoized access to the source sequence for each enumerator.</param>
  297. /// <returns>Sequence resulting from applying the selector function to the memoized view over the source sequence.</returns>
  298. public static IEnumerable<TResult> Memoize<TSource, TResult>(this IEnumerable<TSource> source, Func<IEnumerable<TSource>, IEnumerable<TResult>> selector)
  299. {
  300. if (source == null)
  301. throw new ArgumentNullException("source");
  302. if (selector == null)
  303. throw new ArgumentNullException("selector");
  304. return Create<TResult>(() => selector(source.Memoize()).GetEnumerator());
  305. }
  306. /// <summary>
  307. /// Creates a buffer with a view over the source sequence, causing a specified number of enumerators to obtain access to all of the sequence's elements without causing multiple enumerations over the source.
  308. /// </summary>
  309. /// <typeparam name="TSource">Source sequence element type.</typeparam>
  310. /// <param name="source">Source sequence.</param>
  311. /// <param name="readerCount">Number of enumerators that can access the underlying buffer. Once every enumerator has obtained an element from the buffer, the element is removed from the buffer.</param>
  312. /// <returns>Buffer enabling a specified number of enumerators to retrieve all elements from the shared source sequence, without duplicating source enumeration side-effects.</returns>
  313. public static IBuffer<TSource> Memoize<TSource>(this IEnumerable<TSource> source, int readerCount)
  314. {
  315. if (source == null)
  316. throw new ArgumentNullException("source");
  317. if (readerCount <= 0)
  318. throw new ArgumentOutOfRangeException("readerCount");
  319. return new MemoizedBuffer<TSource>(source.GetEnumerator(), readerCount);
  320. }
  321. /// <summary>
  322. /// Memoizes the source sequence within a selector function where a specified number of enumerators can get access to all of the sequence's elements without causing multiple enumerations over the source.
  323. /// </summary>
  324. /// <typeparam name="TSource">Source sequence element type.</typeparam>
  325. /// <typeparam name="TResult">Result sequence element type.</typeparam>
  326. /// <param name="source">Source sequence.</param>
  327. /// <param name="readerCount">Number of enumerators that can access the underlying buffer. Once every enumerator has obtained an element from the buffer, the element is removed from the buffer.</param>
  328. /// <param name="selector">Selector function with memoized access to the source sequence for a specified number of enumerators.</param>
  329. /// <returns>Sequence resulting from applying the selector function to the memoized view over the source sequence.</returns>
  330. public static IEnumerable<TResult> Memoize<TSource, TResult>(this IEnumerable<TSource> source, int readerCount, Func<IEnumerable<TSource>, IEnumerable<TResult>> selector)
  331. {
  332. if (source == null)
  333. throw new ArgumentNullException("source");
  334. if (readerCount <= 0)
  335. throw new ArgumentOutOfRangeException("readerCount");
  336. if (selector == null)
  337. throw new ArgumentNullException("selector");
  338. return Create<TResult>(() => selector(source.Memoize(readerCount)).GetEnumerator());
  339. }
  340. class MemoizedBuffer<T> : IBuffer<T>
  341. {
  342. private IEnumerator<T> _source;
  343. private IRefCountList<T> _buffer;
  344. private bool _stopped;
  345. private Exception _error;
  346. private bool _disposed;
  347. public MemoizedBuffer(IEnumerator<T> source)
  348. : this(source, new MaxRefCountList<T>())
  349. {
  350. }
  351. public MemoizedBuffer(IEnumerator<T> source, int readerCount)
  352. : this(source, new RefCountList<T>(readerCount))
  353. {
  354. }
  355. private MemoizedBuffer(IEnumerator<T> source, IRefCountList<T> buffer)
  356. {
  357. _source = source;
  358. _buffer = buffer;
  359. }
  360. public IEnumerator<T> GetEnumerator()
  361. {
  362. if (_disposed)
  363. throw new ObjectDisposedException("");
  364. return GetEnumerator_();
  365. }
  366. private IEnumerator<T> GetEnumerator_()
  367. {
  368. var i = 0;
  369. try
  370. {
  371. while (true)
  372. {
  373. if (_disposed)
  374. throw new ObjectDisposedException("");
  375. var hasValue = default(bool);
  376. var current = default(T);
  377. lock (_source)
  378. {
  379. if (i >= _buffer.Count)
  380. {
  381. if (!_stopped)
  382. {
  383. try
  384. {
  385. hasValue = _source.MoveNext();
  386. if (hasValue)
  387. current = _source.Current;
  388. }
  389. catch (Exception ex)
  390. {
  391. _stopped = true;
  392. _error = ex;
  393. _source.Dispose();
  394. }
  395. }
  396. if (_stopped)
  397. {
  398. if (_error != null)
  399. throw _error;
  400. else
  401. break;
  402. }
  403. if (hasValue)
  404. {
  405. _buffer.Add(current);
  406. }
  407. }
  408. else
  409. {
  410. hasValue = true;
  411. }
  412. }
  413. if (hasValue)
  414. yield return _buffer[i];
  415. else
  416. break;
  417. i++;
  418. }
  419. }
  420. finally
  421. {
  422. if (_buffer != null)
  423. _buffer.Done(i + 1);
  424. }
  425. }
  426. System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
  427. {
  428. if (_disposed)
  429. throw new ObjectDisposedException("");
  430. return GetEnumerator();
  431. }
  432. public void Dispose()
  433. {
  434. lock (_source)
  435. {
  436. if (!_disposed)
  437. {
  438. _source.Dispose();
  439. _source = null;
  440. _buffer.Clear();
  441. _buffer = null;
  442. }
  443. _disposed = true;
  444. }
  445. }
  446. }
  447. }
  448. /// <summary>
  449. /// Represents a buffer exposing a shared view over an underlying enumerable sequence.
  450. /// </summary>
  451. /// <typeparam name="T">Element type.</typeparam>
  452. public interface IBuffer<
  453. #if !NO_VARIANCE && !SILVERLIGHT4 // SL4 has defined IEnumerable with invariant T
  454. out
  455. #endif
  456. T> : IEnumerable<T>, IDisposable
  457. {
  458. }
  459. interface IRefCountList<T>
  460. {
  461. void Clear();
  462. int Count { get; }
  463. T this[int i]
  464. {
  465. get;
  466. }
  467. void Add(T item);
  468. void Done(int index);
  469. }
  470. class MaxRefCountList<T> : IRefCountList<T>
  471. {
  472. private IList<T> _list = new List<T>();
  473. public void Clear()
  474. {
  475. _list.Clear();
  476. }
  477. public int Count
  478. {
  479. get { return _list.Count; }
  480. }
  481. public T this[int i]
  482. {
  483. get { return _list[i]; }
  484. }
  485. public void Add(T item)
  486. {
  487. _list.Add(item);
  488. }
  489. public void Done(int index)
  490. {
  491. }
  492. }
  493. class RefCountList<T> : IRefCountList<T>
  494. {
  495. private int _readerCount;
  496. private readonly IDictionary<int, RefCount> _list;
  497. private int _count;
  498. public RefCountList(int readerCount)
  499. {
  500. _readerCount = readerCount;
  501. _list = new Dictionary<int, RefCount>();
  502. }
  503. public int ReaderCount
  504. {
  505. get
  506. {
  507. return _readerCount;
  508. }
  509. set
  510. {
  511. _readerCount = value;
  512. }
  513. }
  514. public void Clear()
  515. {
  516. _list.Clear();
  517. }
  518. public int Count
  519. {
  520. get { return _count; }
  521. }
  522. public T this[int i]
  523. {
  524. get
  525. {
  526. Debug.Assert(i < _count);
  527. var res = default(RefCount);
  528. if (!_list.TryGetValue(i, out res))
  529. throw new InvalidOperationException("Element no longer available in the buffer.");
  530. var val = res.Value;
  531. if (--res.Count == 0)
  532. _list.Remove(i);
  533. return val;
  534. }
  535. }
  536. public void Add(T item)
  537. {
  538. _list[_count] = new RefCount { Value = item, Count = _readerCount };
  539. _count++;
  540. }
  541. public void Done(int index)
  542. {
  543. for (int i = index; i < _count; i++)
  544. {
  545. var ignore = this[i];
  546. }
  547. _readerCount--;
  548. }
  549. class RefCount
  550. {
  551. public int Count;
  552. public T Value;
  553. }
  554. }
  555. }