From 990f4e0f8293db22df22d647b9b1a546f39d8019 Mon Sep 17 00:00:00 2001 From: Chris Cameron Date: Wed, 11 Apr 2018 12:14:06 -0400 Subject: [PATCH 01/11] feat: Initial commit of WeakKeyDictionary for keeping caches of items that can not invalidate themselves --- .../Collections/WeakKeyDictionaryTest.cs | 158 ++++++++++ .../Collections/WeakKeyDictionary.cs | 283 ++++++++++++++++++ .../ICD.Common.Utils_SimplSharp.csproj | 1 + 3 files changed, 442 insertions(+) create mode 100644 ICD.Common.Utils.Tests/Collections/WeakKeyDictionaryTest.cs create mode 100644 ICD.Common.Utils/Collections/WeakKeyDictionary.cs diff --git a/ICD.Common.Utils.Tests/Collections/WeakKeyDictionaryTest.cs b/ICD.Common.Utils.Tests/Collections/WeakKeyDictionaryTest.cs new file mode 100644 index 0000000..21c7874 --- /dev/null +++ b/ICD.Common.Utils.Tests/Collections/WeakKeyDictionaryTest.cs @@ -0,0 +1,158 @@ +using System; +using ICD.Common.Utils.Collections; +using NUnit.Framework; + +namespace ICD.Common.Utils.Tests.Collections +{ + [TestFixture] + public sealed class WeakKeyDictionaryTest + { + private sealed class TestClass + { + } + + #region Properties + + [Test] + public void CountTest() + { +#if DEBUG + Assert.Inconclusive(); + return; +#endif + WeakKeyDictionary dict = new WeakKeyDictionary(); + Assert.AreEqual(0, dict.Count); + + TestClass instance = new TestClass(); + + dict.Add(instance, 0); + + Assert.AreEqual(1, dict.Count, "Expected the added item to increase the dictionary size"); + + GC.Collect(); + GC.WaitForPendingFinalizers(); + GC.Collect(); + + Assert.AreEqual(1, dict.Count, "Expected the dictionary to have one uncollected item"); + + // Need to actually USE the instance at some point AFTER the collection, otherwise it gets optimized out + // and is collected prematurely. + // ReSharper disable once ReturnValueOfPureMethodIsNotUsed + instance.ToString(); + + // Now clear the reference to make sure the instance gets collected. + // ReSharper disable once RedundantAssignment + instance = null; + + GC.Collect(); + GC.WaitForPendingFinalizers(); + GC.Collect(); + + Assert.AreEqual(0, dict.Count, "Expected the dictionary to be empty after collecting"); + } + + [Test] + public void KeysTest() + { + Assert.Inconclusive(); + } + + [Test] + public void ValuesTest() + { + Assert.Inconclusive(); + } + + [Test] + public void IndexerTest() + { + Assert.Inconclusive(); + } + +#endregion + +#region Methods + + [Test] + public void AddTest() + { + Assert.Inconclusive(); + } + + [Test] + public void ContainsKeyTest() + { + Assert.Inconclusive(); + } + + [Test] + public void RemoveTest() + { + Assert.Inconclusive(); + } + + [Test] + public void TryGetValueTest() + { + Assert.Inconclusive(); + } + + [Test] + public void ClearTest() + { + Assert.Inconclusive(); + } + + [Test] + public void RemoveCollectedEntries() + { +#if DEBUG + Assert.Inconclusive(); + return; +#endif + WeakKeyDictionary dict = new WeakKeyDictionary(); + + dict.RemoveCollectedEntries(); + Assert.AreEqual(0, dict.Count, "Expected new dictionary to start empty"); + + TestClass instance = new TestClass(); + + dict.Add(instance, 0); + dict.RemoveCollectedEntries(); + + Assert.AreEqual(1, dict.Count, "Expected instance to add to the dictionary count"); + + GC.Collect(); + GC.WaitForPendingFinalizers(); + GC.Collect(); + + dict.RemoveCollectedEntries(); + + Assert.AreEqual(1, dict.Count, "Expected instance to remain uncollected and stay in the dictionary"); + + // Need to actually USE the instance at some point AFTER the collection, otherwise it gets optimized out + // and is collected prematurely. + // ReSharper disable once ReturnValueOfPureMethodIsNotUsed + instance.ToString(); + + // Now clear the reference to make sure the instance gets collected. + // ReSharper disable once RedundantAssignment + instance = null; + + GC.Collect(); + GC.WaitForPendingFinalizers(); + GC.Collect(); + dict.RemoveCollectedEntries(); + + Assert.AreEqual(0, dict.Count, "Expected instance to be collected and removed from the dictionary"); + } + + [Test] + public void GetEnumeratorTest() + { + Assert.Inconclusive(); + } + +#endregion + } +} diff --git a/ICD.Common.Utils/Collections/WeakKeyDictionary.cs b/ICD.Common.Utils/Collections/WeakKeyDictionary.cs new file mode 100644 index 0000000..fbc541a --- /dev/null +++ b/ICD.Common.Utils/Collections/WeakKeyDictionary.cs @@ -0,0 +1,283 @@ +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 + { + RemoveCollectedEntries(); + return m_Dictionary.Count; + } + } + + public bool IsReadOnly { get { return false; } } + + public ICollection Keys { get { throw new NotImplementedException(); } } + + public ICollection Values { get { throw new NotImplementedException(); } } + + public TValue this[TKey key] + { + get + { + TValue output; + if (TryGetValue(key, out output)) + return output; + + throw new KeyNotFoundException(); + } + set + { + 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 + + bool ICollection>.Remove(KeyValuePair item) + { + throw new NotImplementedException(); + } + + public void Add(TKey key, TValue value) + { + if (key == null) + throw new ArgumentNullException("key"); + + WeakKeyReference weakKey = new WeakKeyReference(key, m_Comparer); + IcdConsole.PrintLine("Adding key {0} with hash code {1}", weakKey.Target, weakKey.HashCode); + + m_Dictionary.Add(weakKey, value); + + IcdConsole.PrintLine("Internal dict count is now {0}", m_Dictionary.Count); + } + + 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(); + + IcdConsole.PrintLine("-------------------"); + foreach (var item in toRemove) + IcdConsole.PrintLine("{0} - {1}", item, item.Target); + IcdConsole.PrintLine("-------------------"); + + m_Dictionary.RemoveAll(toRemove); + } + + #region ICollection + + void ICollection>.Add(KeyValuePair item) + { + Add(item.Key, item.Value); + } + + bool ICollection>.Contains(KeyValuePair item) + { + throw new NotImplementedException(); + } + + void ICollection>.CopyTo(KeyValuePair[] array, int arrayIndex) + { + throw new NotImplementedException(); + } + + #endregion + + #region IEnumerator + + public IEnumerator> GetEnumerator() + { + 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); + } + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + #endregion + + #endregion + } +} diff --git a/ICD.Common.Utils/ICD.Common.Utils_SimplSharp.csproj b/ICD.Common.Utils/ICD.Common.Utils_SimplSharp.csproj index 8038e0b..f69e8d2 100644 --- a/ICD.Common.Utils/ICD.Common.Utils_SimplSharp.csproj +++ b/ICD.Common.Utils/ICD.Common.Utils_SimplSharp.csproj @@ -74,6 +74,7 @@ + From 416bf6414e1aaa8db86db0ab8eb44fd8247b7bb7 Mon Sep 17 00:00:00 2001 From: Chris Cameron Date: Wed, 11 Apr 2018 13:05:13 -0400 Subject: [PATCH 02/11] feature: Extension method for copying an enumerable into an array --- .../Extensions/EnumerableExtensions.cs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/ICD.Common.Utils/Extensions/EnumerableExtensions.cs b/ICD.Common.Utils/Extensions/EnumerableExtensions.cs index 74e69ba..865d2ee 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. /// From 647b67fbfc9f2117f1eef21c5f79c57b7a453985 Mon Sep 17 00:00:00 2001 From: Chris Cameron Date: Wed, 11 Apr 2018 13:09:03 -0400 Subject: [PATCH 03/11] refactor: Removing debug lines --- ICD.Common.Utils/Collections/WeakKeyDictionary.cs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/ICD.Common.Utils/Collections/WeakKeyDictionary.cs b/ICD.Common.Utils/Collections/WeakKeyDictionary.cs index fbc541a..eac0a83 100644 --- a/ICD.Common.Utils/Collections/WeakKeyDictionary.cs +++ b/ICD.Common.Utils/Collections/WeakKeyDictionary.cs @@ -231,11 +231,6 @@ namespace ICD.Common.Utils.Collections .Where(weakKey => !weakKey.IsAlive) .ToArray(); - IcdConsole.PrintLine("-------------------"); - foreach (var item in toRemove) - IcdConsole.PrintLine("{0} - {1}", item, item.Target); - IcdConsole.PrintLine("-------------------"); - m_Dictionary.RemoveAll(toRemove); } From 3e780a2cba114704d5407f3636eae73bff2623e3 Mon Sep 17 00:00:00 2001 From: Chris Cameron Date: Wed, 11 Apr 2018 13:09:20 -0400 Subject: [PATCH 04/11] feature: Implementing remaining WeakKeyDictionary members --- .../Collections/WeakKeyDictionary.cs | 46 ++++++++----------- 1 file changed, 20 insertions(+), 26 deletions(-) diff --git a/ICD.Common.Utils/Collections/WeakKeyDictionary.cs b/ICD.Common.Utils/Collections/WeakKeyDictionary.cs index eac0a83..8801c2a 100644 --- a/ICD.Common.Utils/Collections/WeakKeyDictionary.cs +++ b/ICD.Common.Utils/Collections/WeakKeyDictionary.cs @@ -116,20 +116,13 @@ namespace ICD.Common.Utils.Collections #region Properties - public int Count - { - get - { - RemoveCollectedEntries(); - return m_Dictionary.Count; - } - } + public int Count { get { return GetAliveKvps().Count(); } } public bool IsReadOnly { get { return false; } } - public ICollection Keys { get { throw new NotImplementedException(); } } + public ICollection Keys { get { return GetAliveKvps().Select(kvp => kvp.Key).ToArray(); } } - public ICollection Values { get { throw new NotImplementedException(); } } + public ICollection Values { get { return GetAliveKvps().Select(kvp => kvp.Value).ToArray(); } } public TValue this[TKey key] { @@ -178,22 +171,13 @@ namespace ICD.Common.Utils.Collections #region Methods - bool ICollection>.Remove(KeyValuePair item) - { - throw new NotImplementedException(); - } - public void Add(TKey key, TValue value) { if (key == null) throw new ArgumentNullException("key"); WeakKeyReference weakKey = new WeakKeyReference(key, m_Comparer); - IcdConsole.PrintLine("Adding key {0} with hash code {1}", weakKey.Target, weakKey.HashCode); - m_Dictionary.Add(weakKey, value); - - IcdConsole.PrintLine("Internal dict count is now {0}", m_Dictionary.Count); } public bool ContainsKey(TKey key) @@ -241,21 +225,22 @@ namespace ICD.Common.Utils.Collections Add(item.Key, item.Value); } + bool ICollection>.Remove(KeyValuePair item) + { + return Remove(item.Key); + } + bool ICollection>.Contains(KeyValuePair item) { - throw new NotImplementedException(); + return ContainsKey(item.Key); } void ICollection>.CopyTo(KeyValuePair[] array, int arrayIndex) { - throw new NotImplementedException(); + GetAliveKvps().CopyTo(array, arrayIndex); } - #endregion - - #region IEnumerator - - public IEnumerator> GetEnumerator() + private IEnumerable> GetAliveKvps() { foreach (KeyValuePair, TValue> kvp in m_Dictionary) { @@ -266,6 +251,15 @@ namespace ICD.Common.Utils.Collections } } + #endregion + + #region IEnumerator + + public IEnumerator> GetEnumerator() + { + return GetAliveKvps().GetEnumerator(); + } + IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); From 374aa65af5b2fdff461e828c26601343e7f5b8c8 Mon Sep 17 00:00:00 2001 From: Chris Cameron Date: Wed, 11 Apr 2018 13:10:19 -0400 Subject: [PATCH 05/11] refactor: whitespace --- ICD.Common.Utils/Collections/WeakKeyDictionary.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/ICD.Common.Utils/Collections/WeakKeyDictionary.cs b/ICD.Common.Utils/Collections/WeakKeyDictionary.cs index 8801c2a..d8fc681 100644 --- a/ICD.Common.Utils/Collections/WeakKeyDictionary.cs +++ b/ICD.Common.Utils/Collections/WeakKeyDictionary.cs @@ -103,7 +103,6 @@ namespace ICD.Common.Utils.Collections } } - /// /// WeakDictionary keeps weak references to keys and drops key/value pairs once the key is garbage collected. /// From e70e9b21b0fc55f50db7d7a1c84930bf7064d1ed Mon Sep 17 00:00:00 2001 From: Chris Cameron Date: Wed, 11 Apr 2018 13:58:33 -0400 Subject: [PATCH 06/11] chore: renaming CHANGELOG.txt to CHANGELOG.md --- CHANGELOG.txt => CHANGELOG.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename CHANGELOG.txt => CHANGELOG.md (100%) diff --git a/CHANGELOG.txt b/CHANGELOG.md similarity index 100% rename from CHANGELOG.txt rename to CHANGELOG.md From 27e3e55af4adb953747a145ea00ba8260fd2c669 Mon Sep 17 00:00:00 2001 From: Chris Cameron Date: Wed, 11 Apr 2018 14:33:11 -0400 Subject: [PATCH 07/11] fix: Removing bad validation --- ICD.Common.Utils/Extensions/EventHandlerExtensions.cs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/ICD.Common.Utils/Extensions/EventHandlerExtensions.cs b/ICD.Common.Utils/Extensions/EventHandlerExtensions.cs index cb812b7..6e222db 100644 --- a/ICD.Common.Utils/Extensions/EventHandlerExtensions.cs +++ b/ICD.Common.Utils/Extensions/EventHandlerExtensions.cs @@ -14,9 +14,6 @@ namespace ICD.Common.Utils.Extensions /// public static void Raise(this EventHandler extends, object sender) { - if (sender == null) - throw new ArgumentNullException("sender"); - if (extends != null) extends(sender, EventArgs.Empty); } @@ -31,9 +28,6 @@ namespace ICD.Common.Utils.Extensions public static void Raise(this EventHandler extends, object sender, T args) where T : EventArgs { - if (sender == null) - throw new ArgumentNullException("sender"); - if (args == null) throw new ArgumentNullException("args"); From 0eba82e3ef800e0aebe15175106366a16fc3ceb0 Mon Sep 17 00:00:00 2001 From: Chris Cameron Date: Wed, 11 Apr 2018 15:32:04 -0400 Subject: [PATCH 08/11] feat: Extension method for reading JSON value as a UInt --- ICD.Common.Utils/Extensions/JsonExtensions.cs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/ICD.Common.Utils/Extensions/JsonExtensions.cs b/ICD.Common.Utils/Extensions/JsonExtensions.cs index 77a0eb0..ac56fa2 100644 --- a/ICD.Common.Utils/Extensions/JsonExtensions.cs +++ b/ICD.Common.Utils/Extensions/JsonExtensions.cs @@ -75,6 +75,24 @@ namespace ICD.Common.Utils.Extensions return Type.GetType(value); } + /// + /// Gets the current value as an unsigned integer. + /// + /// + /// + [PublicAPI] + public static uint GetValueAsUInt(this JsonReader extends) + { + if (extends == null) + throw new ArgumentNullException("extends"); + + if (extends.TokenType == JsonToken.Integer) + return (uint)(long)extends.Value; + + string message = string.Format("Token {0} {1} is not {2}", extends.TokenType, extends.Value, JsonToken.Integer); + throw new InvalidCastException(message); + } + /// /// Gets the current value as an integer. /// From 2b49354fd6b91c72a9599b2a2f78b2e72481b71a Mon Sep 17 00:00:00 2001 From: Chris Cameron Date: Thu, 12 Apr 2018 13:34:32 -0400 Subject: [PATCH 09/11] feat: Reflection util for subscribing to an EventInfo --- ICD.Common.Utils/ReflectionUtils.cs | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/ICD.Common.Utils/ReflectionUtils.cs b/ICD.Common.Utils/ReflectionUtils.cs index 1260bb6..d21b9c9 100644 --- a/ICD.Common.Utils/ReflectionUtils.cs +++ b/ICD.Common.Utils/ReflectionUtils.cs @@ -414,5 +414,26 @@ namespace ICD.Common.Utils throw new InvalidCastException(message, e); } } + + /// + /// Subscribes to the event on the given instance using the handler and callback method. + /// + /// + /// + /// + /// + /// + public static Delegate SubscribeEvent(object instance, EventInfo eventInfo, object handler, MethodInfo callback) + { + if (eventInfo == null) + throw new ArgumentNullException("eventInfo"); + + if (callback == null) + throw new ArgumentNullException("callback"); + + Delegate output = CreateDelegate(eventInfo.EventHandlerType, handler, callback); + eventInfo.AddEventHandler(instance, output); + return output; + } } } From bc1ecf8b8e278e5ac9b8e443e99e668d402b5539 Mon Sep 17 00:00:00 2001 From: Chris Cameron Date: Thu, 12 Apr 2018 14:16:53 -0400 Subject: [PATCH 10/11] feat: Reflection method for unsubscribing from an event. --- ICD.Common.Utils/ReflectionUtils.cs | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/ICD.Common.Utils/ReflectionUtils.cs b/ICD.Common.Utils/ReflectionUtils.cs index d21b9c9..534ca34 100644 --- a/ICD.Common.Utils/ReflectionUtils.cs +++ b/ICD.Common.Utils/ReflectionUtils.cs @@ -418,10 +418,10 @@ namespace ICD.Common.Utils /// /// Subscribes to the event on the given instance using the handler and callback method. /// - /// - /// - /// - /// + /// The instance with the event. Null for static types. + /// The EventInfo for the event. + /// The instance with the callback MethodInfo. Null for static types. + /// The MethodInfo for the callback method. /// public static Delegate SubscribeEvent(object instance, EventInfo eventInfo, object handler, MethodInfo callback) { @@ -435,5 +435,22 @@ namespace ICD.Common.Utils eventInfo.AddEventHandler(instance, output); return output; } + + /// + /// Unsubscribes from the event on the given instance. + /// + /// The instance with the event. Null for static types. + /// The EventInfo for the event. + /// The Delegate to be removed from the event. + public static void UnsubscribeEvent(object instance, EventInfo eventInfo, Delegate callback) + { + if (eventInfo == null) + throw new ArgumentNullException("eventInfo"); + + if (callback == null) + throw new ArgumentNullException("callback"); + + eventInfo.RemoveEventHandler(instance, callback); + } } } From 0859e7433fae4cfa4daf18d60514a1fd9b037ca6 Mon Sep 17 00:00:00 2001 From: Chris Cameron Date: Fri, 13 Apr 2018 14:03:13 -0400 Subject: [PATCH 11/11] feat: Shims for adding a sequence of items to a sorted list --- ICD.Common.Utils/Extensions/ListExtensions.cs | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/ICD.Common.Utils/Extensions/ListExtensions.cs b/ICD.Common.Utils/Extensions/ListExtensions.cs index 9e72772..e5394a8 100644 --- a/ICD.Common.Utils/Extensions/ListExtensions.cs +++ b/ICD.Common.Utils/Extensions/ListExtensions.cs @@ -10,6 +10,46 @@ namespace ICD.Common.Utils.Extensions /// public static class ListExtensions { + /// + /// Adds the items into a sorted list. + /// + /// + /// + /// + [PublicAPI] + public static void AddSorted(this List extends, IEnumerable items) + { + if (extends == null) + throw new ArgumentNullException("extends"); + + if (items == null) + throw new ArgumentNullException("items"); + + extends.AddSorted(items, Comparer.Default); + } + + /// + /// Adds the item into a sorted list. + /// + /// + /// + /// + /// + [PublicAPI] + public static void AddSorted(this List extends, IEnumerable items, IComparer comparer) + { + if (extends == null) + throw new ArgumentNullException("extends"); + + if (items == null) + throw new ArgumentNullException("items"); + + if (comparer == null) + throw new ArgumentNullException("comparer"); + + items.ForEach(i => extends.AddSorted(i, comparer)); + } + /// /// Adds the item into a sorted list. ///