Lean  $LEAN_TAG$
ConcurrentSet.cs
1 /*
2  * QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
3  * Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
4  *
5  * Licensed under the Apache License, Version 2.0 (the "License");
6  * you may not use this file except in compliance with the License.
7  * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14 */
15 
16 using System;
17 using System.Collections;
18 using System.Collections.Generic;
19 using System.Collections.Specialized;
20 using System.Linq;
21 
22 namespace QuantConnect.Util
23 {
24  /// <summary>
25  /// Provides a thread-safe set collection that mimics the behavior of <see cref="HashSet{T}"/>
26  /// and will be keep insertion order
27  /// </summary>
28  /// <typeparam name="T">The item type</typeparam>
29  public class ConcurrentSet<T> : ISet<T>
30  {
31  private readonly IEnumerator<T> _emptyEnumerator = Enumerable.Empty<T>().GetEnumerator();
32  private readonly OrderedDictionary _set = new OrderedDictionary();
33  // for performance we will keep a enumerator list which we will refresh only if required
34  private readonly List<T> _enumerator = new List<T>();
35  private bool _refreshEnumerator;
36 
37  /// <summary>Gets the number of elements contained in the <see cref="T:System.Collections.Generic.ICollection`1" />.</summary>
38  /// <returns>The number of elements contained in the <see cref="T:System.Collections.Generic.ICollection`1" />.</returns>
39  public int Count
40  {
41  get
42  {
43  lock (_set)
44  {
45  return _set.Count;
46  }
47  }
48  }
49 
50  /// <summary>Gets a value indicating whether the <see cref="T:System.Collections.Generic.ICollection`1" /> is read-only.</summary>
51  /// <returns>true if the <see cref="T:System.Collections.Generic.ICollection`1" /> is read-only; otherwise, false.</returns>
52  public bool IsReadOnly => false;
53 
54  /// <summary>Adds an item to the <see cref="T:System.Collections.Generic.ICollection`1" />.</summary>
55  /// <param name="item">The object to add to the <see cref="T:System.Collections.Generic.ICollection`1" />.</param>
56  /// <exception cref="T:System.NotSupportedException">The <see cref="T:System.Collections.Generic.ICollection`1" /> is read-only.</exception>
57  void ICollection<T>.Add(T item)
58  {
59  if (item == null)
60  {
61  throw new ArgumentNullException(nameof(item));
62  }
63 
64  lock (_set)
65  {
66  AddImpl(item);
67  }
68  }
69 
70  /// <summary>Modifies the current set so that it contains all elements that are present in either the current set or the specified collection.</summary>
71  /// <param name="other">The collection to compare to the current set.</param>
72  /// <exception cref="T:System.ArgumentNullException">
73  /// <paramref name="other" /> is null.</exception>
74  public void UnionWith(IEnumerable<T> other)
75  {
76  var otherSet = other.ToHashSet();
77 
78  lock (_set)
79  {
80  foreach (var item in otherSet)
81  {
82  // wont overwrite existing references of same key
83  AddImpl(item);
84  }
85  }
86  }
87 
88  /// <summary>Modifies the current set so that it contains only elements that are also in a specified collection.</summary>
89  /// <param name="other">The collection to compare to the current set.</param>
90  /// <exception cref="T:System.ArgumentNullException">
91  /// <paramref name="other" /> is null.</exception>
92  public void IntersectWith(IEnumerable<T> other)
93  {
94  var otherSet = other.ToHashSet();
95 
96  lock (_set)
97  {
98  // remove items in '_set' that are not in 'other'
99  // enumerating this and not '_set' so its safe to modify
100  foreach (var item in this)
101  {
102  if (!otherSet.Contains(item))
103  {
104  RemoveImpl(item);
105  }
106  }
107  }
108  }
109 
110  /// <summary>Removes all elements in the specified collection from the current set.</summary>
111  /// <param name="other">The collection of items to remove from the set.</param>
112  /// <exception cref="T:System.ArgumentNullException">
113  /// <paramref name="other" /> is null.</exception>
114  public void ExceptWith(IEnumerable<T> other)
115  {
116  var otherSet = other.ToHashSet();
117 
118  lock (_set)
119  {
120  // remove items from 'other'
121  foreach (var item in otherSet)
122  {
123  RemoveImpl(item);
124  }
125  }
126  }
127 
128  /// <summary>Modifies the current set so that it contains only elements that are present either in the current set or in the specified collection, but not both. </summary>
129  /// <param name="other">The collection to compare to the current set.</param>
130  /// <exception cref="T:System.ArgumentNullException">
131  /// <paramref name="other" /> is null.</exception>
132  public void SymmetricExceptWith(IEnumerable<T> other)
133  {
134  var otherSet = other.ToHashSet();
135 
136  lock (_set)
137  {
138  foreach (var item in otherSet)
139  {
140  if (!AddImpl(item))
141  {
142  // remove items in both collections
143  RemoveImpl(item);
144  }
145  }
146  }
147  }
148 
149  /// <summary>Determines whether a set is a subset of a specified collection.</summary>
150  /// <returns>true if the current set is a subset of <paramref name="other" />; otherwise, false.</returns>
151  /// <param name="other">The collection to compare to the current set.</param>
152  /// <exception cref="T:System.ArgumentNullException">
153  /// <paramref name="other" /> is null.</exception>
154  public bool IsSubsetOf(IEnumerable<T> other)
155  {
156  var otherSet = other.ToHashSet();
157  lock (_set)
158  {
159  foreach (var item in otherSet)
160  {
161  if (!_set.Contains(item))
162  {
163  return false;
164  }
165  }
166 
167  // non-strict subset can be equal
168  return _set.Keys.Count == 0;
169  }
170  }
171 
172  /// <summary>Determines whether the current set is a superset of a specified collection.</summary>
173  /// <returns>true if the current set is a superset of <paramref name="other" />; otherwise, false.</returns>
174  /// <param name="other">The collection to compare to the current set.</param>
175  /// <exception cref="T:System.ArgumentNullException">
176  /// <paramref name="other" /> is null.</exception>
177  public bool IsSupersetOf(IEnumerable<T> other)
178  {
179  var otherSet = other.ToHashSet();
180  lock (_set)
181  {
182  foreach (DictionaryEntry item in _set)
183  {
184  if (!otherSet.Remove((T)item.Key))
185  {
186  return false;
187  }
188  }
189  }
190 
191  // non-strict superset can be equal
192  return true;
193  }
194 
195  /// <summary>Determines whether the current set is a proper (strict) superset of a specified collection.</summary>
196  /// <returns>true if the current set is a proper superset of <paramref name="other" />; otherwise, false.</returns>
197  /// <param name="other">The collection to compare to the current set. </param>
198  /// <exception cref="T:System.ArgumentNullException">
199  /// <paramref name="other" /> is null.</exception>
200  public bool IsProperSupersetOf(IEnumerable<T> other)
201  {
202  var hasOther = false;
203  var otherSet = other.ToHashSet();
204  lock (_set)
205  {
206  foreach (DictionaryEntry item in _set)
207  {
208  if (!otherSet.Remove((T)item.Key))
209  {
210  return false;
211  }
212 
213  hasOther = true;
214  }
215  }
216 
217  // to be a strict superset, _set must contain extra elements and contain all of other
218  return hasOther;
219  }
220 
221  /// <summary>Determines whether the current set is a proper (strict) subset of a specified collection.</summary>
222  /// <returns>true if the current set is a proper subset of <paramref name="other" />; otherwise, false.</returns>
223  /// <param name="other">The collection to compare to the current set.</param>
224  /// <exception cref="T:System.ArgumentNullException">
225  /// <paramref name="other" /> is null.</exception>
226  public bool IsProperSubsetOf(IEnumerable<T> other)
227  {
228  var hasOther = false;
229  var otherSet = other.ToHashSet();
230  lock (_set)
231  {
232  foreach (var item in otherSet)
233  {
234  if (!_set.Contains(item))
235  {
236  return false;
237  }
238 
239  hasOther = true;
240  }
241 
242  // to be a strict subset, other must contain extra elements and _set must contain all of other
243  return hasOther && _set.Keys.Count == 0;
244  }
245  }
246 
247  /// <summary>Determines whether the current set overlaps with the specified collection.</summary>
248  /// <returns>true if the current set and <paramref name="other" /> share at least one common element; otherwise, false.</returns>
249  /// <param name="other">The collection to compare to the current set.</param>
250  /// <exception cref="T:System.ArgumentNullException">
251  /// <paramref name="other" /> is null.</exception>
252  public bool Overlaps(IEnumerable<T> other)
253  {
254  var otherSet = other.ToHashSet();
255 
256  lock (_set)
257  {
258  foreach (var item in otherSet)
259  {
260  if (_set.Contains(item))
261  {
262  return true;
263  }
264  }
265  }
266 
267  return false;
268  }
269 
270  /// <summary>Determines whether the current set and the specified collection contain the same elements.</summary>
271  /// <returns>true if the current set is equal to <paramref name="other" />; otherwise, false.</returns>
272  /// <param name="other">The collection to compare to the current set.</param>
273  /// <exception cref="T:System.ArgumentNullException">
274  /// <paramref name="other" /> is null.</exception>
275  public bool SetEquals(IEnumerable<T> other)
276  {
277  var otherSet = other.ToHashSet();
278  lock (_set)
279  {
280  foreach (DictionaryEntry item in _set)
281  {
282  if (!otherSet.Remove((T) item.Key))
283  {
284  return false;
285  }
286  }
287  }
288 
289  return otherSet.Count == 0;
290  }
291 
292  /// <summary>Adds an element to the current set and returns a value to indicate if the element was successfully added. </summary>
293  /// <returns>true if the element is added to the set; false if the element is already in the set.</returns>
294  /// <param name="item">The element to add to the set.</param>
295  public bool Add(T item)
296  {
297  lock (_set)
298  {
299  return AddImpl(item);
300  }
301  }
302 
303  /// <summary>Removes all items from the <see cref="T:System.Collections.Generic.ICollection`1" />.</summary>
304  /// <exception cref="T:System.NotSupportedException">The <see cref="T:System.Collections.Generic.ICollection`1" /> is read-only. </exception>
305  public void Clear()
306  {
307  lock (_set)
308  {
309  _refreshEnumerator = true;
310  _set.Clear();
311  }
312  }
313 
314  /// <summary>Determines whether the <see cref="T:System.Collections.Generic.ICollection`1" /> contains a specific value.</summary>
315  /// <returns>true if <paramref name="item" /> is found in the <see cref="T:System.Collections.Generic.ICollection`1" />; otherwise, false.</returns>
316  /// <param name="item">The object to locate in the <see cref="T:System.Collections.Generic.ICollection`1" />.</param>
317  public bool Contains(T item)
318  {
319  lock (_set)
320  {
321  return item != null && _set.Contains(item);
322  }
323  }
324 
325  /// <summary>Copies the elements of the <see cref="T:System.Collections.Generic.ICollection`1" /> to an <see cref="T:System.Array" />, starting at a particular <see cref="T:System.Array" /> index.</summary>
326  /// <param name="array">The one-dimensional <see cref="T:System.Array" /> that is the destination of the elements copied from <see cref="T:System.Collections.Generic.ICollection`1" />. The <see cref="T:System.Array" /> must have zero-based indexing.</param>
327  /// <param name="arrayIndex">The zero-based index in <paramref name="array" /> at which copying begins.</param>
328  /// <exception cref="T:System.ArgumentNullException">
329  /// <paramref name="array" /> is null.</exception>
330  /// <exception cref="T:System.ArgumentOutOfRangeException">
331  /// <paramref name="arrayIndex" /> is less than 0.</exception>
332  /// <exception cref="T:System.ArgumentException">The number of elements in the source <see cref="T:System.Collections.Generic.ICollection`1" /> is greater than the available space from <paramref name="arrayIndex" /> to the end of the destination <paramref name="array" />.</exception>
333  public void CopyTo(T[] array, int arrayIndex)
334  {
335  lock (_set)
336  {
337  foreach (DictionaryEntry item in _set)
338  {
339  array[arrayIndex++] = (T) item.Key;
340  }
341  }
342  }
343 
344  /// <summary>Removes the first occurrence of a specific object from the <see cref="T:System.Collections.Generic.ICollection`1" />.</summary>
345  /// <returns>true if <paramref name="item" /> was successfully removed from the <see cref="T:System.Collections.Generic.ICollection`1" />; otherwise, false. This method also returns false if <paramref name="item" /> is not found in the original <see cref="T:System.Collections.Generic.ICollection`1" />.</returns>
346  /// <param name="item">The object to remove from the <see cref="T:System.Collections.Generic.ICollection`1" />.</param>
347  /// <exception cref="T:System.NotSupportedException">The <see cref="T:System.Collections.Generic.ICollection`1" /> is read-only.</exception>
348  public bool Remove(T item)
349  {
350  lock (_set)
351  {
352  if (item != null && _set.Contains(item))
353  {
354  RemoveImpl(item);
355  return true;
356  }
357  return false;
358  }
359  }
360 
361  /// <summary>Returns an enumerator that iterates through the collection.</summary>
362  /// <remarks>For thread safety will return a snapshot of the collection</remarks>
363  /// <returns>A <see cref="T:System.Collections.Generic.IEnumerator`1" /> that can be used to iterate through the collection.</returns>
364  /// <filterpriority>1</filterpriority>
365  public IEnumerator<T> GetEnumerator()
366  {
367  lock (_set)
368  {
369  if (_refreshEnumerator)
370  {
371  _enumerator.Clear();
372  foreach (DictionaryEntry item in _set)
373  {
374  _enumerator.Add((T)item.Key);
375  }
376 
377  _refreshEnumerator = false;
378  }
379 
380  return _enumerator.Count == 0 ? _emptyEnumerator : _enumerator.GetEnumerator();
381  }
382  }
383 
384  /// <summary>Returns an enumerator that iterates through a collection.</summary>
385  /// <remarks>For thread safety will return a snapshot of the collection</remarks>
386  /// <returns>An <see cref="T:System.Collections.IEnumerator" /> object that can be used to iterate through the collection.</returns>
387  /// <filterpriority>2</filterpriority>
388  IEnumerator IEnumerable.GetEnumerator()
389  {
390  return GetEnumerator();
391  }
392 
393  private bool AddImpl(T item)
394  {
395  if (!_set.Contains(item))
396  {
397  _refreshEnumerator = true;
398  _set.Add(item, item);
399  return true;
400  }
401 
402  return false;
403  }
404 
405  private void RemoveImpl(T item)
406  {
407  _refreshEnumerator = true;
408  _set.Remove(item);
409  }
410  }
411 }