diff --git a/Nuclex.Support.csproj b/Nuclex.Support.csproj
index c97e4d2..dbaaf46 100644
--- a/Nuclex.Support.csproj
+++ b/Nuclex.Support.csproj
@@ -95,11 +95,15 @@
PathHelper.cs
-
+
+
+
+ PluginHelper.cs
+
@@ -112,11 +116,17 @@
+
+ Shared.cs
+
StringHelper.cs
+
+ StringSegment.cs
+
@@ -136,6 +146,9 @@
+
+ WeakReference.cs
+
diff --git a/Source/Collections/ReadOnlyDictionary.cs b/Source/Collections/ReadOnlyDictionary.cs
index c4bf9d6..fbbfc76 100644
--- a/Source/Collections/ReadOnlyDictionary.cs
+++ b/Source/Collections/ReadOnlyDictionary.cs
@@ -98,8 +98,8 @@ namespace Nuclex.Support.Collections {
}
}
- /// Collection aller Werte im Dictionary
- public ICollection Values { // TODO: RO-Wrappen!
+ /// Collection of all values contained in the Dictionary
+ public ICollection Values {
get {
if(this.readonlyValueCollection == null) {
this.readonlyValueCollection = new ReadOnlyCollection(
diff --git a/Source/PathHelper.Test.cs b/Source/PathHelper.Test.cs
index 4d4e335..9aa193c 100644
--- a/Source/PathHelper.Test.cs
+++ b/Source/PathHelper.Test.cs
@@ -77,6 +77,29 @@ namespace Nuclex.Support {
);
}
+ ///
+ /// Tests whether the relative path creator correctly builds the relative path to
+ /// the parent folder of the base path for windows paths with more than one level.
+ ///
+ [Test]
+ public void TestRelativeWindowsPathToParentFolderTwoLevels() {
+ Assert.That(
+ PathHelper.MakeRelative(
+ platformify("C:/Folder1/Folder2/Folder3"),
+ platformify("C:/Folder1")
+ ),
+ Is.EqualTo(platformify("../.."))
+ );
+ Assert.That(
+ PathHelper.MakeRelative(
+ platformify("C:/Folder1/Folder2/Folder3/"),
+ platformify("C:/Folder1/")
+ ),
+ Is.EqualTo(platformify("../../"))
+ );
+ }
+
+
///
/// Tests whether the relative path creator correctly builds the relative
/// path to the parent folder of the base path for unix paths.
@@ -99,6 +122,28 @@ namespace Nuclex.Support {
);
}
+ ///
+ /// Tests whether the relative path creator correctly builds the relative path to
+ /// the parent folder of the base path for unix paths with more than one level.
+ ///
+ [Test]
+ public void TestRelativeUnixPathToParentFolderTwoLevels() {
+ Assert.That(
+ PathHelper.MakeRelative(
+ platformify("/Folder1/Folder2/Folder3"),
+ platformify("/Folder1")
+ ),
+ Is.EqualTo(platformify("../.."))
+ );
+ Assert.That(
+ PathHelper.MakeRelative(
+ platformify("/Folder1/Folder2/Folder3/"),
+ platformify("/Folder1/")
+ ),
+ Is.EqualTo(platformify("../../"))
+ );
+ }
+
///
/// Tests whether the relative path creator correctly builds the relative
/// path to a nested folder in the base path for windows paths.
diff --git a/Source/Plugins/AbstractFactory.cs b/Source/Plugins/AbstractFactory.cs
new file mode 100644
index 0000000..b8aa6e7
--- /dev/null
+++ b/Source/Plugins/AbstractFactory.cs
@@ -0,0 +1,39 @@
+#region CPL License
+/*
+Nuclex Framework
+Copyright (C) 2002-2008 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.Generic;
+
+namespace Nuclex.Support.Plugins {
+
+ /// Abstract factory for a concrete type
+ /// Interface or base class of the product of the factory
+ public interface IAbstractFactory {
+
+ /// The concrete type as implemented by the factory instance
+ Type ConcreteType { get; }
+
+ /// Creates a new instance of the type to which the factory is specialized
+ /// The newly created instance
+ ProductType CreateInstance();
+
+ }
+
+} // namespace Nuclex.Support.Plugins
diff --git a/Source/Plugins/Employer.cs b/Source/Plugins/Employer.cs
index 43988b6..34b0d84 100644
--- a/Source/Plugins/Employer.cs
+++ b/Source/Plugins/Employer.cs
@@ -34,7 +34,7 @@ namespace Nuclex.Support.Plugins {
/// Determines whether the type suites the employer's requirements
/// Type which will be assessed
- /// True if the type can be employed
+ /// True if the type can be employed, otherwise false
public virtual bool CanEmploy(Type type) {
return PluginHelper.HasDefaultConstructor(type);
}
diff --git a/Source/Plugins/FactoryEmployer.cs b/Source/Plugins/FactoryEmployer.cs
index d4425de..3707b93 100644
--- a/Source/Plugins/FactoryEmployer.cs
+++ b/Source/Plugins/FactoryEmployer.cs
@@ -23,21 +23,10 @@ using System.Collections.Generic;
namespace Nuclex.Support.Plugins {
- /// Abstract factory
- /// Interface or base class of the product of the factory
- public interface IFactory {
-
- /// The concrete type as implemented by the factory instance
- Type ConcreteType { get; }
-
- /// Creates a new instance of the type to which the factory is specialized
- /// The newly created instance
- T CreateInstance();
-
- }
-
/// Employer to create factories of suiting types found in plugins
- /// Interface or base class that the types need to implement
+ ///
+ /// Interface or base class that the types need to implement
+ ///
///
///
/// This employer will not directly instanciate any compatible types found in
@@ -52,16 +41,16 @@ namespace Nuclex.Support.Plugins {
/// a human-readable name, capabilities or an icon.
///
///
- public class FactoryEmployer : Employer {
+ public class FactoryEmployer : Employer {
- #region class Factory
+ #region class ConcreteFactory
/// Concrete factory for the types in a plugin assembly
- private class Factory : IFactory {
+ private class ConcreteFactory : IAbstractFactory {
/// Initializes a factory and configures it for the specified product
/// Type of which the factory creates instances
- public Factory(Type type) {
+ public ConcreteFactory(Type type) {
this.concreteType = type;
}
@@ -72,8 +61,8 @@ namespace Nuclex.Support.Plugins {
/// Create a new instance of the type that the factory is configured to
/// The newly created instance
- public T CreateInstance() {
- return (T)Activator.CreateInstance(this.concreteType);
+ public ProductType CreateInstance() {
+ return (ProductType)Activator.CreateInstance(this.concreteType);
}
/// Concrete product which the factory instance creates
@@ -85,11 +74,11 @@ namespace Nuclex.Support.Plugins {
/// Initializes a new FactoryEmployer
public FactoryEmployer() {
- this.employedFactories = new List>();
+ this.employedFactories = new List>();
}
/// List of all factories that the instance employer has created
- public List> Factories {
+ public List> Factories {
get { return this.employedFactories; }
}
@@ -99,17 +88,17 @@ namespace Nuclex.Support.Plugins {
public override bool CanEmploy(Type type) {
return
PluginHelper.HasDefaultConstructor(type) &&
- typeof(T).IsAssignableFrom(type);
+ typeof(ProductType).IsAssignableFrom(type);
}
/// Employs the specified plugin type
/// Type to be employed
public override void Employ(Type type) {
- this.employedFactories.Add(new Factory(type));
+ this.employedFactories.Add(new ConcreteFactory(type));
}
/// All factories that the instance employer has created
- private List> employedFactories;
+ private List> employedFactories;
}
diff --git a/Source/Plugins/Attributes.cs b/Source/Plugins/NoPluginAttribute.cs
similarity index 100%
rename from Source/Plugins/Attributes.cs
rename to Source/Plugins/NoPluginAttribute.cs
diff --git a/Source/Plugins/PluginHelper.Test.cs b/Source/Plugins/PluginHelper.Test.cs
new file mode 100644
index 0000000..45c6a74
--- /dev/null
+++ b/Source/Plugins/PluginHelper.Test.cs
@@ -0,0 +1,84 @@
+#region CPL License
+/*
+Nuclex Framework
+Copyright (C) 2002-2008 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.IO;
+
+#if UNITTEST
+
+using NUnit.Framework;
+using NUnit.Framework.SyntaxHelpers;
+
+namespace Nuclex.Support.Plugins {
+
+ /// Unit Test for the plugin helper class
+ [TestFixture]
+ public class PluginHelperTest {
+
+ #region class NoDefaultConstructor
+
+ /// Test class that doesn't have a default constructor
+ private class NoDefaultConstructor {
+ /// Initializes a new instance of the test class
+ /// Dummy argument so this is no default constructor
+ public NoDefaultConstructor(int dummy) { }
+ }
+
+ #endregion // class NoDefaultConstructor
+
+ #region class NonPublicDefaultConstructor
+
+ /// Test class that has a non-public default constructor
+ private class NonPublicDefaultConstructor {
+ /// Initializes a new instance of the test class
+ protected NonPublicDefaultConstructor() { }
+ }
+
+ #endregion // class NonPublicDefaultConstructor
+
+ #region class PublicDefaultConstructor
+
+ /// Test class that has a public default constructor
+ private class PublicDefaultConstructor {
+ /// Initializes a new instance of the test class
+ public PublicDefaultConstructor() { }
+ }
+
+ #endregion // class PublicDefaultConstructor
+
+ /// Tests whether the default constructor detection works as expected
+ [Test]
+ public void TestDefaultConstructorDetection() {
+ Assert.IsFalse(
+ PluginHelper.HasDefaultConstructor(typeof(NoDefaultConstructor))
+ );
+ Assert.IsFalse(
+ PluginHelper.HasDefaultConstructor(typeof(NonPublicDefaultConstructor))
+ );
+ Assert.IsTrue(
+ PluginHelper.HasDefaultConstructor(typeof(PublicDefaultConstructor))
+ );
+ }
+
+ }
+
+} // namespace Nuclex.Support.Plugins
+
+#endif // UNITTEST
diff --git a/Source/Plugins/PluginHelper.cs b/Source/Plugins/PluginHelper.cs
index d13f538..0a799cf 100644
--- a/Source/Plugins/PluginHelper.cs
+++ b/Source/Plugins/PluginHelper.cs
@@ -19,20 +19,21 @@ License along with this library
#endregion
using System;
+using System.Reflection;
namespace Nuclex.Support.Plugins {
/// Supporting functions for the plugin classes
- internal static class PluginHelper {
+ public static class PluginHelper {
/// Determines whether the given type has a default constructor
/// Type which is to be checked
/// True if the type has a default constructor
public static bool HasDefaultConstructor(Type type) {
- System.Reflection.ConstructorInfo[] constructors = type.GetConstructors();
+ ConstructorInfo[] constructors = type.GetConstructors();
- foreach(System.Reflection.ConstructorInfo constructor in constructors)
- if(constructor.IsPublic && (constructor.GetParameters().Length != 0))
+ foreach(ConstructorInfo constructor in constructors)
+ if(constructor.IsPublic && (constructor.GetParameters().Length == 0))
return true;
return false;
diff --git a/Source/Shared.Test.cs b/Source/Shared.Test.cs
new file mode 100644
index 0000000..e220d24
--- /dev/null
+++ b/Source/Shared.Test.cs
@@ -0,0 +1,70 @@
+#region CPL License
+/*
+Nuclex Framework
+Copyright (C) 2002-2008 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.IO;
+
+#if UNITTEST
+
+using NUnit.Framework;
+using NUnit.Framework.SyntaxHelpers;
+
+namespace Nuclex.Support {
+
+ /// Unit Test for the shared instance provider class
+ [TestFixture]
+ public class SharedTest {
+
+ #region class Dummy
+
+ /// Dummy class for testing the shared instance provider
+ private class Dummy {
+ /// Initializes a new dummy
+ public Dummy() {}
+ }
+
+ #endregion // class Dummy
+
+ ///
+ /// Verifies that the shared instance provider returns the same instance of a class
+ /// when asked for the same class twice.
+ ///
+ [Test]
+ public void TestSameInstance() {
+ Dummy dummyInstance = Shared.Instance;
+ Dummy otherDummyInstance = Shared.Instance;
+
+ // Make sure they're the same instance. We could have put an instance counter in
+ // the dummy class, but this might or might not work well across multiple tests
+ // because the order in which tests are executed is undefined and Shared<> changes
+ // its global state when the first test is run by remembering the instance.
+ //
+ // Maybe this really is a defect in Shared<> and the class should be equipped with
+ // a method such as Discard() or Dispose() to get rid of the instance?
+ Assert.IsTrue(
+ ReferenceEquals(dummyInstance, otherDummyInstance)
+ );
+ }
+
+ }
+
+} // namespace Nuclex.Support
+
+#endif // UNITTEST
diff --git a/Source/Shared.cs b/Source/Shared.cs
index ae94b18..9fab20e 100644
--- a/Source/Shared.cs
+++ b/Source/Shared.cs
@@ -19,6 +19,7 @@ License along with this library
#endregion
using System;
+using System.Diagnostics;
namespace Nuclex.Support {
@@ -30,7 +31,7 @@ namespace Nuclex.Support {
/// Returns the global instance of the class
public static SharedType Instance {
- [System.Diagnostics.DebuggerStepThrough]
+ [DebuggerStepThrough]
get {
return instance;
}
diff --git a/Source/StringHelper.Test.cs b/Source/StringHelper.Test.cs
index ce9a8c4..2d81f65 100644
--- a/Source/StringHelper.Test.cs
+++ b/Source/StringHelper.Test.cs
@@ -47,6 +47,21 @@ namespace Nuclex.Support {
);
}
+ ///
+ /// Verifies that the IndexNotOfAny() method works identical to the framework's
+ /// implementation of the IndexOfAny() method, only inverted, using a start index.
+ ///
+ [Test]
+ public void TestIndexNotOfAnyWithStartIndex() {
+ string positive = "OOOOOxxxxxOOOOO";
+ string negative = "xxxxxOOOOOxxxxx";
+
+ Assert.AreEqual(
+ positive.IndexOfAny(new char[] { 'O' }, 5),
+ StringHelper.IndexNotOfAny(negative, new char[] { 'O' }, 5)
+ );
+ }
+
///
/// Verifies that the LastIndexNotOfAny() method works identical to the framework's
/// implementation of the LastIndexOfAny() method, only inverted.
@@ -63,14 +78,17 @@ namespace Nuclex.Support {
}
///
- /// Verifies that the IndexNotOfAny() method works with multiple characters
+ /// Verifies that the LastIndexNotOfAny() method works identical to the framework's
+ /// implementation of the LastIndexOfAny() method, only inverted, using a start index.
///
[Test]
- public void TestMultipleCharIndexNotOfAny() {
- string haystack = "0123456789";
+ public void TestLastIndexNotOfAnyWithStartIndex() {
+ string positive = "OOOOOxxxxxOOOOO";
+ string negative = "xxxxxOOOOOxxxxx";
Assert.AreEqual(
- 5, StringHelper.IndexNotOfAny(haystack, new char[] { '4', '3', '2', '1', '0' })
+ positive.LastIndexOfAny(new char[] { 'x' }, 5),
+ StringHelper.LastIndexNotOfAny(negative, new char[] { 'x' }, 5)
);
}
@@ -78,14 +96,121 @@ namespace Nuclex.Support {
/// Verifies that the IndexNotOfAny() method works with multiple characters
///
[Test]
- public void TestMultipleCharLastIndexNotOfAny() {
- string haystack = "0123456789";
+ public void TestMultipleCharIndexNotOfAny() {
+ string positive = "abcde12345";
+ string negative = "12345abcde";
Assert.AreEqual(
- 4, StringHelper.LastIndexNotOfAny(haystack, new char[] { '9', '8', '7', '6', '5' })
+ positive.IndexOfAny(new char[] { '1', '2', '3', '4', '5' }),
+ StringHelper.IndexNotOfAny(negative, new char[] { '1', '2', '3', '4', '5' })
);
}
+ ///
+ /// Verifies that the IndexNotOfAny() method works with multiple characters,
+ /// using a start index
+ ///
+ [Test]
+ public void TestMultipleCharIndexNotOfAnyWithStartIndex() {
+ string positive = "12345abcde12345";
+ string negative = "abcde12345abcde";
+
+ Assert.AreEqual(
+ positive.IndexOfAny(new char[] { '1', '2', '3', '4', '5' }, 5),
+ StringHelper.IndexNotOfAny(negative, new char[] { '1', '2', '3', '4', '5' }, 5)
+ );
+ }
+
+ ///
+ /// Verifies that the LastIndexNotOfAny() method works with multiple characters
+ ///
+ [Test]
+ public void TestMultipleCharLastIndexNotOfAny() {
+ string positive = "abcde12345";
+ string negative = "12345abcde";
+
+ Assert.AreEqual(
+ positive.LastIndexOfAny(new char[] { 'a', 'b', 'c', 'd', 'e' }),
+ StringHelper.LastIndexNotOfAny(negative, new char[] { 'a', 'b', 'c', 'd', 'e' })
+ );
+ }
+
+ ///
+ /// Verifies that the LastIndexNotOfAny() method works with multiple characters,
+ /// using a start index
+ ///
+ [Test]
+ public void TestMultipleCharLastIndexNotOfAnyWithStartIndex() {
+ string positive = "12345abcde12345";
+ string negative = "abcde12345abcde";
+
+ Assert.AreEqual(
+ positive.LastIndexOfAny(new char[] { 'a', 'b', 'c', 'd', 'e' }, 5),
+ StringHelper.LastIndexNotOfAny(negative, new char[] { 'a', 'b', 'c', 'd', 'e' }, 5)
+ );
+ }
+
+ ///
+ /// Verifies that the IndexNotOfAny() method fails when only matches are found
+ ///
+ [Test]
+ public void TestIndexNotOfAnyMatchesOnly() {
+ string positive = "1234512345";
+ string negative = "abcdeabcde";
+
+ Assert.AreEqual(
+ positive.IndexOfAny(new char[] { 'a', 'b', 'c', 'd', 'e' }),
+ StringHelper.IndexNotOfAny(negative, new char[] { 'a', 'b', 'c', 'd', 'e' })
+ );
+ }
+
+ ///
+ /// Verifies that the IndexNotOfAny() method fails when only matches are found,
+ /// using a start index
+ ///
+ [Test]
+ public void TestIndexNotOfAnyMatchesOnlyWithStartIndex() {
+ string positive = "abcde1234512345";
+ string negative = "12345abcdeabcde";
+
+ Assert.AreEqual(
+ positive.IndexOfAny(new char[] { 'a', 'b', 'c', 'd', 'e' }, 5),
+ StringHelper.IndexNotOfAny(negative, new char[] { 'a', 'b', 'c', 'd', 'e' }, 5)
+ );
+ }
+
+ ///
+ /// Verifies that the LastIndexNotOfAny() method fails when only matches are found
+ ///
+ [Test]
+ public void TestLastIndexNotOfAnyMatchesOnly() {
+ string positive = "1234512345";
+ string negative = "abcdeabcde";
+
+ Assert.AreEqual(
+ positive.LastIndexOfAny(new char[] { 'a', 'b', 'c', 'd', 'e' }),
+ StringHelper.LastIndexNotOfAny(negative, new char[] { 'a', 'b', 'c', 'd', 'e' })
+ );
+ }
+
+ ///
+ /// Verifies that the LastIndexNotOfAny() method fails when only matches are found,
+ /// using a start index
+ ///
+ [Test]
+ public void TestLastIndexNotOfAnyMatchesOnlyWithStartIndex() {
+ string positive = "abcde1234512345";
+ string negative = "12345abcdeabcde";
+
+ Assert.AreEqual(
+ positive.LastIndexOfAny(new char[] { 'a', 'b', 'c', 'd', 'e' }, 5),
+ StringHelper.LastIndexNotOfAny(negative, new char[] { 'a', 'b', 'c', 'd', 'e' }, 5)
+ );
+ }
+
+ // TODO: Also need unit tests for the 'length' argument
+ // to guarantee the methods stop searching at the exact character
+
}
} // namespace Nuclex.Support
diff --git a/Source/StringSegment.Test.cs b/Source/StringSegment.Test.cs
new file mode 100644
index 0000000..57964d2
--- /dev/null
+++ b/Source/StringSegment.Test.cs
@@ -0,0 +1,195 @@
+#region CPL License
+/*
+Nuclex Framework
+Copyright (C) 2002-2008 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.IO;
+
+#if UNITTEST
+
+using NUnit.Framework;
+using NUnit.Framework.SyntaxHelpers;
+
+namespace Nuclex.Support {
+
+ /// Unit Test for the strign segment class
+ [TestFixture]
+ public class StringSegmentTest {
+
+ ///
+ /// Tests whether the default constructor of the StringSegment class throws the
+ /// right exception when being passed 'null' instead of a string
+ ///
+ [Test, ExpectedException(typeof(ArgumentNullException))]
+ public void TestNullStringInSimpleConstructor() {
+ new StringSegment(null);
+ }
+
+ ///
+ /// Tests whether the simple constructor of the StringSegment class accepts
+ /// an empty string
+ ///
+ [Test]
+ public void TestEmptyStringInSimpleConstructor() {
+ new StringSegment(string.Empty);
+ }
+
+ ///
+ /// Tests whether the full constructor of the StringSegment class throws the
+ /// right exception when being passed 'null' instead of a string
+ ///
+ [Test, ExpectedException(typeof(ArgumentNullException))]
+ public void TestNullStringInFullConstructor() {
+ new StringSegment(null, 0, 0);
+ }
+
+ ///
+ /// Tests whether the full constructor of the StringSegment class accepts
+ /// an empty string
+ ///
+ [Test]
+ public void TestEmptyStringInFullConstructor() {
+ new StringSegment(string.Empty, 0, 0);
+ }
+
+ ///
+ /// Tests whether the full constructor of the StringSegment class throws the
+ /// right exception when being passed an invalid start offset
+ ///
+ [Test, ExpectedException(typeof(ArgumentOutOfRangeException))]
+ public void TestInvalidOffsetInConstructor() {
+ new StringSegment(string.Empty, -1, 0);
+ }
+
+ ///
+ /// Tests whether the full constructor of the StringSegment class throws the
+ /// right exception when being passed an invalid string length
+ ///
+ [Test, ExpectedException(typeof(ArgumentOutOfRangeException))]
+ public void TestInvalidLengthInConstructor() {
+ new StringSegment(string.Empty, 0, -1);
+ }
+
+ ///
+ /// Tests whether the full constructor of the StringSegment class throws the
+ /// right exception when being passed a string length that's too large
+ ///
+ [Test, ExpectedException(typeof(ArgumentException))]
+ public void TestExcessiveLengthInConstructor() {
+ new StringSegment("hello", 3, 3);
+ }
+
+ /// Tests whether the 'Text' property works as expected
+ [Test]
+ public void TestTextProperty() {
+ StringSegment testSegment = new StringSegment("hello", 1, 3);
+ Assert.AreEqual("hello", testSegment.Text);
+ }
+
+ /// Tests whether the 'Offset' property works as expected
+ [Test]
+ public void TestOffsetProperty() {
+ StringSegment testSegment = new StringSegment("hello", 1, 3);
+ Assert.AreEqual(1, testSegment.Offset);
+ }
+
+ /// Tests whether the 'Count' property works as expected
+ [Test]
+ public void TestCountProperty() {
+ StringSegment testSegment = new StringSegment("hello", 1, 3);
+ Assert.AreEqual(3, testSegment.Count);
+ }
+
+ ///
+ /// Tests whether two differing instances produce different hash codes
+ ///
+ [Test]
+ public void TestHashCodeOnDifferingInstances() {
+ StringSegment helloWorldSegment = new StringSegment("hello world", 2, 7);
+ StringSegment howAreYouSegment = new StringSegment("how are you", 1, 9);
+
+ Assert.AreNotEqual(
+ helloWorldSegment.GetHashCode(), howAreYouSegment.GetHashCode()
+ );
+ }
+
+ ///
+ /// Tests whether two equivalent instances produce an identical hash code
+ ///
+ [Test]
+ public void TestHashCodeOnEquivalentInstances() {
+ StringSegment helloWorld1Segment = new StringSegment("hello world", 2, 7);
+ StringSegment helloWorld2Segment = new StringSegment("hello world", 2, 7);
+
+ Assert.AreEqual(
+ helloWorld1Segment.GetHashCode(), helloWorld2Segment.GetHashCode()
+ );
+ }
+
+ /// Tests the equals method performing a comparison against null
+ [Test]
+ public void TestEqualsOnNull() {
+ StringSegment helloWorldSegment = new StringSegment("hello world", 2, 7);
+
+ Assert.IsFalse(
+ helloWorldSegment.Equals(null)
+ );
+ }
+
+ /// Tests the equality operator with differing instances
+ [Test]
+ public void TestEqualityOnDifferingInstances() {
+ StringSegment helloWorldSegment = new StringSegment("hello world", 2, 7);
+ StringSegment howAreYouSegment = new StringSegment("how are you", 1, 9);
+
+ Assert.IsFalse(helloWorldSegment == howAreYouSegment);
+ }
+
+ /// Tests the equality operator with equivalent instances
+ [Test]
+ public void TestEqualityOnEquivalentInstances() {
+ StringSegment helloWorld1Segment = new StringSegment("hello world", 2, 7);
+ StringSegment helloWorld2Segment = new StringSegment("hello world", 2, 7);
+
+ Assert.IsTrue(helloWorld1Segment == helloWorld2Segment);
+ }
+
+ /// Tests the inequality operator with differing instances
+ [Test]
+ public void TestInequalityOnDifferingInstances() {
+ StringSegment helloWorldSegment = new StringSegment("hello world", 2, 7);
+ StringSegment howAreYouSegment = new StringSegment("how are you", 1, 9);
+
+ Assert.IsTrue(helloWorldSegment != howAreYouSegment);
+ }
+
+ /// Tests the inequality operator with equivalent instances
+ [Test]
+ public void TestInequalityOnEquivalentInstances() {
+ StringSegment helloWorld1Segment = new StringSegment("hello world", 2, 7);
+ StringSegment helloWorld2Segment = new StringSegment("hello world", 2, 7);
+
+ Assert.IsFalse(helloWorld1Segment != helloWorld2Segment);
+ }
+
+ }
+
+} // namespace Nuclex.Support
+
+#endif // UNITTEST
diff --git a/Source/StringSegment.cs b/Source/StringSegment.cs
index a401a61..4d4fc54 100644
--- a/Source/StringSegment.cs
+++ b/Source/StringSegment.cs
@@ -25,8 +25,22 @@ using System.Runtime.InteropServices;
namespace Nuclex.Support {
/// Delimits a section of a string
+ ///
+ ///
+ /// The design of this class pretty much mirrors that of the
+ /// class found in the .NET framework, but is
+ /// specialized to be used for strings, which can not be expressed as arrays but
+ /// share a lot of the characteristics of an array.
+ ///
+ ///
+ /// In certain situations, passing a StringSegment instead of the the actual
+ /// section from a string is useful. For example, the caller might want to know
+ /// from which index of the original string the substring was taken. Used internally
+ /// in parsers, it can also prevent needless string copying and garbage generation.
+ ///
+ ///
[Serializable, StructLayout(LayoutKind.Sequential)]
- internal struct StringSegment {
+ public struct StringSegment {
///
/// Initializes a new instance of the class that delimits
@@ -35,7 +49,7 @@ namespace Nuclex.Support {
/// String that will be wrapped
/// String is null
public StringSegment(string text) {
- if(text == null) {
+ if(text == null) { // questionable, but matches behavior of ArraySegment class
throw new ArgumentNullException("text");
}
@@ -59,8 +73,8 @@ namespace Nuclex.Support {
///
/// String is null
public StringSegment(string text, int offset, int count) {
- if(text == null) {
- throw new ArgumentNullException("array");
+ if(text == null) { // questionable, but matches behavior of ArraySegment class
+ throw new ArgumentNullException("text");
}
if(offset < 0) {
throw new ArgumentOutOfRangeException(
@@ -72,7 +86,7 @@ namespace Nuclex.Support {
"count", "Argument out of range, non-negative number required"
);
}
- if((text.Length - offset) < count) {
+ if(count > (text.Length - offset)) {
throw new ArgumentException(
"Invalid argument, specified offset and count exceed string length"
);
diff --git a/Source/WeakReference.Test.cs b/Source/WeakReference.Test.cs
new file mode 100644
index 0000000..c2bc336
--- /dev/null
+++ b/Source/WeakReference.Test.cs
@@ -0,0 +1,115 @@
+#region CPL License
+/*
+Nuclex Framework
+Copyright (C) 2002-2008 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.IO;
+using System.Runtime.Serialization.Formatters.Binary;
+
+#if UNITTEST
+
+using NUnit.Framework;
+using NUnit.Framework.SyntaxHelpers;
+
+namespace Nuclex.Support {
+
+ /// Unit Test for the strongly typed weak reference class
+ [TestFixture]
+ public class WeakReferenceTest {
+
+ #region class Dummy
+
+ /// Dummy class for testing the shared instance provider
+ [Serializable]
+ private class Dummy {
+ /// Initializes a new dummy
+ public Dummy() { }
+ }
+
+ #endregion // class Dummy
+
+ /// Tests whether the simple constructor works
+ [Test]
+ public void TestSimpleConstructor() {
+ new WeakReference(new Dummy());
+ }
+
+ /// Test whether the full constructor works
+ [Test]
+ public void TestFullConstructor() {
+ new WeakReference(new Dummy(), false);
+ }
+
+ ///
+ /// Test whether the target object can be retrieved from the weak reference
+ ///
+ [Test]
+ public void TestTargetRetrieval() {
+ Dummy strongReference = new Dummy();
+ WeakReference weakReference = new WeakReference(strongReference);
+
+ // We can not just call GC.Collect() and base our test on the assumption, that
+ // the garbage collector will actually collect the Dummy instance. This is up
+ // to the garbage collector to decide. But we can keep a strong reference in
+ // parallel and safely assume that the WeakReference will not be invalidated!
+ Assert.AreSame(strongReference, weakReference.Target);
+ }
+
+ ///
+ /// Test whether the target object can be reassigned in the weak reference
+ ///
+ [Test]
+ public void TestTargetReassignment() {
+ Dummy strongReference1 = new Dummy();
+ Dummy strongReference2 = new Dummy();
+ WeakReference weakReference = new WeakReference(strongReference1);
+
+ Assert.AreSame(strongReference1, weakReference.Target);
+ weakReference.Target = strongReference2;
+ Assert.AreSame(strongReference2, weakReference.Target);
+ }
+
+ ///
+ /// Test whether the target object can be reassigned in the weak reference
+ ///
+ [Test]
+ public void TestSerialization() {
+ BinaryFormatter formatter = new BinaryFormatter();
+
+ using(MemoryStream memory = new MemoryStream()) {
+ WeakReference weakReference1 = new WeakReference(new Dummy());
+
+ formatter.Serialize(memory, weakReference1);
+ memory.Position = 0;
+ object weakReference2 = formatter.Deserialize(memory);
+
+ // We cannot make any more predictions but for the type of the weak reference.
+ // The pointee might have been garbage collected just now or the serializer
+ // might have decided not to serialize the pointee at all (which is a valid
+ // decision if the serializer found no strong reference to the pointee) in
+ // another of the object graph.
+ Assert.IsNotNull(weakReference2 as WeakReference);
+ }
+ }
+
+ }
+
+} // namespace Nuclex.Support
+
+#endif // UNITTEST
diff --git a/Source/WeakReference.cs b/Source/WeakReference.cs
index e98a181..2ce26b7 100644
--- a/Source/WeakReference.cs
+++ b/Source/WeakReference.cs
@@ -28,6 +28,7 @@ namespace Nuclex.Support {
/// Type-safe weak reference, referencing an object while still allowing
/// that object to be garbage collected.
///
+ [Serializable]
public class WeakReference : WeakReference
where ReferencedType : class {