diff --git a/ICD.Common.Utils/Collections/IcdHashSet.cs b/ICD.Common.Utils/Collections/IcdHashSet.cs
index 8561c70..17fbe65 100644
--- a/ICD.Common.Utils/Collections/IcdHashSet.cs
+++ b/ICD.Common.Utils/Collections/IcdHashSet.cs
@@ -67,6 +67,11 @@ namespace ICD.Common.Utils.Collections
#region Methods
+ ///
+ /// Returns a set containing all of this sets items plus all of the items in the given set.
+ ///
+ ///
+ ///
[PublicAPI]
public IcdHashSet Union(IEnumerable set)
{
@@ -80,6 +85,11 @@ namespace ICD.Common.Utils.Collections
return unionSet;
}
+ ///
+ /// Returns a new set of this sets items exluding the items in the given set.
+ ///
+ ///
+ ///
[PublicAPI]
public IcdHashSet Subtract(IEnumerable set)
{
@@ -94,13 +104,11 @@ namespace ICD.Common.Utils.Collections
return subtractSet;
}
- [PublicAPI]
- public bool IsSubsetOf(IcdHashSet set)
- {
- IcdHashSet setToCompare = set ?? NullSet;
- return this.All(setToCompare.Contains);
- }
-
+ ///
+ /// Returns all of the items that are common between this set and the given set.
+ ///
+ ///
+ ///
[PublicAPI]
public IcdHashSet Intersection(IcdHashSet set)
{
@@ -126,32 +134,74 @@ namespace ICD.Common.Utils.Collections
[PublicAPI]
public IcdHashSet NonIntersection(IcdHashSet set)
{
- return Subtract(set).Union(set.Subtract(this));
+ IcdHashSet setToCompare = set ?? NullSet;
+
+ return Subtract(set).Union(setToCompare.Subtract(this));
}
+ ///
+ /// Returns true if the given set contains all of the items in this set.
+ ///
+ ///
+ ///
+ [PublicAPI]
+ public bool IsSubsetOf(IcdHashSet set)
+ {
+ IcdHashSet setToCompare = set ?? NullSet;
+
+ return this.All(setToCompare.Contains);
+ }
+
+ ///
+ /// Returns true if the given set contains all of the items in this set, and the sets are not equal.
+ ///
+ ///
+ ///
[PublicAPI]
public bool IsProperSubsetOf(IcdHashSet set)
{
IcdHashSet setToCompare = set ?? NullSet;
- // Is a proper subset if A is a subset of B and A != B
- return (IsSubsetOf(setToCompare) && !setToCompare.IsSubsetOf(this));
+ return IsSubsetOf(setToCompare) && !setToCompare.IsSubsetOf(this);
}
+ ///
+ /// Returns true if this set contains all of the items in the given set.
+ ///
+ ///
+ ///
[PublicAPI]
public bool IsSupersetOf(IcdHashSet set)
{
IcdHashSet setToCompare = set ?? NullSet;
+
return setToCompare.IsSubsetOf(this);
}
+ ///
+ /// Returns true if this set contains all of the items in the given set, and the sets are not equal.
+ ///
+ ///
+ ///
[PublicAPI]
public bool IsProperSupersetOf(IcdHashSet set)
{
IcdHashSet setToCompare = set ?? NullSet;
- // B is a proper superset of A if B is a superset of A and A != B
- return (IsSupersetOf(setToCompare) && !setToCompare.IsSupersetOf(this));
+ return IsSupersetOf(setToCompare) && !setToCompare.IsSupersetOf(this);
+ }
+
+ ///
+ /// Returns true if this set contains all of the items in the given set, and vice versa.
+ ///
+ ///
+ ///
+ [PublicAPI]
+ public bool SetEquals(IcdHashSet set)
+ {
+ IcdHashSet setToCompare = set ?? NullSet;
+
+ return IsSupersetOf(setToCompare) && setToCompare.IsSupersetOf(this);
}
#endregion
@@ -165,9 +215,9 @@ namespace ICD.Common.Utils.Collections
///
public bool Add(T item)
{
-// ReSharper disable CompareNonConstrainedGenericWithNull
+ // ReSharper disable CompareNonConstrainedGenericWithNull
if (item == null)
-// ReSharper restore CompareNonConstrainedGenericWithNull
+ // ReSharper restore CompareNonConstrainedGenericWithNull
throw new ArgumentNullException("item");
if (m_Dict.ContainsKey(item))
diff --git a/ICD.Common.Utils/Collections/IcdOrderedDictionary.cs b/ICD.Common.Utils/Collections/IcdOrderedDictionary.cs
new file mode 100644
index 0000000..a688522
--- /dev/null
+++ b/ICD.Common.Utils/Collections/IcdOrderedDictionary.cs
@@ -0,0 +1,159 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
+using ICD.Common.Utils.Extensions;
+
+namespace ICD.Common.Utils.Collections
+{
+ public sealed class IcdOrderedDictionary : IDictionary
+ {
+ private readonly List m_OrderedKeys;
+ private readonly Dictionary m_Dictionary;
+ private readonly IComparer m_Comparer;
+
+ #region Properties
+
+ public int Count { get { return m_Dictionary.Count; } }
+
+ public bool IsReadOnly { get { return false; } }
+
+ public ICollection Keys { get { return m_OrderedKeys; } }
+
+ public ICollection Values
+ {
+ get
+ {
+ return m_OrderedKeys.Select(k => m_Dictionary[k])
+ .ToArray(Count);
+ }
+ }
+
+ public TValue this[TKey key]
+ {
+ get { return m_Dictionary[key]; }
+ set
+ {
+// ReSharper disable once CompareNonConstrainedGenericWithNull
+ if (key == null)
+ throw new ArgumentNullException("key");
+
+ if (!ContainsKey(key))
+ m_OrderedKeys.AddSorted(key, m_Comparer);
+
+ m_Dictionary[key] = value;
+ }
+ }
+
+ #endregion
+
+ ///
+ /// Constructor.
+ ///
+ public IcdOrderedDictionary()
+ : this(Comparer.Default)
+ {
+ }
+
+ ///
+ /// Constructor.
+ ///
+ ///
+ public IcdOrderedDictionary(IComparer comparer)
+ {
+ if (comparer == null)
+ throw new ArgumentNullException("comparer");
+
+ m_Comparer = comparer;
+ m_OrderedKeys = new List();
+ m_Dictionary = new Dictionary();
+ }
+
+ #region Methods
+
+ public IEnumerator> GetEnumerator()
+ {
+ return m_OrderedKeys.Select(k => new KeyValuePair(k, m_Dictionary[k]))
+ .GetEnumerator();
+ }
+
+ public void Add(TKey key, TValue value)
+ {
+// ReSharper disable once CompareNonConstrainedGenericWithNull
+ if (key == null)
+ throw new ArgumentNullException("key");
+
+ if (m_Dictionary.ContainsKey(key))
+ throw new ArgumentException("An item with the same key has already been added.", "key");
+
+ this[key] = value;
+ }
+
+ public void Clear()
+ {
+ m_OrderedKeys.Clear();
+ m_Dictionary.Clear();
+ }
+
+ public bool ContainsKey(TKey key)
+ {
+ return m_Dictionary.ContainsKey(key);
+ }
+
+ public bool Remove(TKey key)
+ {
+// ReSharper disable once CompareNonConstrainedGenericWithNull
+ if (key == null)
+ throw new ArgumentNullException("key");
+
+ if (!m_Dictionary.Remove(key))
+ return false;
+
+ m_OrderedKeys.Remove(key);
+
+ return true;
+ }
+
+ public bool TryGetValue(TKey key, out TValue value)
+ {
+ return m_Dictionary.TryGetValue(key, out value);
+ }
+
+ #endregion
+
+ #region Private Methods
+
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return GetEnumerator();
+ }
+
+ void ICollection>.Add(KeyValuePair item)
+ {
+ Add(item.Key, item.Value);
+ }
+
+ bool ICollection>.Contains(KeyValuePair item)
+ {
+ TValue value;
+ return TryGetValue(item.Key, out value) &&
+ EqualityComparer.Default.Equals(value, item.Value);
+ }
+
+ void ICollection>.CopyTo(KeyValuePair[] array, int index)
+ {
+ foreach (KeyValuePair kvp in this)
+ {
+ array.SetValue(kvp, index);
+ index++;
+ }
+ }
+
+ bool ICollection>.Remove(KeyValuePair item)
+ {
+ return (this as ICollection>).Contains(item) && Remove(item.Key);
+ }
+
+ #endregion
+ }
+}
diff --git a/ICD.Common.Utils/Collections/PriorityQueue.cs b/ICD.Common.Utils/Collections/PriorityQueue.cs
new file mode 100644
index 0000000..e66ddfe
--- /dev/null
+++ b/ICD.Common.Utils/Collections/PriorityQueue.cs
@@ -0,0 +1,244 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
+using ICD.Common.Properties;
+using ICD.Common.Utils.Extensions;
+
+namespace ICD.Common.Utils.Collections
+{
+ ///
+ /// Provides a first-in first-out collection with enhanced insertion features.
+ ///
+ public sealed class PriorityQueue : IEnumerable, ICollection
+ {
+ private readonly IcdOrderedDictionary> m_PriorityToQueue;
+ private int m_Count;
+
+ #region Properties
+
+ ///
+ /// Gets the number of items in the collection.
+ ///
+ public int Count { get { return m_Count; } }
+
+ ///
+ /// Is the collection thread safe?
+ ///
+ public bool IsSynchronized { get { return false; } }
+
+ ///
+ /// Gets a reference for locking.
+ ///
+ public object SyncRoot { get { return this; } }
+
+ #endregion
+
+ ///
+ /// Constructor.
+ ///
+ public PriorityQueue()
+ {
+ m_PriorityToQueue = new IcdOrderedDictionary>();
+ }
+
+ #region Methods
+
+ ///
+ /// Clears the collection.
+ ///
+ [PublicAPI]
+ public void Clear()
+ {
+ m_PriorityToQueue.Clear();
+ m_Count = 0;
+ }
+
+ ///
+ /// Adds the item to the end of the queue.
+ ///
+ ///
+ [PublicAPI]
+ public void Enqueue(T item)
+ {
+ Enqueue(item, int.MaxValue);
+ }
+
+ ///
+ /// Adds the item to the queue with the given priority.
+ /// Lower values are dequeued first.
+ ///
+ ///
+ ///
+ [PublicAPI]
+ public void Enqueue(T item, int priority)
+ {
+ if (!m_PriorityToQueue.ContainsKey(priority))
+ m_PriorityToQueue.Add(priority, new List());
+
+ m_PriorityToQueue[priority].Add(item);
+ m_Count++;
+ }
+
+ ///
+ /// Enqueues the item at the beginning of the queue.
+ ///
+ ///
+ [PublicAPI]
+ public void EnqueueFirst(T item)
+ {
+ const int priority = int.MinValue;
+
+ if (!m_PriorityToQueue.ContainsKey(priority))
+ m_PriorityToQueue.Add(priority, new List());
+
+ m_PriorityToQueue[priority].Insert(0, item);
+ m_Count++;
+ }
+
+ ///
+ /// Removes any items in the queue matching the predicate.
+ /// Inserts the given item in the position of the first removed item, or at the end of the queue.
+ /// This is useful for reducing duplication, or replacing items with something more pertinant.
+ ///
+ ///
+ ///
+ [PublicAPI]
+ public void EnqueueRemove(T item, Func remove)
+ {
+ if (remove == null)
+ throw new ArgumentNullException("remove");
+
+ EnqueueRemove(item, remove, int.MaxValue);
+ }
+
+ ///
+ /// Removes any items in the queue matching the predicate.
+ /// Inserts the given item in the position of the first removed item, or at the end of the queue.
+ /// This is useful for reducing duplication, or replacing items with something more pertinant.
+ ///
+ ///
+ ///
+ ///
+ [PublicAPI]
+ public void EnqueueRemove(T item, Func remove, int priority)
+ {
+ if (remove == null)
+ throw new ArgumentNullException("remove");
+
+ bool inserted = false;
+
+ foreach (KeyValuePair> kvp in m_PriorityToQueue)
+ {
+ int[] removeIndices =
+ kvp.Value
+ .FindIndices(v => remove(v))
+ .Reverse()
+ .ToArray();
+
+ if (removeIndices.Length == 0)
+ continue;
+
+ foreach (int removeIndex in removeIndices)
+ {
+ kvp.Value.RemoveAt(removeIndex);
+ m_Count--;
+ }
+
+ if (!inserted)
+ {
+ int insertIndex = removeIndices[0];
+ kvp.Value.Insert(insertIndex, item);
+ m_Count++;
+
+ inserted = true;
+ }
+ }
+
+ if (!inserted)
+ Enqueue(item, priority);
+ }
+
+ ///
+ /// Dequeues the first item with the lowest priority value.
+ ///
+ ///
+ [PublicAPI]
+ public T Dequeue()
+ {
+ T output;
+ if (TryDequeue(out output))
+ return output;
+
+ throw new InvalidOperationException("The queue is empty.");
+ }
+
+ ///
+ /// Attempts to dequeue an item from the queue.
+ ///
+ ///
+ ///
+ [PublicAPI]
+ public bool TryDequeue(out T output)
+ {
+ output = default(T);
+
+ KeyValuePair> kvp;
+ if (!m_PriorityToQueue.TryFirst(out kvp))
+ return false;
+
+ int priority = kvp.Key;
+ List queue = kvp.Value;
+
+ output = queue[0];
+ queue.RemoveAt(0);
+
+ if (queue.Count == 0)
+ m_PriorityToQueue.Remove(priority);
+
+ m_Count--;
+
+ return true;
+ }
+
+ ///
+ /// Gets an enumerator for the items.
+ ///
+ ///
+ public IEnumerator GetEnumerator()
+ {
+ return m_PriorityToQueue.Values
+ .SelectMany(v => v)
+ .GetEnumerator();
+ }
+
+ ///
+ /// Copies the collection to the target array.
+ ///
+ ///
+ ///
+ public void CopyTo(Array array, int index)
+ {
+ foreach (T item in this)
+ {
+ array.SetValue(item, index);
+ index++;
+ }
+ }
+
+ #endregion
+
+ #region Private Methods
+
+ ///
+ /// Gets an enumerator for the items.
+ ///
+ ///
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return GetEnumerator();
+ }
+
+ #endregion
+ }
+}
diff --git a/ICD.Common.Utils/Collections/WeakKeyDictionary.cs b/ICD.Common.Utils/Collections/WeakKeyDictionary.cs
new file mode 100644
index 0000000..402718c
--- /dev/null
+++ b/ICD.Common.Utils/Collections/WeakKeyDictionary.cs
@@ -0,0 +1,273 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
+using ICD.Common.Properties;
+using ICD.Common.Utils.Extensions;
+
+namespace ICD.Common.Utils.Collections
+{
+ public sealed class WeakKeyReference : WeakReference
+ {
+ private readonly int m_HashCode;
+
+ public new T Target { get { return (T)base.Target; } }
+
+ public int HashCode { get { return m_HashCode; } }
+
+ ///
+ /// Constructor.
+ ///
+ ///
+ public WeakKeyReference(T key)
+ : this(key, EqualityComparer.Default)
+ {
+ }
+
+ ///
+ /// Constructor.
+ ///
+ ///
+ ///
+ public WeakKeyReference(T key, IEqualityComparer comparer)
+ : base(key)
+ {
+ if (comparer == null)
+ throw new ArgumentNullException("comparer");
+
+ // Retain the object's hash code immediately so that even
+ // if the target is GC'ed we will be able to find and
+ // remove the dead weak reference.
+ m_HashCode = comparer.GetHashCode(key);
+ }
+ }
+
+ public sealed class WeakKeyComparer : IEqualityComparer>
+ {
+ private readonly IEqualityComparer m_Comparer;
+
+ ///
+ /// Constructor.
+ ///
+ ///
+ public WeakKeyComparer(IEqualityComparer comparer)
+ {
+ if (comparer == null)
+ comparer = EqualityComparer.Default;
+
+ m_Comparer = comparer;
+ }
+
+ public int GetHashCode(WeakKeyReference weakKey)
+ {
+ return weakKey.HashCode;
+ }
+
+ // Note: There are actually 9 cases to handle here.
+ //
+ // Let Wa = Alive Weak Reference
+ // Let Wd = Dead Weak Reference
+ // Let S = Strong Reference
+ //
+ // x | y | Equals(x,y)
+ // -------------------------------------------------
+ // Wa | Wa | comparer.Equals(x.Target, y.Target)
+ // Wa | Wd | false
+ // Wa | S | comparer.Equals(x.Target, y)
+ // Wd | Wa | false
+ // Wd | Wd | x == y
+ // Wd | S | false
+ // S | Wa | comparer.Equals(x, y.Target)
+ // S | Wd | false
+ // S | S | comparer.Equals(x, y)
+ // -------------------------------------------------
+ public bool Equals(WeakKeyReference x, WeakKeyReference y)
+ {
+ bool xIsDead;
+ bool yIsDead;
+
+ T first = GetTarget(x, out xIsDead);
+ T second = GetTarget(y, out yIsDead);
+
+ if (xIsDead)
+ return yIsDead && x == y;
+
+ return !yIsDead && m_Comparer.Equals(first, second);
+ }
+
+ private static T GetTarget(WeakKeyReference obj, out bool isDead)
+ {
+ T target = obj.Target;
+ isDead = !obj.IsAlive;
+ return target;
+ }
+ }
+
+ ///
+ /// WeakDictionary keeps weak references to keys and drops key/value pairs once the key is garbage collected.
+ ///
+ ///
+ ///
+ public sealed class WeakKeyDictionary : IDictionary
+ {
+ private readonly Dictionary, TValue> m_Dictionary;
+ private readonly IEqualityComparer m_Comparer;
+
+ #region Properties
+
+ public int Count { get { return GetAliveKvps().Count(); } }
+
+ public bool IsReadOnly { get { return false; } }
+
+ public ICollection Keys { get { return GetAliveKvps().Select(kvp => kvp.Key).ToArray(); } }
+
+ public ICollection Values { get { return GetAliveKvps().Select(kvp => kvp.Value).ToArray(); } }
+
+ public TValue this[TKey key]
+ {
+ get
+ {
+ TValue output;
+ if (TryGetValue(key, out output))
+ return output;
+
+ throw new KeyNotFoundException();
+ }
+ set
+ {
+// ReSharper disable once CompareNonConstrainedGenericWithNull
+ if (key == null)
+ throw new ArgumentNullException("key");
+
+ WeakKeyReference weakKey = new WeakKeyReference(key, m_Comparer);
+ m_Dictionary[weakKey] = value;
+ }
+ }
+
+ #endregion
+
+ public WeakKeyDictionary()
+ : this(0)
+ {
+ }
+
+ public WeakKeyDictionary(int capacity)
+ : this(capacity, EqualityComparer.Default)
+ {
+ }
+
+ public WeakKeyDictionary(IEqualityComparer comparer)
+ : this(0, comparer)
+ {
+ }
+
+ public WeakKeyDictionary(int capacity, IEqualityComparer comparer)
+ {
+ m_Comparer = comparer;
+
+ WeakKeyComparer keyComparer = new WeakKeyComparer(m_Comparer);
+ m_Dictionary = new Dictionary, TValue>(capacity, keyComparer);
+ }
+
+ #region Methods
+
+ public void Add(TKey key, TValue value)
+ {
+// ReSharper disable once CompareNonConstrainedGenericWithNull
+ if (key == null)
+ throw new ArgumentNullException("key");
+
+ WeakKeyReference weakKey = new WeakKeyReference(key, m_Comparer);
+ m_Dictionary.Add(weakKey, value);
+ }
+
+ public bool ContainsKey(TKey key)
+ {
+ TValue unused;
+ return TryGetValue(key, out unused);
+ }
+
+ public bool Remove(TKey key)
+ {
+ return m_Dictionary.Remove(new WeakKeyReference(key, m_Comparer));
+ }
+
+ public bool TryGetValue(TKey key, out TValue value)
+ {
+ return m_Dictionary.TryGetValue(new WeakKeyReference(key, m_Comparer), out value);
+ }
+
+ public void Clear()
+ {
+ m_Dictionary.Clear();
+ }
+
+ ///
+ /// Removes the left-over weak references for entries in the dictionary
+ /// whose key or value has already been reclaimed by the garbage
+ /// collector. This will reduce the dictionary's Count by the number
+ /// of dead key-value pairs that were eliminated.
+ ///
+ [PublicAPI]
+ public void RemoveCollectedEntries()
+ {
+ IEnumerable> toRemove =
+ m_Dictionary.Select(pair => pair.Key)
+ .Where(weakKey => !weakKey.IsAlive)
+ .ToArray();
+
+ m_Dictionary.RemoveAll(toRemove);
+ }
+
+ #region ICollection
+
+ void ICollection>.Add(KeyValuePair item)
+ {
+ Add(item.Key, item.Value);
+ }
+
+ bool ICollection>.Remove(KeyValuePair item)
+ {
+ return Remove(item.Key);
+ }
+
+ bool ICollection>.Contains(KeyValuePair item)
+ {
+ return ContainsKey(item.Key);
+ }
+
+ void ICollection>.CopyTo(KeyValuePair[] array, int arrayIndex)
+ {
+ GetAliveKvps().CopyTo(array, arrayIndex);
+ }
+
+ private IEnumerable> GetAliveKvps()
+ {
+ foreach (KeyValuePair, TValue> kvp in m_Dictionary)
+ {
+ WeakKeyReference weakKey = kvp.Key;
+ TKey key = weakKey.Target;
+ if (weakKey.IsAlive)
+ yield return new KeyValuePair(key, kvp.Value);
+ }
+ }
+
+ #endregion
+
+ #region IEnumerator
+
+ public IEnumerator> GetEnumerator()
+ {
+ return GetAliveKvps().GetEnumerator();
+ }
+
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return GetEnumerator();
+ }
+
+ #endregion
+
+ #endregion
+ }
+}
diff --git a/ICD.Common.Utils/Extensions/EnumerableExtensions.cs b/ICD.Common.Utils/Extensions/EnumerableExtensions.cs
index 5233bd5..244b936 100644
--- a/ICD.Common.Utils/Extensions/EnumerableExtensions.cs
+++ b/ICD.Common.Utils/Extensions/EnumerableExtensions.cs
@@ -578,6 +578,23 @@ namespace ICD.Common.Utils.Extensions
return extends.Where(i => !comparer.Equals(item, i));
}
+ ///
+ /// Copies all the elements of the current one-dimensional array to the specified one-dimensional array
+ /// starting at the specified destination array index. The index is specified as a 32-bit integer.
+ ///
+ ///
+ ///
+ ///
+ ///
+ public static void CopyTo(this IEnumerable extends, T[] array, int index)
+ {
+ if (extends == null)
+ throw new ArgumentNullException("extends");
+
+ ICollection collection = extends as ICollection ?? extends.ToArray();
+ collection.CopyTo(array, index);
+ }
+
///
/// Returns the sequence as a IcdHashSet.
///
@@ -801,6 +818,17 @@ namespace ICD.Common.Utils.Extensions
return output;
}
+ ///
+ /// Wraps this object instance into an IEnumerable consisting of a single item.
+ ///
+ /// Type of the object.
+ /// The instance that will be wrapped.
+ /// An IEnumerable<T> consisting of a single item.
+ public static IEnumerable Yield(this T item)
+ {
+ yield return item;
+ }
+
///
/// Given a sequence [A, B, C] returns a sequence [[A, B], [B, C]]
///
@@ -1057,6 +1085,49 @@ namespace ICD.Common.Utils.Extensions
}
}
+ // since S# can't do anonymous types
+ private struct TryParseStruct
+ {
+ public readonly T value;
+ public readonly bool isParsed;
+
+ public TryParseStruct(T value, bool isParsed)
+ {
+ this.value = value;
+ this.isParsed = isParsed;
+ }
+ }
+
+ // since Func<...,T> can't specify `out` parameters
+ public delegate bool TryParseDelegate(string input, out T output);
+
+ ///
+ /// Attempts to parse each value of the enumerable,
+ /// throwing away the values that don't parse correctly.
+ ///
+ /// type to parse to
+ /// enumerable of strings to parse
+ /// TryParse function for given type
+ /// enumerable of successfully parsed values
+ public static IEnumerable TryParseSkipFailures(this IEnumerable extends,
+ TryParseDelegate tryParseFunc)
+ {
+ if (extends == null)
+ throw new ArgumentNullException("extends");
+
+ if (tryParseFunc == null)
+ throw new ArgumentNullException("tryParseFunc");
+
+ return extends.Select(str =>
+ {
+ T value;
+ bool isParsed = tryParseFunc(str, out value);
+ return new TryParseStruct(value, isParsed);
+ })
+ .Where(v => v.isParsed)
+ .Select(v => v.value);
+ }
+
#if SIMPLSHARP
///
diff --git a/ICD.Common.Utils/ICD.Common.Utils_SimplSharp.csproj b/ICD.Common.Utils/ICD.Common.Utils_SimplSharp.csproj
index 52ac708..b663b60 100644
--- a/ICD.Common.Utils/ICD.Common.Utils_SimplSharp.csproj
+++ b/ICD.Common.Utils/ICD.Common.Utils_SimplSharp.csproj
@@ -74,6 +74,9 @@
+
+
+