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 {