|
11 | 11 |
|
12 | 12 | namespace Open.Collections |
13 | 13 | { |
14 | | - public class LazyList<T> : DisposableBase, IReadOnlyList<T> |
| 14 | + /// <summary> |
| 15 | + /// A a thread-safe list for caching the results of an enumerable. |
| 16 | + /// Note: should be disposed manually whenever possible as the locking mechanism is a ReaderWriterLockSlim. |
| 17 | + /// </summary> |
| 18 | + public class LazyList<T> : LazyListUnsafe<T> |
15 | 19 | { |
16 | | - List<T> _cached; |
17 | | - IEnumerator<T> _enumerator; |
18 | | - |
19 | 20 | ReaderWriterLockSlim Sync; |
| 21 | + int _safeCount; |
20 | 22 |
|
| 23 | + /// <summary> |
| 24 | + /// A value indicating whether the results are known or expected to be finite. |
| 25 | + /// A list that was constructed as endless but has reached the end of the results will return false. |
| 26 | + /// </summary> |
21 | 27 | public bool IsEndless { get; private set; } |
22 | | - public LazyList(IEnumerable<T> source, bool isEndless = false) |
| 28 | + |
| 29 | + public LazyList(IEnumerable<T> source, bool isEndless = false) : base(source) |
23 | 30 | { |
24 | | - _enumerator = source.GetEnumerator(); |
25 | | - _cached = new List<T>(); |
26 | 31 | Sync = new ReaderWriterLockSlim(LockRecursionPolicy.NoRecursion); // This is important as it's possible to recurse infinitely to generate a result. :( |
27 | 32 | IsEndless = isEndless; // To indicate if a source is not allowed to fully enumerate. |
28 | 33 | } |
29 | 34 |
|
30 | 35 | protected override void OnDispose() |
31 | 36 | { |
32 | | - using (Sync.WriteLock()) |
33 | | - { |
34 | | - DisposeOf(ref _enumerator); |
35 | | - Nullify(ref _cached)?.Clear(); |
36 | | - } |
| 37 | + using (Sync.WriteLock()) base.OnDispose(); |
37 | 38 |
|
38 | 39 | DisposeOf(ref Sync); |
39 | 40 | } |
40 | 41 |
|
41 | | - public T this[int index] |
42 | | - { |
43 | | - get |
44 | | - { |
45 | | - AssertIsAlive(); |
46 | | - |
47 | | - if (index < 0) |
48 | | - throw new ArgumentOutOfRangeException(nameof(index), "Cannot be less than zero."); |
49 | | - if (!EnsureIndex(index)) |
50 | | - throw new ArgumentOutOfRangeException(nameof(index), "Greater than total count."); |
51 | | - |
52 | | - return _cached[index]; |
53 | | - } |
54 | | - } |
| 42 | + private const string MARKED_ENDLESS = "This list is marked as endless and may never complete."; |
55 | 43 |
|
56 | | - private int _safeCount; |
57 | | - public int Count |
| 44 | + /// <inheritdoc/> |
| 45 | + public override int IndexOf(T item) |
58 | 46 | { |
59 | | - get |
60 | | - { |
61 | | - AssertIsAlive(); |
62 | | - Finish(); |
63 | | - return _cached.Count; |
64 | | - } |
65 | | - } |
| 47 | + const string MESSAGE = MARKED_ENDLESS+" Use an enumerator, then Take(x).IndexOf()."; |
| 48 | + if (IsEndless) throw new InvalidOperationException(MESSAGE); |
66 | 49 |
|
67 | | - public bool TryGetValueAt(int index, out T value) |
68 | | - { |
69 | | - if (EnsureIndex(index)) |
70 | | - { |
71 | | - value = _cached[index]; |
72 | | - return true; |
73 | | - } |
74 | | - |
75 | | - value = default!; |
76 | | - return false; |
77 | | - } |
78 | | - |
79 | | - public IEnumerator<T> GetEnumerator() |
80 | | - { |
81 | | - AssertIsAlive(); |
82 | | - |
83 | | - var index = -1; |
84 | | - // Interlocked allows for multi-threaded access to this enumerator. |
85 | | - while (TryGetValueAt(Interlocked.Increment(ref index), out var value)) |
86 | | - yield return value; |
87 | | - } |
88 | | - |
89 | | - public int IndexOf(T item) |
90 | | - { |
91 | | - AssertIsAlive(); |
92 | | - if (IsEndless) |
93 | | - throw new InvalidOperationException("This list is marked as endless and may never complete. Use an enumerator, then Take(x).IndexOf()."); |
94 | | - |
95 | | - var index = 0; |
96 | | - while (EnsureIndex(index)) |
97 | | - { |
98 | | - var value = _cached[index]; |
99 | | - if (value is null) |
100 | | - { |
101 | | - if (item is null) return index; |
102 | | - } |
103 | | - else if (value.Equals(item)) |
104 | | - return index; |
105 | | - |
106 | | - index++; |
107 | | - } |
108 | | - |
109 | | - return -1; |
110 | | - |
111 | | - } |
112 | | - |
113 | | - public bool Contains(T item) |
114 | | - { |
115 | | - AssertIsAlive(); |
116 | | - return IndexOf(item) != -1; |
117 | | - } |
118 | | - |
119 | | - public void CopyTo(T[] array, int arrayIndex = 0) |
120 | | - { |
121 | | - AssertIsAlive(); |
122 | | - var len = Math.Min(IsEndless ? int.MaxValue : Count, array.Length - arrayIndex); |
123 | | - for (var i = 0; i < len; i++) |
124 | | - array[i + arrayIndex] = this[i]; |
125 | | - } |
126 | | - |
127 | | - System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() |
128 | | - { |
129 | | - return GetEnumerator(); |
| 50 | + return base.IndexOf(item); |
130 | 51 | } |
131 | 52 |
|
132 | | - private bool EnsureIndex(int maxIndex) |
| 53 | + protected override bool EnsureIndex(int maxIndex) |
133 | 54 | { |
134 | 55 | if (maxIndex < _safeCount) |
135 | 56 | return true; |
@@ -186,11 +107,11 @@ private bool EnsureIndex(int maxIndex) |
186 | 107 | return false; |
187 | 108 | } |
188 | 109 |
|
189 | | - private void Finish() |
| 110 | + protected override void Finish() |
190 | 111 | { |
191 | 112 | if (IsEndless) |
192 | | - throw new InvalidOperationException("This list is marked as endless and may never complete."); |
193 | | - while (EnsureIndex(int.MaxValue)) { } |
| 113 | + throw new InvalidOperationException(MARKED_ENDLESS); |
| 114 | + base.Finish(); |
194 | 115 | } |
195 | 116 |
|
196 | 117 | } |
|
0 commit comments