diff --git a/Nuclex.Support (Xbox 360).csproj b/Nuclex.Support (Xbox 360).csproj index d71fd52..ca11d6d 100644 --- a/Nuclex.Support (Xbox 360).csproj +++ b/Nuclex.Support (Xbox 360).csproj @@ -128,6 +128,13 @@ TransformingReadOnlyCollection.cs + + + WeakCollection.cs + + + WeakCollection.cs + FloatHelper.cs diff --git a/Nuclex.Support.csproj b/Nuclex.Support.csproj index 704f9b3..044e686 100644 --- a/Nuclex.Support.csproj +++ b/Nuclex.Support.csproj @@ -110,6 +110,13 @@ TransformingReadOnlyCollection.cs + + + WeakCollection.cs + + + WeakCollection.cs + FloatHelper.cs diff --git a/Source/Collections/TransformingReadOnlyCollection.Test.cs b/Source/Collections/TransformingReadOnlyCollection.Test.cs index 161ecbf..a92a20c 100644 --- a/Source/Collections/TransformingReadOnlyCollection.Test.cs +++ b/Source/Collections/TransformingReadOnlyCollection.Test.cs @@ -84,7 +84,7 @@ namespace Nuclex.Support.Collections { } /// - /// Verifies that the CopyTo() of the transforming read only collection works + /// Verifies that the CopyTo() method of the transforming read only collection works /// [Test] public void TestCopyToArray() { @@ -99,7 +99,7 @@ namespace Nuclex.Support.Collections { } /// - /// Verifies that the CopyTo() of the transforming read only collection throws + /// Verifies that the CopyTo() method of the transforming read only collection throws /// an exception if the target array is too small to hold the collection's contents /// [Test, ExpectedException(typeof(ArgumentException))] @@ -415,8 +415,8 @@ namespace Nuclex.Support.Collections { } /// - /// Verifies that the CopyTo() of the transforming read only collection works - /// if invoked via the ICollection interface + /// Verifies that the CopyTo() method of the transforming read only collection + /// works if invoked via the ICollection interface /// [Test] public void TestCopyToArrayViaICollection() { diff --git a/Source/Collections/WeakCollection.Interfaces.cs b/Source/Collections/WeakCollection.Interfaces.cs new file mode 100644 index 0000000..24726ee --- /dev/null +++ b/Source/Collections/WeakCollection.Interfaces.cs @@ -0,0 +1,211 @@ +#region CPL License +/* +Nuclex Framework +Copyright (C) 2002-2009 Nuclex Development Labs + +This library is free software; you can redistribute it and/or +modify it under the terms of the IBM Common Public License as +published by the IBM Corporation; either version 1.0 of the +License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +IBM Common Public License for more details. + +You should have received a copy of the IBM Common Public +License along with this library +*/ +#endregion + +using System; +using System.Threading; +using System.Collections; +using System.Collections.Generic; + +namespace Nuclex.Support.Collections { + + partial class WeakCollection { + + #region IEnumerable Members + + /// Returns an enumerator that iterates through a collection. + /// + /// A System.Collections.IEnumerator object that can be used to iterate through + /// the collection. + /// + IEnumerator IEnumerable.GetEnumerator() { + return GetEnumerator(); + } + + #endregion + + #region IList Members + + /// Adds an item to the WeakCollection. + /// The System.Object to add to the WeakCollection. + /// The position into which the new element was inserted. + /// + /// The System.Collections.IList is read-only or the WeakCollection has a fixed size. + /// + int IList.Add(object value) { + ItemType valueAsItemType = downcastToItemType(value); + return (this.items as IList).Add(new WeakReference(valueAsItemType)); + } + + /// + /// Determines whether the WeakCollection contains a specific value. + /// + /// The System.Object to locate in the WeakCollection. + /// + /// True if the System.Object is found in the WeakCollection; otherwise, false. + /// + bool IList.Contains(object value) { + ItemType valueAsItemType = downcastToItemType(value); + return Contains(valueAsItemType); + } + + /// Determines the index of a specific item in the WeakCollection. + /// The System.Object to locate in the WeakCollection. + /// + /// The index of value if found in the list; otherwise, -1. + /// + int IList.IndexOf(object value) { + ItemType valueAsItemType = downcastToItemType(value); + return IndexOf(valueAsItemType); + } + + /// + /// Inserts an item to the WeakCollection at the specified index. + /// + /// + /// The zero-based index at which value should be inserted. + /// + /// The System.Object to insert into the WeakCollection. + /// + /// Index is not a valid index in the TransformingReadOnlyCollection. + /// + /// + /// The System.Collections.IList is read-only or the WeakCollection has a fixed size. + /// + /// + /// Value is null reference in the WeakCollection. + /// + void IList.Insert(int index, object value) { + ItemType valueAsItemType = downcastToItemType(value); + Insert(index, valueAsItemType); + } + + /// + /// A value indicating whether the WeakCollection has a fixed size. + /// + bool IList.IsFixedSize { + get { return (this.items as IList).IsFixedSize; } + } + + /// + /// Removes the first occurrence of a specific object from the WeakCollection. + /// + /// The System.Object to remove from the WeakCollection. + /// + /// The WeakCollection is read-only or the WeakCollection has a fixed size. + /// + void IList.Remove(object value) { + ItemType valueAsItemType = downcastToItemType(value); + Remove(valueAsItemType); + } + + /// Gets or sets the element at the specified index. + /// The zero-based index of the element to get or set. + /// The element at the specified index + /// + /// Index is not a valid index in the WeakCollection + /// + object IList.this[int index] { + get { return this[index]; } + set { + ItemType valueAsItemType = downcastToItemType(value); + this[index] = valueAsItemType; + } + } + + #endregion + + #region ICollection Members + + /// + /// Copies the elements of the WeakCollection to an System.Array, starting at + /// a particular System.Array index. + /// + /// + /// The one-dimensional System.Array that is the destination of the elements + /// copied from WeakCollection. The System.Array must have zero-based indexing. + /// + /// The zero-based index in array at which copying begins. + /// + /// Array is null. + /// + /// + /// Index is less than zero. + /// + /// + /// Array is multidimensional or index is equal to or greater than the length + /// of array or the number of elements in the source WeakCollection is greater than + /// the available space from index to the end of the destination array. + /// + /// + /// The type of the source WeakCollection cannot be cast automatically to the type of + /// the destination array. + /// + void ICollection.CopyTo(Array array, int index) { + CopyTo((ItemType[])array, index); + } + + /// + /// A value indicating whether access to the WeakCollection is + /// synchronized (thread safe). + /// + bool ICollection.IsSynchronized { + get { return false; } + } + + /// + /// An object that can be used to synchronize access to the WeakCollection. + /// + object ICollection.SyncRoot { + get { + if(this.syncRoot == null) { + ICollection is2 = this.items as ICollection; + if(is2 != null) { + this.syncRoot = is2.SyncRoot; + } else { + Interlocked.CompareExchange(ref this.syncRoot, new object(), null); + } + } + + return this.syncRoot; + } + } + + #endregion + + /// + /// Downcasts an object reference to a reference to the collection's item type + /// + /// Object reference that will be downcast + /// + /// The specified object referecne as a reference to the collection's item type + /// + private static ItemType downcastToItemType(object value) { + ItemType valueAsItemType = value as ItemType; + if(!ReferenceEquals(value, null)) { + if(valueAsItemType == null) { + throw new ArgumentException("Object is not of a compatible type", "value"); + } + } + return valueAsItemType; + } + + } + +} // namespace Nuclex.Support.Collections diff --git a/Source/Collections/WeakCollection.Test.cs b/Source/Collections/WeakCollection.Test.cs new file mode 100644 index 0000000..5e9d9b1 --- /dev/null +++ b/Source/Collections/WeakCollection.Test.cs @@ -0,0 +1,655 @@ +#region CPL License +/* +Nuclex Framework +Copyright (C) 2002-2009 Nuclex Development Labs + +This library is free software; you can redistribute it and/or +modify it under the terms of the IBM Common Public License as +published by the IBM Corporation; either version 1.0 of the +License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +IBM Common Public License for more details. + +You should have received a copy of the IBM Common Public +License along with this library +*/ +#endregion + +using System; +using System.Collections; +using System.Collections.Generic; + +#if UNITTEST + +using NUnit.Framework; +using NMock2; + +namespace Nuclex.Support.Collections { + + /// Unit Test for the weak collection wrapper + [TestFixture] + public class WeakCollectionTest { + + #region class Dummy + + /// Dummy class used to test the weakly referencing collection + private class Dummy { + + /// Initializes a new dummy + /// Value that will be stored by the dummy + public Dummy(int value) { + this.Value = value; + } + + /// + /// Determines whether the specified System.Object is equal to + /// the current Dummy object. + /// + /// + /// The System.Object to compare with the current Dummy object + /// + /// + /// True if the specified System.Object is equal to the current Dummy object; + /// otherwise, false. + /// + public override bool Equals(object otherAsObject) { + Dummy other = otherAsObject as Dummy; + if(other == null) { + return false; + } + return this.Value.Equals(other.Value); + } + + /// Serves as a hash function for a particular type. + /// A hash code for the current System.Object. + public override int GetHashCode() { + return this.Value.GetHashCode(); + } + + /// Some value that can be used for testing + public int Value; + + } + + #endregion // class Dummy + + /// Verifies that the constructor of the weak collection is working + [Test] + public void TestConstructor() { + WeakCollection dummies = new WeakCollection( + new List>() + ); + Assert.IsNotNull(dummies); + } + + /// + /// Test whether the non-typesafe Add() method of the weak collection works + /// + [Test] + public void TestAddAsObject() { + WeakCollection dummies = new WeakCollection( + new List>() + ); + + Dummy oneTwoThreeDummy = new Dummy(12345); + (dummies as IList).Add((object)oneTwoThreeDummy); + + CollectionAssert.Contains(dummies, oneTwoThreeDummy); + } + + /// + /// Test whether the non-typesafe Add() method throws an exception if an object is + /// added that is not compatible to the collection's item type + /// + [Test, ExpectedException(typeof(ArgumentException))] + public void TestThrowOnAddIncompatibleObject() { + WeakCollection dummies = new WeakCollection( + new List>() + ); + + (dummies as IList).Add(new object()); + } + + /// + /// Test whether the generic Add() method of the weak collection works + /// + [Test] + public void TestAdd() { + WeakCollection dummies = new WeakCollection( + new List>() + ); + + Dummy oneTwoThreeDummy = new Dummy(12345); + dummies.Add(oneTwoThreeDummy); + + CollectionAssert.Contains(dummies, oneTwoThreeDummy); + } + + /// Tests whether the Clear() method works + [Test] + public void TestClear() { + WeakCollection dummies = new WeakCollection( + new List>() + ); + + Dummy oneTwoThreeDummy = new Dummy(12345); + dummies.Add(oneTwoThreeDummy); + Dummy threeTwoOneDummy = new Dummy(54321); + dummies.Add(threeTwoOneDummy); + + Assert.AreEqual(2, dummies.Count); + + dummies.Clear(); + + Assert.AreEqual(0, dummies.Count); + } + + /// Tests whether the Contains() method works + [Test] + public void TestContains() { + WeakCollection dummies = new WeakCollection( + new List>() + ); + + Dummy oneTwoThreeDummy = new Dummy(12345); + dummies.Add(oneTwoThreeDummy); + Dummy threeTwoOneDummy = new Dummy(54321); + + Assert.IsTrue(dummies.Contains(oneTwoThreeDummy)); + Assert.IsFalse(dummies.Contains(threeTwoOneDummy)); + } + + /// Tests whether the non-typesafe Contains() method works + [Test] + public void TestContainsWithObject() { + WeakCollection dummies = new WeakCollection( + new List>() + ); + + Dummy oneTwoThreeDummy = new Dummy(12345); + dummies.Add(oneTwoThreeDummy); + Dummy threeTwoOneDummy = new Dummy(54321); + + Assert.IsTrue((dummies as IList).Contains((object)oneTwoThreeDummy)); + Assert.IsFalse((dummies as IList).Contains((object)threeTwoOneDummy)); + } + + /// + /// Verifies that the Enumerator of the dummy collection correctly + /// implements the Reset() method + /// + [Test] + public void TestEnumeratorReset() { + WeakCollection dummies = new WeakCollection( + new List>() + ); + Dummy oneTwoThreeDummy = new Dummy(123); + dummies.Add(oneTwoThreeDummy); + Dummy fourFiveSixDummy = new Dummy(456); + dummies.Add(fourFiveSixDummy); + + IEnumerator dummyEnumerator = dummies.GetEnumerator(); + Assert.IsTrue(dummyEnumerator.MoveNext()); + Assert.IsTrue(dummyEnumerator.MoveNext()); + Assert.IsFalse(dummyEnumerator.MoveNext()); + + dummyEnumerator.Reset(); + + Assert.IsTrue(dummyEnumerator.MoveNext()); + Assert.IsTrue(dummyEnumerator.MoveNext()); + Assert.IsFalse(dummyEnumerator.MoveNext()); + } + + /// Verifies that the IndexOf() method is working as intended + [Test] + public void TestIndexOf() { + WeakCollection dummies = new WeakCollection( + new List>() + ); + Dummy oneTwoThreeDummy = new Dummy(123); + dummies.Add(oneTwoThreeDummy); + Dummy fourFiveSixDummy = new Dummy(456); + dummies.Add(fourFiveSixDummy); + Dummy sevenEightNineDummy = new Dummy(789); + + Assert.AreEqual(0, dummies.IndexOf(oneTwoThreeDummy)); + Assert.AreEqual(1, dummies.IndexOf(fourFiveSixDummy)); + Assert.AreEqual(-1, dummies.IndexOf(sevenEightNineDummy)); + } + + /// + /// Verifies that the non-typesafe IndexOf() method is working as intended + /// + [Test] + public void TestIndexOfWithObject() { + WeakCollection dummies = new WeakCollection( + new List>() + ); + Dummy oneTwoThreeDummy = new Dummy(123); + dummies.Add(oneTwoThreeDummy); + Dummy fourFiveSixDummy = new Dummy(456); + dummies.Add(fourFiveSixDummy); + Dummy sevenEightNineDummy = new Dummy(789); + + Assert.AreEqual(0, (dummies as IList).IndexOf((object)oneTwoThreeDummy)); + Assert.AreEqual(1, (dummies as IList).IndexOf((object)fourFiveSixDummy)); + Assert.AreEqual(-1, (dummies as IList).IndexOf((object)sevenEightNineDummy)); + } + + /// + /// Verifies that an exception is thrown if an incompatible object is passed to + /// the non-typesafe variant of the IndexOf() method + /// + [Test, ExpectedException(typeof(ArgumentException))] + public void TestThrowOnIndexOfWithIncompatibleObject() { + WeakCollection dummies = new WeakCollection( + new List>() + ); + + Assert.IsNull((dummies as IList).IndexOf(new object())); + } + + /// Test whether the IndexOf() method can cope with null references + [Test] + public void TestIndexOfNull() { + WeakCollection dummies = new WeakCollection( + new List>() + ); + + Assert.AreEqual(-1, dummies.IndexOf(null)); + dummies.Add(null); + Assert.AreEqual(0, dummies.IndexOf(null)); + } + + /// + /// Verifies that the CopyTo() method of the weak collection works + /// + [Test] + public void TestCopyToArray() { + WeakCollection dummies = new WeakCollection( + new List>() + ); + Dummy oneTwoThreeDummy = new Dummy(123); + dummies.Add(oneTwoThreeDummy); + Dummy fourFiveSixDummy = new Dummy(456); + dummies.Add(fourFiveSixDummy); + + Dummy[] inputDummies = new Dummy[] { oneTwoThreeDummy, fourFiveSixDummy }; + Dummy[] outputDummies = new Dummy[dummies.Count]; + + dummies.CopyTo(outputDummies, 0); + + CollectionAssert.AreEqual(inputDummies, outputDummies); + } + + /// + /// Verifies that the CopyTo() method of the weak collection throws an exception + /// if the target array is too small to hold the collection's contents + /// + [Test, ExpectedException(typeof(ArgumentException))] + public void TestThrowOnCopyToTooSmallArray() { + WeakCollection dummies = new WeakCollection( + new List>() + ); + Dummy oneTwoThreeDummy = new Dummy(123); + dummies.Add(oneTwoThreeDummy); + Dummy fourFiveSixDummy = new Dummy(456); + dummies.Add(fourFiveSixDummy); + + Dummy[] outputStrings = new Dummy[dummies.Count - 1]; + dummies.CopyTo(outputStrings, 0); + } + + /// + /// Verifies that the CopyTo() method of the transforming read only collection + /// works if invoked via the ICollection interface + /// + [Test] + public void TestCopyToArrayViaICollection() { + WeakCollection dummies = new WeakCollection( + new List>() + ); + Dummy oneTwoThreeDummy = new Dummy(123); + dummies.Add(oneTwoThreeDummy); + Dummy fourFiveSixDummy = new Dummy(456); + dummies.Add(fourFiveSixDummy); + + Dummy[] inputDummies = new Dummy[] { oneTwoThreeDummy, fourFiveSixDummy }; + Dummy[] outputDummies = new Dummy[dummies.Count]; + + (dummies as ICollection).CopyTo(outputDummies, 0); + + CollectionAssert.AreEqual(inputDummies, outputDummies); + } + + /// + /// Verifies that the Insert() method correctly shifts items in the collection + /// + [Test] + public void TestInsert() { + WeakCollection dummies = new WeakCollection( + new List>() + ); + Dummy oneTwoThreeDummy = new Dummy(123); + dummies.Add(oneTwoThreeDummy); + + Dummy fourFiveSixDummy = new Dummy(456); + dummies.Insert(0, fourFiveSixDummy); + + Assert.AreEqual(2, dummies.Count); + Assert.AreSame(fourFiveSixDummy, dummies[0]); + Assert.AreSame(oneTwoThreeDummy, dummies[1]); + } + + /// + /// Verifies that the non-typesafe Insert() method correctly shifts items in + /// the collection + /// + [Test] + public void TestInsertObject() { + WeakCollection dummies = new WeakCollection( + new List>() + ); + Dummy oneTwoThreeDummy = new Dummy(123); + dummies.Add(oneTwoThreeDummy); + + Dummy fourFiveSixDummy = new Dummy(456); + (dummies as IList).Insert(0, (object)fourFiveSixDummy); + + Assert.AreEqual(2, dummies.Count); + Assert.AreSame(fourFiveSixDummy, dummies[0]); + Assert.AreSame(oneTwoThreeDummy, dummies[1]); + } + + /// + /// Verifies that the non-typesafe Insert() method correctly shifts items in + /// the collection + /// + [Test, ExpectedException(typeof(ArgumentException))] + public void TestThrowOnInsertIncompatibleObject() { + WeakCollection dummies = new WeakCollection( + new List>() + ); + Dummy oneTwoThreeDummy = new Dummy(123); + dummies.Add(oneTwoThreeDummy); + + Dummy fourFiveSixDummy = new Dummy(456); + (dummies as IList).Insert(0, new object()); + } + + /// + /// Checks whether the IsFixedSize property of the weak collection returns + /// the expected result for a weak collection based on a fixed array + /// + [Test] + public void TestIsFixedSizeViaIList() { + Dummy oneTwoThreeDummy = new Dummy(123); + Dummy fourFiveSixDummy = new Dummy(456); + + WeakReference[] dummyReferences = new WeakReference[] { + new WeakReference(oneTwoThreeDummy), + new WeakReference(fourFiveSixDummy) + }; + WeakCollection dummies = new WeakCollection(dummyReferences); + + Assert.IsTrue((dummies as IList).IsFixedSize); + } + + /// + /// Tests whether the IsReadOnly property of the weak collection works + /// + [Test] + public void TestIsReadOnly() { + Dummy oneTwoThreeDummy = new Dummy(123); + Dummy fourFiveSixDummy = new Dummy(456); + + List> dummyReferences = new List>(); + dummyReferences.Add(new WeakReference(oneTwoThreeDummy)); + dummyReferences.Add(new WeakReference(fourFiveSixDummy)); + + ReadOnlyList> readOnlyDummyReferences = + new ReadOnlyList>(dummyReferences); + + WeakCollection dummies = new WeakCollection(dummyReferences); + WeakCollection readOnlydummies = new WeakCollection( + readOnlyDummyReferences + ); + + Assert.IsFalse(dummies.IsReadOnly); + Assert.IsTrue(readOnlydummies.IsReadOnly); + } + + /// + /// Tests whether the IsSynchronized property of the weak collection works + /// + [Test] + public void TestIsSynchronized() { + WeakCollection dummies = new WeakCollection( + new List>() + ); + + Assert.IsFalse((dummies as IList).IsSynchronized); + } + + /// Tests the indexer of the weak collection + [Test] + public void TestIndexer() { + WeakCollection dummies = new WeakCollection( + new List>() + ); + Dummy oneTwoThreeDummy = new Dummy(123); + dummies.Add(oneTwoThreeDummy); + Dummy fourFiveSixDummy = new Dummy(456); + dummies.Add(fourFiveSixDummy); + + Assert.AreSame(oneTwoThreeDummy, dummies[0]); + Assert.AreSame(fourFiveSixDummy, dummies[1]); + + dummies[0] = fourFiveSixDummy; + + Assert.AreSame(fourFiveSixDummy, dummies[0]); + } + + /// Tests the non-typesafe indexer of the weak collection + [Test] + public void TestIndexerWithObject() { + WeakCollection dummies = new WeakCollection( + new List>() + ); + Dummy oneTwoThreeDummy = new Dummy(123); + dummies.Add(oneTwoThreeDummy); + Dummy fourFiveSixDummy = new Dummy(456); + dummies.Add(fourFiveSixDummy); + + Assert.AreSame((object)oneTwoThreeDummy, (dummies as IList)[0]); + Assert.AreSame((object)fourFiveSixDummy, (dummies as IList)[1]); + + (dummies as IList)[0] = (object)fourFiveSixDummy; + + Assert.AreSame((object)fourFiveSixDummy, (dummies as IList)[0]); + } + + /// + /// Tests whether the non-typesafe indexer of the weak collection throws + /// the correct exception if an incompatible object is assigned + /// + [Test, ExpectedException(typeof(ArgumentException))] + public void TestThrowOnIndexerWithIncompatibleObject() { + WeakCollection dummies = new WeakCollection( + new List>() + ); + Dummy oneTwoThreeDummy = new Dummy(123); + dummies.Add(oneTwoThreeDummy); + + (dummies as IList)[0] = new object(); + } + + /// Tests the Remove() method of the weak collection + [Test] + public void TestRemove() { + WeakCollection dummies = new WeakCollection( + new List>() + ); + Dummy oneTwoThreeDummy = new Dummy(123); + dummies.Add(oneTwoThreeDummy); + Dummy fourFiveSixDummy = new Dummy(456); + dummies.Add(fourFiveSixDummy); + + Assert.AreEqual(2, dummies.Count); + Assert.IsTrue(dummies.Remove(oneTwoThreeDummy)); + Assert.AreEqual(1, dummies.Count); + Assert.IsFalse(dummies.Remove(oneTwoThreeDummy)); + } + + /// Tests the non-typesafe Remove() method of the weak collection + [Test] + public void TestRemoveObject() { + WeakCollection dummies = new WeakCollection( + new List>() + ); + Dummy oneTwoThreeDummy = new Dummy(123); + dummies.Add(oneTwoThreeDummy); + Dummy fourFiveSixDummy = new Dummy(456); + dummies.Add(fourFiveSixDummy); + + Assert.AreEqual(2, dummies.Count); + (dummies as IList).Remove((object)oneTwoThreeDummy); + Assert.AreEqual(1, dummies.Count); + } + + /// + /// Tests whether a null object can be managed by and removed from the weak collection + /// + [Test] + public void TestRemoveNull() { + WeakCollection dummies = new WeakCollection( + new List>() + ); + dummies.Add(null); + + Assert.AreEqual(1, dummies.Count); + Assert.IsTrue(dummies.Remove(null)); + Assert.AreEqual(0, dummies.Count); + } + + /// + /// Tests whether the non-typesafe Remove() method of the weak collection throws + /// an exception if an object is tried to be removed that is incompatible with + /// the collection's item type + /// + [Test, ExpectedException(typeof(ArgumentException))] + public void TestThrowOnRemoveIncompatibleObject() { + WeakCollection dummies = new WeakCollection( + new List>() + ); + (dummies as IList).Remove(new object()); + } + + /// Tests the RemoveAt() method of the weak collection + [Test] + public void TestRemoveAt() { + WeakCollection dummies = new WeakCollection( + new List>() + ); + Dummy oneTwoThreeDummy = new Dummy(123); + dummies.Add(oneTwoThreeDummy); + Dummy fourFiveSixDummy = new Dummy(456); + dummies.Add(fourFiveSixDummy); + + Assert.AreSame(oneTwoThreeDummy, dummies[0]); + dummies.RemoveAt(0); + Assert.AreSame(fourFiveSixDummy, dummies[0]); + } + + /// + /// Verifies that the IsSynchronized property and the SyncRoot property are working + /// + [Test] + public void TestSynchronization() { + WeakCollection dummies = new WeakCollection( + new List>() + ); + + if(!(dummies as ICollection).IsSynchronized) { + lock((dummies as ICollection).SyncRoot) { + Assert.AreEqual(0, dummies.Count); + } + } + } + + private class ListWithoutICollection : IList> { + public int IndexOf(WeakReference item) { throw new NotImplementedException(); } + public void Insert(int index, WeakReference item) { + throw new NotImplementedException(); + } + public void RemoveAt(int index) { throw new NotImplementedException(); } + public WeakReference this[int index] { + get { throw new NotImplementedException(); } + set { throw new NotImplementedException(); } + } + public void Add(WeakReference item) { throw new NotImplementedException(); } + public void Clear() { throw new NotImplementedException(); } + public bool Contains(WeakReference item) { throw new NotImplementedException(); } + public void CopyTo(WeakReference[] array, int arrayIndex) { + throw new NotImplementedException(); + } + public int Count { get { return 12345; } } + public bool IsReadOnly { get { throw new NotImplementedException(); } } + public bool Remove(WeakReference item) { throw new NotImplementedException(); } + public IEnumerator> GetEnumerator() { + throw new NotImplementedException(); + } + IEnumerator IEnumerable.GetEnumerator() { throw new NotImplementedException(); } + } + + /// + /// Verifies that the IsSynchronized property and the SyncRoot property are working + /// on transforming read only collections based on IList<>s that do not + /// implement the ICollection interface + /// + [Test] + public void TestSynchronizationOfIListWithoutICollection() { + WeakCollection dummies = new WeakCollection( + new ListWithoutICollection() + ); + + if(!(dummies as ICollection).IsSynchronized) { + lock((dummies as ICollection).SyncRoot) { + int count = dummies.Count; + Assert.AreEqual(12345, count); // ;-) + } + } + } + + /// Tests the RemoveDeadItems() method + [Test] + public void TestRemoveDeadItems() { + List> dummyReferences = new List>(); + + Dummy oneTwoThreeDummy = new Dummy(123); + dummyReferences.Add(new WeakReference(oneTwoThreeDummy)); + + dummyReferences.Add(new WeakReference(null)); + + Dummy fourFiveSixDummy = new Dummy(456); + dummyReferences.Add(new WeakReference(fourFiveSixDummy)); + + WeakCollection dummies = new WeakCollection(dummyReferences); + + Assert.AreEqual(3, dummies.Count); + + dummies.RemoveDeadItems(); + + Assert.AreEqual(2, dummies.Count); + Assert.AreSame(oneTwoThreeDummy, dummies[0]); + Assert.AreSame(fourFiveSixDummy, dummies[1]); + } + + } + +} // namespace Nuclex.Support.Collections + +#endif // UNITTEST diff --git a/Source/Collections/WeakCollection.cs b/Source/Collections/WeakCollection.cs new file mode 100644 index 0000000..88ad0c7 --- /dev/null +++ b/Source/Collections/WeakCollection.cs @@ -0,0 +1,335 @@ +#region CPL License +/* +Nuclex Framework +Copyright (C) 2002-2009 Nuclex Development Labs + +This library is free software; you can redistribute it and/or +modify it under the terms of the IBM Common Public License as +published by the IBM Corporation; either version 1.0 of the +License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +IBM Common Public License for more details. + +You should have received a copy of the IBM Common Public +License along with this library +*/ +#endregion + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Collections.ObjectModel; + +namespace Nuclex.Support.Collections { + + /// Collection of weakly referenced objects + /// + /// This collection tries to expose the interface of a normal collection, but stores + /// objects as weak references. When an object is accessed, it can return null. + /// when the collection detects that one of its items was garbage collected, it + /// will silently remove that item. + /// + public partial class WeakCollection : IList, IList + where ItemType : class { + + #region class UnpackingEnumerator + + /// + /// An enumerator that unpacks the items returned by an enumerator of the + /// weak reference collection into the actual item type on-the-fly. + /// + private class UnpackingEnumerator : IEnumerator { + + /// Initializes a new unpacking enumerator + /// + /// Enumerator of the weak reference collection + /// + public UnpackingEnumerator( + IEnumerator> containedTypeEnumerator + ) { + this.containedTypeEnumerator = containedTypeEnumerator; + } + + /// Immediately releases all resources used by the instance + public void Dispose() { + this.containedTypeEnumerator.Dispose(); + } + + /// + /// The element in the collection at the current position of the enumerator. + /// + public ItemType Current { + get { return this.containedTypeEnumerator.Current.Target; } + } + + /// Gets the current element in the collection. + /// The current element in the collection. + /// + /// The enumerator is positioned before the first element of the collection + /// or after the last element. + /// + public bool MoveNext() { + return this.containedTypeEnumerator.MoveNext(); + } + + /// + /// Sets the enumerator to its initial position, which is before the first element + /// in the collection. + /// + /// + /// The collection was modified after the enumerator was created. + /// + public void Reset() { + this.containedTypeEnumerator.Reset(); + } + + /// The current element in the collection. + /// + /// The enumerator is positioned before the first element of the collection + /// or after the last element. + /// + object IEnumerator.Current { + get { return Current; } + } + + /// An enumerator from the wrapped collection + private IEnumerator> containedTypeEnumerator; + + } + + #endregion // class UnpackingEnumerator + + /// Initializes a new weak reference collection + /// + /// Internal list of weak references that are unpacking when accessed through + /// the WeakCollection's interface. + /// + public WeakCollection(IList> items) { + this.items = items; + } + + /// + /// Determines whether an element is in the WeakCollection + /// + /// + /// The object to locate in the WeakCollection. The value can be null. + /// + /// + /// True if value is found in the WeakCollection; otherwise, false. + /// + /// + /// The default implementation of this method is very unoptimized and will + /// enumerate all the items in the collection, transforming one after another + /// to check whether the transformed item matches the item the user was + /// looking for. It is recommended to provide a custom implementation of + /// this method, if possible. + /// + public virtual bool Contains(ItemType item) { + return (IndexOf(item) != -1); + } + + /// + /// Copies the entire WeakCollection to a compatible one-dimensional + /// System.Array, starting at the specified index of the target array. + /// + /// + /// The one-dimensional System.Array that is the destination of the elements copied + /// from the WeakCollection. The System.Array must have zero-based indexing. + /// + /// + /// The zero-based index in array at which copying begins. + /// + /// + /// Index is equal to or greater than the length of array or the number of elements + /// in the source WeakCollection is greater than the available space from index to + /// the end of the destination array. + /// + /// + /// Index is less than zero. + /// + /// + /// Array is null. + /// + public void CopyTo(ItemType[] array, int index) { + if(this.items.Count > (array.Length - index)) { + throw new ArgumentException( + "Array too small to fit the collection items starting at the specified index" + ); + } + + for(int itemIndex = 0; itemIndex < this.items.Count; ++itemIndex) { + array[itemIndex + index] = this.items[itemIndex].Target; + } + } + + /// Removes all items from the WeakCollection + public void Clear() { + this.items.Clear(); + } + + /// + /// Returns an enumerator that iterates through the WeakCollection. + /// + /// An enumerator or the WeakCollection. + public IEnumerator GetEnumerator() { + return new UnpackingEnumerator(this.items.GetEnumerator()); + } + + /// + /// Searches for the specified object and returns the zero-based index of the + /// first occurrence within the entire WeakCollection. + /// + /// + /// The object to locate in the WeakCollection. The value can + /// be null for reference types. + /// + /// + /// The zero-based index of the first occurrence of item within the entire + /// WeakCollection, if found; otherwise, -1. + /// + /// + /// The default implementation of this method is very unoptimized and will + /// enumerate all the items in the collection, transforming one after another + /// to check whether the transformed item matches the item the user was + /// looking for. It is recommended to provide a custom implementation of + /// this method, if possible. + /// + public int IndexOf(ItemType item) { + EqualityComparer comparer = EqualityComparer.Default; + + for(int index = 0; index < this.items.Count; ++index) { + ItemType itemAtIndex = this.items[index].Target; + if((itemAtIndex == null) || (item == null)) { + if(ReferenceEquals(item, itemAtIndex)) { + return index; + } + } else { + if(comparer.Equals(itemAtIndex, item)) { + return index; + } + } + } + + return -1; + } + + /// + /// The number of elements contained in the WeakCollection instance + /// + public int Count { + get { return this.items.Count; } + } + + /// Gets the element at the specified index. + /// The zero-based index of the element to get. + /// The element at the specified index. + /// + /// Index is less than zero or index is equal to or greater than + /// WeakCollection.Count. + /// + public ItemType this[int index] { + get { return this.items[index].Target; } + set { this.items[index] = new WeakReference(value); } + } + + /// + /// Removes the first occurrence of a specific object from the WeakCollection. + /// + /// The object to remove from the WeakCollection + /// + /// True if item was successfully removed from the WeakCollection; otherwise, false. + /// + public bool Remove(ItemType item) { + EqualityComparer comparer = EqualityComparer.Default; + + lock(this) { + for(int index = 0; index < this.items.Count; ++index) { + ItemType itemAtIndex = this.items[index].Target; + if((itemAtIndex == null) || (item == null)) { + if(ReferenceEquals(item, itemAtIndex)) { + this.items.RemoveAt(index); + return true; + } + } else { + if(comparer.Equals(item, itemAtIndex)) { + this.items.RemoveAt(index); + return true; + } + } + } + } // lock(this) + + return false; + } + + /// Adds an item to the WeakCollection. + /// The object to add to the WeakCollection + public void Add(ItemType item) { + this.items.Add(new WeakReference(item)); + } + + /// Inserts an item to the WeakCollection at the specified index. + /// + /// The zero-based index at which item should be inserted. + /// + /// The object to insert into the WeakCollection + /// + /// index is not a valid index in the WeakCollection. + /// + public void Insert(int index, ItemType item) { + this.items.Insert(index, new WeakReference(item)); + } + + /// + /// Removes the WeakCollection item at the specified index. + /// + /// The zero-based index of the item to remove. + /// + /// Index is not a valid index in the WeakCollection. + /// + public void RemoveAt(int index) { + this.items.RemoveAt(index); + } + + /// Whether the List is write-protected + public bool IsReadOnly { + get { return this.items.IsReadOnly; } + } + + /// + /// Removes the items that have been garbage collected from the collection + /// + public void RemoveDeadItems() { + lock(this) { + int eliminatedItemCount = 0; + + // Eliminate all items that have been garbage collected by shifting + for(int index = 0; index + eliminatedItemCount < this.items.Count; ++index) { + if(!this.items[index].IsAlive) { + ++eliminatedItemCount; + this.items[index] = this.items[index + eliminatedItemCount]; + } + } + + // If any garbage collected items were found, resize the collection so + // the space that became empty in the previous shifting process will be freed + if(eliminatedItemCount > 0) { + int endIndex = this.items.Count - eliminatedItemCount; + for(int index = this.items.Count - 1; index >= endIndex; --index) { + this.items.RemoveAt(index); + } + } + } + } + + /// Weak references to the items contained in the collection + private IList> items; + /// Synchronization root for threaded accesses to this collection + private object syncRoot; + + } + +} // namespace Nuclex.Support.Collections diff --git a/Source/Tracking/IdleStateEventArgs.Test.cs b/Source/Tracking/IdleStateEventArgs.Test.cs index 99beb22..d6ecaec 100644 --- a/Source/Tracking/IdleStateEventArgs.Test.cs +++ b/Source/Tracking/IdleStateEventArgs.Test.cs @@ -18,11 +18,11 @@ License along with this library */ #endregion +#if UNITTEST + using System; using System.IO; -#if UNITTEST - using NUnit.Framework; namespace Nuclex.Support.Tracking { diff --git a/Source/Tracking/ProgressReportEventArgs.Test.cs b/Source/Tracking/ProgressReportEventArgs.Test.cs index 9d4acaf..803ee68 100644 --- a/Source/Tracking/ProgressReportEventArgs.Test.cs +++ b/Source/Tracking/ProgressReportEventArgs.Test.cs @@ -18,11 +18,11 @@ License along with this library */ #endregion +#if UNITTEST + using System; using System.IO; -#if UNITTEST - using NUnit.Framework; namespace Nuclex.Support.Tracking { diff --git a/Source/Tracking/Request.Test.cs b/Source/Tracking/Request.Test.cs index 4cd897f..6afc579 100644 --- a/Source/Tracking/Request.Test.cs +++ b/Source/Tracking/Request.Test.cs @@ -18,14 +18,14 @@ License along with this library */ #endregion +#if UNITTEST + using System; using System.Collections.Generic; using System.IO; using Nuclex.Support.Scheduling; -#if UNITTEST - using NUnit.Framework; using NMock2; @@ -124,7 +124,6 @@ namespace Nuclex.Support.Tracking { failedDummy.Join(); } - } } // namespace Nuclex.Support.Tracking diff --git a/Source/Tracking/StatusReportEventArgs.Test.cs b/Source/Tracking/StatusReportEventArgs.Test.cs index e075e5a..6712374 100644 --- a/Source/Tracking/StatusReportEventArgs.Test.cs +++ b/Source/Tracking/StatusReportEventArgs.Test.cs @@ -18,11 +18,11 @@ License along with this library */ #endregion +#if UNITTEST + using System; using System.IO; -#if UNITTEST - using NUnit.Framework; namespace Nuclex.Support.Tracking { diff --git a/Source/Tracking/WeightedTransaction.Test.cs b/Source/Tracking/WeightedTransaction.Test.cs index 8511456..6018a71 100644 --- a/Source/Tracking/WeightedTransaction.Test.cs +++ b/Source/Tracking/WeightedTransaction.Test.cs @@ -18,11 +18,11 @@ License along with this library */ #endregion +#if UNITTEST + using System; using System.IO; -#if UNITTEST - using NUnit.Framework; namespace Nuclex.Support.Tracking {