Changed license to Apache License 2.0

This commit is contained in:
cygon 2024-06-13 18:36:21 +02:00
parent d3bf0be9d7
commit 52dc3d3708
144 changed files with 32422 additions and 32544 deletions

View File

@ -348,11 +348,11 @@
<Content Include="Documents\Request Framework.txt" /> <Content Include="Documents\Request Framework.txt" />
</ItemGroup> </ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it. <!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets. Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild"> <Target Name="BeforeBuild">
</Target> </Target>
<Target Name="AfterBuild"> <Target Name="AfterBuild">
</Target> </Target>
--> -->
</Project> </Project>

View File

@ -351,11 +351,11 @@
<Content Include="Documents\Request Framework.txt" /> <Content Include="Documents\Request Framework.txt" />
</ItemGroup> </ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it. <!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets. Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild"> <Target Name="BeforeBuild">
</Target> </Target>
<Target Name="AfterBuild"> <Target Name="AfterBuild">
</Target> </Target>
--> -->
</Project> </Project>

View File

@ -1,37 +1,56 @@
using System.Reflection; #region Apache License 2.0
using System.Runtime.CompilerServices; /*
using System.Runtime.InteropServices; Nuclex .NET Framework
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information Licensed under the Apache License, Version 2.0 (the "License");
// associated with an assembly. you may not use this file except in compliance with the License.
[assembly: AssemblyTitle("Nuclex.Support")] You may obtain a copy of the License at
[assembly: AssemblyProduct("Nuclex.Support")]
[assembly: AssemblyDescription("")] http://www.apache.org/licenses/LICENSE-2.0
[assembly: AssemblyCompany("Nuclex Development Labs")]
[assembly: AssemblyCopyright("Copyright © Nuclex Development Labs 2008-2017")] Unless required by applicable law or agreed to in writing, software
[assembly: AssemblyTrademark("")] distributed under the License is distributed on an "AS IS" BASIS,
[assembly: AssemblyCulture("")] WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
// Setting ComVisible to false makes the types in this assembly not visible limitations under the License.
// to COM components. If you need to access a type in this assembly from */
// COM, set the ComVisible attribute to true on that type. #endregion // Apache License 2.0
[assembly: ComVisible(false)]
using System.Reflection;
// The following GUID is for the ID of the typelib if this project is exposed to COM using System.Runtime.CompilerServices;
[assembly: Guid("1308e4c3-a0c1-423a-aaae-61c7314777e0")] using System.Runtime.InteropServices;
#if UNITTEST // General Information about an assembly is controlled through the following
// This is required to NMock can derive its proxies from interfaces in // set of attributes. Change these attribute values to modify the information
// the internal unit test classes // associated with an assembly.
[assembly: InternalsVisibleTo(NMock.Constants.InternalsVisibleToDynamicProxy)] [assembly: AssemblyTitle("Nuclex.Support")]
#endif [assembly: AssemblyProduct("Nuclex.Support")]
[assembly: AssemblyDescription("")]
// Version information for an assembly consists of the following four values: [assembly: AssemblyCompany("Nuclex Development Labs")]
// [assembly: AssemblyCopyright("Copyright © Markus Ewald / Nuclex Development Labs 2002-2024")]
// Major Version [assembly: AssemblyTrademark("")]
// Minor Version [assembly: AssemblyCulture("")]
// Build Number
// Revision // Setting ComVisible to false makes the types in this assembly not visible
// // to COM components. If you need to access a type in this assembly from
[assembly: AssemblyVersion("1.0.0.0")] // COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("1308e4c3-a0c1-423a-aaae-61c7314777e0")]
#if UNITTEST
// This is required to NMock can derive its proxies from interfaces in
// the internal unit test classes
[assembly: InternalsVisibleTo(NMock.Constants.InternalsVisibleToDynamicProxy)]
#endif
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
[assembly: AssemblyVersion("1.0.0.0")]

27
Source/Async/AsyncStatus.cs Executable file → Normal file
View File

@ -1,22 +1,21 @@
#region CPL License #region Apache License 2.0
/* /*
Nuclex Framework Nuclex .NET Framework
Copyright (C) 2002-2017 Nuclex Development Labs Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
This library is free software; you can redistribute it and/or Licensed under the Apache License, Version 2.0 (the "License");
modify it under the terms of the IBM Common Public License as you may not use this file except in compliance with the License.
published by the IBM Corporation; either version 1.0 of the You may obtain a copy of the License at
License, or (at your option) any later version.
This library is distributed in the hope that it will be useful, http://www.apache.org/licenses/LICENSE-2.0
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 Unless required by applicable law or agreed to in writing, software
License along with this library distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/ */
#endregion #endregion // Apache License 2.0
using System; using System;

27
Source/Async/AsyncStatusEventArgs.cs Executable file → Normal file
View File

@ -1,22 +1,21 @@
#region CPL License #region Apache License 2.0
/* /*
Nuclex Framework Nuclex .NET Framework
Copyright (C) 2002-2017 Nuclex Development Labs Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
This library is free software; you can redistribute it and/or Licensed under the Apache License, Version 2.0 (the "License");
modify it under the terms of the IBM Common Public License as you may not use this file except in compliance with the License.
published by the IBM Corporation; either version 1.0 of the You may obtain a copy of the License at
License, or (at your option) any later version.
This library is distributed in the hope that it will be useful, http://www.apache.org/licenses/LICENSE-2.0
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 Unless required by applicable law or agreed to in writing, software
License along with this library distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/ */
#endregion #endregion // Apache License 2.0
using System; using System;

27
Source/Async/IAsyncAction.cs Executable file → Normal file
View File

@ -1,22 +1,21 @@
#region CPL License #region Apache License 2.0
/* /*
Nuclex Framework Nuclex .NET Framework
Copyright (C) 2002-2017 Nuclex Development Labs Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
This library is free software; you can redistribute it and/or Licensed under the Apache License, Version 2.0 (the "License");
modify it under the terms of the IBM Common Public License as you may not use this file except in compliance with the License.
published by the IBM Corporation; either version 1.0 of the You may obtain a copy of the License at
License, or (at your option) any later version.
This library is distributed in the hope that it will be useful, http://www.apache.org/licenses/LICENSE-2.0
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 Unless required by applicable law or agreed to in writing, software
License along with this library distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/ */
#endregion #endregion // Apache License 2.0
using System; using System;
using System.Threading; using System.Threading;

27
Source/Async/IAsyncSwitch.cs Executable file → Normal file
View File

@ -1,22 +1,21 @@
#region CPL License #region Apache License 2.0
/* /*
Nuclex Framework Nuclex .NET Framework
Copyright (C) 2002-2017 Nuclex Development Labs Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
This library is free software; you can redistribute it and/or Licensed under the Apache License, Version 2.0 (the "License");
modify it under the terms of the IBM Common Public License as you may not use this file except in compliance with the License.
published by the IBM Corporation; either version 1.0 of the You may obtain a copy of the License at
License, or (at your option) any later version.
This library is distributed in the hope that it will be useful, http://www.apache.org/licenses/LICENSE-2.0
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 Unless required by applicable law or agreed to in writing, software
License along with this library distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/ */
#endregion #endregion // Apache License 2.0
using System; using System;
using System.Threading; using System.Threading;

27
Source/Async/IAsyncTask.cs Executable file → Normal file
View File

@ -1,22 +1,21 @@
#region CPL License #region Apache License 2.0
/* /*
Nuclex Framework Nuclex .NET Framework
Copyright (C) 2002-2017 Nuclex Development Labs Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
This library is free software; you can redistribute it and/or Licensed under the Apache License, Version 2.0 (the "License");
modify it under the terms of the IBM Common Public License as you may not use this file except in compliance with the License.
published by the IBM Corporation; either version 1.0 of the You may obtain a copy of the License at
License, or (at your option) any later version.
This library is distributed in the hope that it will be useful, http://www.apache.org/licenses/LICENSE-2.0
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 Unless required by applicable law or agreed to in writing, software
License along with this library distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/ */
#endregion #endregion // Apache License 2.0
using System; using System;

27
Source/Async/ICancellable.cs Executable file → Normal file
View File

@ -1,22 +1,21 @@
#region CPL License #region Apache License 2.0
/* /*
Nuclex Framework Nuclex .NET Framework
Copyright (C) 2002-2017 Nuclex Development Labs Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
This library is free software; you can redistribute it and/or Licensed under the Apache License, Version 2.0 (the "License");
modify it under the terms of the IBM Common Public License as you may not use this file except in compliance with the License.
published by the IBM Corporation; either version 1.0 of the You may obtain a copy of the License at
License, or (at your option) any later version.
This library is distributed in the hope that it will be useful, http://www.apache.org/licenses/LICENSE-2.0
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 Unless required by applicable law or agreed to in writing, software
License along with this library distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/ */
#endregion #endregion // Apache License 2.0
using System; using System;

27
Source/Async/IProgressSource.cs Executable file → Normal file
View File

@ -1,22 +1,21 @@
#region CPL License #region Apache License 2.0
/* /*
Nuclex Framework Nuclex .NET Framework
Copyright (C) 2002-2017 Nuclex Development Labs Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
This library is free software; you can redistribute it and/or Licensed under the Apache License, Version 2.0 (the "License");
modify it under the terms of the IBM Common Public License as you may not use this file except in compliance with the License.
published by the IBM Corporation; either version 1.0 of the You may obtain a copy of the License at
License, or (at your option) any later version.
This library is distributed in the hope that it will be useful, http://www.apache.org/licenses/LICENSE-2.0
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 Unless required by applicable law or agreed to in writing, software
License along with this library distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/ */
#endregion #endregion // Apache License 2.0
using System; using System;

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,226 +1,225 @@
#region CPL License #region Apache License 2.0
/* /*
Nuclex Framework Nuclex .NET Framework
Copyright (C) 2002-2017 Nuclex Development Labs Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
This library is free software; you can redistribute it and/or Licensed under the Apache License, Version 2.0 (the "License");
modify it under the terms of the IBM Common Public License as you may not use this file except in compliance with the License.
published by the IBM Corporation; either version 1.0 of the You may obtain a copy of the License at
License, or (at your option) any later version.
http://www.apache.org/licenses/LICENSE-2.0
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of Unless required by applicable law or agreed to in writing, software
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the distributed under the License is distributed on an "AS IS" BASIS,
IBM Common Public License for more details. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
You should have received a copy of the IBM Common Public limitations under the License.
License along with this library */
*/ #endregion // Apache License 2.0
#endregion
#if !NO_SETS
#if !NO_SETS
#if UNITTEST
#if UNITTEST
using System;
using System; using System.Collections.Generic;
using System.Collections.Generic;
using NUnit.Framework;
using NUnit.Framework;
namespace Nuclex.Support.Cloning {
namespace Nuclex.Support.Cloning {
/// <summary>Unit Test for the expression tree-based cloner</summary>
/// <summary>Unit Test for the expression tree-based cloner</summary> [TestFixture]
[TestFixture] internal class ExpressionTreeClonerTest : CloneFactoryTest {
internal class ExpressionTreeClonerTest : CloneFactoryTest {
/// <summary>Initializes a new unit test suite for the reflection cloner</summary>
/// <summary>Initializes a new unit test suite for the reflection cloner</summary> public ExpressionTreeClonerTest() {
public ExpressionTreeClonerTest() { this.cloneFactory = new ExpressionTreeCloner();
this.cloneFactory = new ExpressionTreeCloner(); }
}
/// <summary>Verifies that cloning a null object simply returns null</summary>
/// <summary>Verifies that cloning a null object simply returns null</summary> [Test]
[Test] public void CloningNullYieldsNull() {
public void CloningNullYieldsNull() { Assert.IsNull(this.cloneFactory.DeepFieldClone<object>(null));
Assert.IsNull(this.cloneFactory.DeepFieldClone<object>(null)); Assert.IsNull(this.cloneFactory.DeepPropertyClone<object>(null));
Assert.IsNull(this.cloneFactory.DeepPropertyClone<object>(null)); Assert.IsNull(this.cloneFactory.ShallowFieldClone<object>(null));
Assert.IsNull(this.cloneFactory.ShallowFieldClone<object>(null)); Assert.IsNull(this.cloneFactory.ShallowPropertyClone<object>(null));
Assert.IsNull(this.cloneFactory.ShallowPropertyClone<object>(null)); }
}
/// <summary>
/// <summary> /// Verifies that clones of objects whose class doesn't possess a default constructor
/// Verifies that clones of objects whose class doesn't possess a default constructor /// can be made
/// can be made /// </summary>
/// </summary> [Test]
[Test] public void ClassWithoutDefaultConstructorCanBeCloned() {
public void ClassWithoutDefaultConstructorCanBeCloned() { var original = new ClassWithoutDefaultConstructor(1234);
var original = new ClassWithoutDefaultConstructor(1234); ClassWithoutDefaultConstructor clone = this.cloneFactory.DeepFieldClone(original);
ClassWithoutDefaultConstructor clone = this.cloneFactory.DeepFieldClone(original);
Assert.AreNotSame(original, clone);
Assert.AreNotSame(original, clone); Assert.AreEqual(original.Dummy, clone.Dummy);
Assert.AreEqual(original.Dummy, clone.Dummy); }
}
/// <summary>Verifies that clones of primitive types can be created</summary>
/// <summary>Verifies that clones of primitive types can be created</summary> [Test]
[Test] public void PrimitiveTypesCanBeCloned() {
public void PrimitiveTypesCanBeCloned() { int original = 12345;
int original = 12345; int clone = this.cloneFactory.DeepFieldClone(original);
int clone = this.cloneFactory.DeepFieldClone(original); Assert.AreEqual(original, clone);
Assert.AreEqual(original, clone); }
}
/// <summary>Verifies that shallow clones of arrays can be made</summary>
/// <summary>Verifies that shallow clones of arrays can be made</summary> [Test]
[Test] public void ReferenceTypesCanBeCloned() {
public void ReferenceTypesCanBeCloned() { var original = new TestReferenceType() { TestField = 123, TestProperty = 456 };
var original = new TestReferenceType() { TestField = 123, TestProperty = 456 }; TestReferenceType clone = this.cloneFactory.DeepFieldClone(original);
TestReferenceType clone = this.cloneFactory.DeepFieldClone(original);
Assert.AreNotSame(original, clone);
Assert.AreNotSame(original, clone); Assert.AreEqual(original.TestField, clone.TestField);
Assert.AreEqual(original.TestField, clone.TestField); Assert.AreEqual(original.TestProperty, clone.TestProperty);
Assert.AreEqual(original.TestProperty, clone.TestProperty); }
}
/// <summary>Verifies that shallow clones of arrays can be made</summary>
/// <summary>Verifies that shallow clones of arrays can be made</summary> [Test]
[Test] public void PrimitiveArraysCanBeCloned() {
public void PrimitiveArraysCanBeCloned() { var original = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
var original = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 }; int[] clone = this.cloneFactory.DeepFieldClone(original);
int[] clone = this.cloneFactory.DeepFieldClone(original);
Assert.AreNotSame(original, clone);
Assert.AreNotSame(original, clone); CollectionAssert.AreEqual(original, clone);
CollectionAssert.AreEqual(original, clone); }
}
/// <summary>Verifies that shallow clones of arrays can be made</summary>
/// <summary>Verifies that shallow clones of arrays can be made</summary> [Test]
[Test] public void ShallowClonesOfArraysCanBeMade() {
public void ShallowClonesOfArraysCanBeMade() { var original = new TestReferenceType[] {
var original = new TestReferenceType[] { new TestReferenceType() { TestField = 123, TestProperty = 456 }
new TestReferenceType() { TestField = 123, TestProperty = 456 } };
}; TestReferenceType[] clone = this.cloneFactory.ShallowFieldClone(original);
TestReferenceType[] clone = this.cloneFactory.ShallowFieldClone(original);
Assert.AreSame(original[0], clone[0]);
Assert.AreSame(original[0], clone[0]); }
}
/// <summary>Verifies that deep clones of arrays can be made</summary>
/// <summary>Verifies that deep clones of arrays can be made</summary> [Test]
[Test] public void DeepClonesOfArraysCanBeMade() {
public void DeepClonesOfArraysCanBeMade() { var original = new TestReferenceType[,] {
var original = new TestReferenceType[,] { {
{ new TestReferenceType() { TestField = 123, TestProperty = 456 }
new TestReferenceType() { TestField = 123, TestProperty = 456 } }
} };
}; TestReferenceType[,] clone = this.cloneFactory.DeepFieldClone(original);
TestReferenceType[,] clone = this.cloneFactory.DeepFieldClone(original);
Assert.AreNotSame(original[0, 0], clone[0, 0]);
Assert.AreNotSame(original[0, 0], clone[0, 0]); Assert.AreEqual(original[0, 0].TestField, clone[0, 0].TestField);
Assert.AreEqual(original[0, 0].TestField, clone[0, 0].TestField); Assert.AreEqual(original[0, 0].TestProperty, clone[0, 0].TestProperty);
Assert.AreEqual(original[0, 0].TestProperty, clone[0, 0].TestProperty); }
}
/// <summary>Verifies that deep clones of a generic list can be made</summary>
/// <summary>Verifies that deep clones of a generic list can be made</summary> [Test]
[Test] public void GenericListsCanBeCloned() {
public void GenericListsCanBeCloned() { var original = new List<int>(new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 });
var original = new List<int>(new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 }); List<int> clone = this.cloneFactory.DeepFieldClone(original);
List<int> clone = this.cloneFactory.DeepFieldClone(original);
CollectionAssert.AreEqual(original, clone);
CollectionAssert.AreEqual(original, clone); }
}
/// <summary>Verifies that deep clones of a generic dictionary can be made</summary>
/// <summary>Verifies that deep clones of a generic dictionary can be made</summary> [Test]
[Test] public void GenericDictionariesCanBeCloned() {
public void GenericDictionariesCanBeCloned() { var original = new Dictionary<int, string>();
var original = new Dictionary<int, string>(); original.Add(1, "one");
original.Add(1, "one"); Dictionary<int, string> clone = this.cloneFactory.DeepFieldClone(original);
Dictionary<int, string> clone = this.cloneFactory.DeepFieldClone(original);
Assert.AreEqual("one", clone[1]);
Assert.AreEqual("one", clone[1]); }
}
/// <summary>
/// <summary> /// Verifies that a field-based shallow clone of a value type can be performed
/// Verifies that a field-based shallow clone of a value type can be performed /// </summary>
/// </summary> [Test]
[Test] public void ShallowFieldBasedClonesOfValueTypesCanBeMade() {
public void ShallowFieldBasedClonesOfValueTypesCanBeMade() { HierarchicalValueType original = CreateValueType();
HierarchicalValueType original = CreateValueType(); HierarchicalValueType clone = this.cloneFactory.ShallowFieldClone(original);
HierarchicalValueType clone = this.cloneFactory.ShallowFieldClone(original); VerifyClone(ref original, ref clone, isDeepClone: false, isPropertyBasedClone: false);
VerifyClone(ref original, ref clone, isDeepClone: false, isPropertyBasedClone: false); }
}
/// <summary>
/// <summary> /// Verifies that a field-based shallow clone of a reference type can be performed
/// Verifies that a field-based shallow clone of a reference type can be performed /// </summary>
/// </summary> [Test]
[Test] public void ShallowFieldBasedClonesOfReferenceTypesCanBeMade() {
public void ShallowFieldBasedClonesOfReferenceTypesCanBeMade() { HierarchicalReferenceType original = CreateReferenceType();
HierarchicalReferenceType original = CreateReferenceType(); HierarchicalReferenceType clone = this.cloneFactory.ShallowFieldClone(original);
HierarchicalReferenceType clone = this.cloneFactory.ShallowFieldClone(original); VerifyClone(original, clone, isDeepClone: false, isPropertyBasedClone: false);
VerifyClone(original, clone, isDeepClone: false, isPropertyBasedClone: false); }
}
/// <summary>
/// <summary> /// Verifies that a field-based deep clone of a value type can be performed
/// Verifies that a field-based deep clone of a value type can be performed /// </summary>
/// </summary> [Test]
[Test] public void DeepFieldBasedClonesOfValueTypesCanBeMade() {
public void DeepFieldBasedClonesOfValueTypesCanBeMade() { HierarchicalValueType original = CreateValueType();
HierarchicalValueType original = CreateValueType(); HierarchicalValueType clone = this.cloneFactory.DeepFieldClone(original);
HierarchicalValueType clone = this.cloneFactory.DeepFieldClone(original); VerifyClone(ref original, ref clone, isDeepClone: true, isPropertyBasedClone: false);
VerifyClone(ref original, ref clone, isDeepClone: true, isPropertyBasedClone: false); }
}
/// <summary>
/// <summary> /// Verifies that a field-based deep clone of a reference type can be performed
/// Verifies that a field-based deep clone of a reference type can be performed /// </summary>
/// </summary> [Test]
[Test] public void DeepFieldBasedClonesOfReferenceTypesCanBeMade() {
public void DeepFieldBasedClonesOfReferenceTypesCanBeMade() { HierarchicalReferenceType original = CreateReferenceType();
HierarchicalReferenceType original = CreateReferenceType(); HierarchicalReferenceType clone = this.cloneFactory.DeepFieldClone(original);
HierarchicalReferenceType clone = this.cloneFactory.DeepFieldClone(original); VerifyClone(original, clone, isDeepClone: true, isPropertyBasedClone: false);
VerifyClone(original, clone, isDeepClone: true, isPropertyBasedClone: false); }
}
/// <summary>
/// <summary> /// Verifies that a property-based shallow clone of a value type can be performed
/// Verifies that a property-based shallow clone of a value type can be performed /// </summary>
/// </summary> [Test]
[Test] public void ShallowPropertyBasedClonesOfValueTypesCanBeMade() {
public void ShallowPropertyBasedClonesOfValueTypesCanBeMade() { HierarchicalValueType original = CreateValueType();
HierarchicalValueType original = CreateValueType(); HierarchicalValueType clone = this.cloneFactory.ShallowPropertyClone(original);
HierarchicalValueType clone = this.cloneFactory.ShallowPropertyClone(original); VerifyClone(ref original, ref clone, isDeepClone: false, isPropertyBasedClone: true);
VerifyClone(ref original, ref clone, isDeepClone: false, isPropertyBasedClone: true); }
}
/// <summary>
/// <summary> /// Verifies that a property-based shallow clone of a reference type can be performed
/// Verifies that a property-based shallow clone of a reference type can be performed /// </summary>
/// </summary> [Test]
[Test] public void ShallowPropertyBasedClonesOfReferenceTypesCanBeMade() {
public void ShallowPropertyBasedClonesOfReferenceTypesCanBeMade() { HierarchicalReferenceType original = CreateReferenceType();
HierarchicalReferenceType original = CreateReferenceType(); HierarchicalReferenceType clone = this.cloneFactory.ShallowPropertyClone(original);
HierarchicalReferenceType clone = this.cloneFactory.ShallowPropertyClone(original); VerifyClone(original, clone, isDeepClone: false, isPropertyBasedClone: true);
VerifyClone(original, clone, isDeepClone: false, isPropertyBasedClone: true); }
}
/// <summary>
/// <summary> /// Verifies that a property-based deep clone of a value type can be performed
/// Verifies that a property-based deep clone of a value type can be performed /// </summary>
/// </summary> [Test]
[Test] public void DeepPropertyBasedClonesOfValueTypesCanBeMade() {
public void DeepPropertyBasedClonesOfValueTypesCanBeMade() { HierarchicalValueType original = CreateValueType();
HierarchicalValueType original = CreateValueType(); HierarchicalValueType clone = this.cloneFactory.DeepPropertyClone(original);
HierarchicalValueType clone = this.cloneFactory.DeepPropertyClone(original); VerifyClone(ref original, ref clone, isDeepClone: true, isPropertyBasedClone: true);
VerifyClone(ref original, ref clone, isDeepClone: true, isPropertyBasedClone: true); }
}
/// <summary>
/// <summary> /// Verifies that a property-based deep clone of a reference type can be performed
/// Verifies that a property-based deep clone of a reference type can be performed /// </summary>
/// </summary> [Test]
[Test] public void DeepPropertyBasedClonesOfReferenceTypesCanBeMade() {
public void DeepPropertyBasedClonesOfReferenceTypesCanBeMade() { HierarchicalReferenceType original = CreateReferenceType();
HierarchicalReferenceType original = CreateReferenceType(); HierarchicalReferenceType clone = this.cloneFactory.DeepPropertyClone(original);
HierarchicalReferenceType clone = this.cloneFactory.DeepPropertyClone(original); VerifyClone(original, clone, isDeepClone: true, isPropertyBasedClone: true);
VerifyClone(original, clone, isDeepClone: true, isPropertyBasedClone: true); }
}
/// <summary>Clone factory being tested</summary>
/// <summary>Clone factory being tested</summary> private ICloneFactory cloneFactory;
private ICloneFactory cloneFactory;
}
}
} // namespace Nuclex.Support.Cloning
} // namespace Nuclex.Support.Cloning
#endif // UNITTEST
#endif // UNITTEST
#endif // !NO_SETS
#endif // !NO_SETS

View File

@ -1,286 +1,285 @@
#region CPL License #region Apache License 2.0
/* /*
Nuclex Framework Nuclex .NET Framework
Copyright (C) 2002-2017 Nuclex Development Labs Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
This library is free software; you can redistribute it and/or Licensed under the Apache License, Version 2.0 (the "License");
modify it under the terms of the IBM Common Public License as you may not use this file except in compliance with the License.
published by the IBM Corporation; either version 1.0 of the You may obtain a copy of the License at
License, or (at your option) any later version.
http://www.apache.org/licenses/LICENSE-2.0
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of Unless required by applicable law or agreed to in writing, software
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the distributed under the License is distributed on an "AS IS" BASIS,
IBM Common Public License for more details. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
You should have received a copy of the IBM Common Public limitations under the License.
License along with this library */
*/ #endregion // Apache License 2.0
#endregion
#if !NO_SETS
#if !NO_SETS
using System;
using System; using System.Collections.Concurrent;
using System.Collections.Concurrent;
namespace Nuclex.Support.Cloning {
namespace Nuclex.Support.Cloning {
/// <summary>
/// <summary> /// Cloning factory which uses expression trees to improve performance when cloning
/// Cloning factory which uses expression trees to improve performance when cloning /// is a high-frequency action.
/// is a high-frequency action. /// </summary>
/// </summary> public partial class ExpressionTreeCloner : ICloneFactory {
public partial class ExpressionTreeCloner : ICloneFactory {
/// <summary>Initializes the static members of the expression tree cloner</summary>
/// <summary>Initializes the static members of the expression tree cloner</summary> static ExpressionTreeCloner() {
static ExpressionTreeCloner() { shallowFieldBasedCloners = new ConcurrentDictionary<Type, Func<object, object>>();
shallowFieldBasedCloners = new ConcurrentDictionary<Type, Func<object, object>>(); deepFieldBasedCloners = new ConcurrentDictionary<Type, Func<object, object>>();
deepFieldBasedCloners = new ConcurrentDictionary<Type, Func<object, object>>(); shallowPropertyBasedCloners = new ConcurrentDictionary<Type, Func<object, object>>();
shallowPropertyBasedCloners = new ConcurrentDictionary<Type, Func<object, object>>(); deepPropertyBasedCloners = new ConcurrentDictionary<Type, Func<object, object>>();
deepPropertyBasedCloners = new ConcurrentDictionary<Type, Func<object, object>>(); }
}
/// <summary>
/// <summary> /// Creates a deep clone of the specified object, also creating clones of all
/// Creates a deep clone of the specified object, also creating clones of all /// child objects being referenced
/// child objects being referenced /// </summary>
/// </summary> /// <typeparam name="TCloned">Type of the object that will be cloned</typeparam>
/// <typeparam name="TCloned">Type of the object that will be cloned</typeparam> /// <param name="objectToClone">Object that will be cloned</param>
/// <param name="objectToClone">Object that will be cloned</param> /// <returns>A deep clone of the provided object</returns>
/// <returns>A deep clone of the provided object</returns> public static TCloned DeepFieldClone<TCloned>(TCloned objectToClone) {
public static TCloned DeepFieldClone<TCloned>(TCloned objectToClone) { object objectToCloneAsObject = objectToClone;
object objectToCloneAsObject = objectToClone; if(objectToCloneAsObject == null) {
if(objectToCloneAsObject == null) { return default(TCloned);
return default(TCloned); }
}
Func<object, object> cloner = getOrCreateDeepFieldBasedCloner(typeof(TCloned));
Func<object, object> cloner = getOrCreateDeepFieldBasedCloner(typeof(TCloned)); return (TCloned)cloner(objectToCloneAsObject);
return (TCloned)cloner(objectToCloneAsObject); }
}
/// <summary>
/// <summary> /// Creates a deep clone of the specified object, also creating clones of all
/// Creates a deep clone of the specified object, also creating clones of all /// child objects being referenced
/// child objects being referenced /// </summary>
/// </summary> /// <typeparam name="TCloned">Type of the object that will be cloned</typeparam>
/// <typeparam name="TCloned">Type of the object that will be cloned</typeparam> /// <param name="objectToClone">Object that will be cloned</param>
/// <param name="objectToClone">Object that will be cloned</param> /// <returns>A deep clone of the provided object</returns>
/// <returns>A deep clone of the provided object</returns> public static TCloned DeepPropertyClone<TCloned>(TCloned objectToClone) {
public static TCloned DeepPropertyClone<TCloned>(TCloned objectToClone) { object objectToCloneAsObject = objectToClone;
object objectToCloneAsObject = objectToClone; if(objectToCloneAsObject == null) {
if(objectToCloneAsObject == null) { return default(TCloned);
return default(TCloned); }
}
Func<object, object> cloner = getOrCreateDeepPropertyBasedCloner(typeof(TCloned));
Func<object, object> cloner = getOrCreateDeepPropertyBasedCloner(typeof(TCloned)); return (TCloned)cloner(objectToCloneAsObject);
return (TCloned)cloner(objectToCloneAsObject); }
}
/// <summary>
/// <summary> /// Creates a shallow clone of the specified object, reusing any referenced objects
/// Creates a shallow clone of the specified object, reusing any referenced objects /// </summary>
/// </summary> /// <typeparam name="TCloned">Type of the object that will be cloned</typeparam>
/// <typeparam name="TCloned">Type of the object that will be cloned</typeparam> /// <param name="objectToClone">Object that will be cloned</param>
/// <param name="objectToClone">Object that will be cloned</param> /// <returns>A shallow clone of the provided object</returns>
/// <returns>A shallow clone of the provided object</returns> public static TCloned ShallowFieldClone<TCloned>(TCloned objectToClone) {
public static TCloned ShallowFieldClone<TCloned>(TCloned objectToClone) { object objectToCloneAsObject = objectToClone;
object objectToCloneAsObject = objectToClone; if(objectToCloneAsObject == null) {
if(objectToCloneAsObject == null) { return default(TCloned);
return default(TCloned); }
}
Func<object, object> cloner = getOrCreateShallowFieldBasedCloner(typeof(TCloned));
Func<object, object> cloner = getOrCreateShallowFieldBasedCloner(typeof(TCloned)); return (TCloned)cloner(objectToCloneAsObject);
return (TCloned)cloner(objectToCloneAsObject); }
}
/// <summary>
/// <summary> /// Creates a shallow clone of the specified object, reusing any referenced objects
/// Creates a shallow clone of the specified object, reusing any referenced objects /// </summary>
/// </summary> /// <typeparam name="TCloned">Type of the object that will be cloned</typeparam>
/// <typeparam name="TCloned">Type of the object that will be cloned</typeparam> /// <param name="objectToClone">Object that will be cloned</param>
/// <param name="objectToClone">Object that will be cloned</param> /// <returns>A shallow clone of the provided object</returns>
/// <returns>A shallow clone of the provided object</returns> public static TCloned ShallowPropertyClone<TCloned>(TCloned objectToClone) {
public static TCloned ShallowPropertyClone<TCloned>(TCloned objectToClone) { object objectToCloneAsObject = objectToClone;
object objectToCloneAsObject = objectToClone; if(objectToCloneAsObject == null) {
if(objectToCloneAsObject == null) { return default(TCloned);
return default(TCloned); }
}
Func<object, object> cloner = getOrCreateShallowPropertyBasedCloner(typeof(TCloned));
Func<object, object> cloner = getOrCreateShallowPropertyBasedCloner(typeof(TCloned)); return (TCloned)cloner(objectToCloneAsObject);
return (TCloned)cloner(objectToCloneAsObject); }
}
/// <summary>
/// <summary> /// Creates a shallow clone of the specified object, reusing any referenced objects
/// Creates a shallow clone of the specified object, reusing any referenced objects /// </summary>
/// </summary> /// <typeparam name="TCloned">Type of the object that will be cloned</typeparam>
/// <typeparam name="TCloned">Type of the object that will be cloned</typeparam> /// <param name="objectToClone">Object that will be cloned</param>
/// <param name="objectToClone">Object that will be cloned</param> /// <returns>A shallow clone of the provided object</returns>
/// <returns>A shallow clone of the provided object</returns> TCloned ICloneFactory.ShallowFieldClone<TCloned>(TCloned objectToClone) {
TCloned ICloneFactory.ShallowFieldClone<TCloned>(TCloned objectToClone) { return ExpressionTreeCloner.ShallowFieldClone<TCloned>(objectToClone);
return ExpressionTreeCloner.ShallowFieldClone<TCloned>(objectToClone); }
}
/// <summary>
/// <summary> /// Creates a shallow clone of the specified object, reusing any referenced objects
/// Creates a shallow clone of the specified object, reusing any referenced objects /// </summary>
/// </summary> /// <typeparam name="TCloned">Type of the object that will be cloned</typeparam>
/// <typeparam name="TCloned">Type of the object that will be cloned</typeparam> /// <param name="objectToClone">Object that will be cloned</param>
/// <param name="objectToClone">Object that will be cloned</param> /// <returns>A shallow clone of the provided object</returns>
/// <returns>A shallow clone of the provided object</returns> TCloned ICloneFactory.ShallowPropertyClone<TCloned>(TCloned objectToClone) {
TCloned ICloneFactory.ShallowPropertyClone<TCloned>(TCloned objectToClone) { return ExpressionTreeCloner.ShallowPropertyClone<TCloned>(objectToClone);
return ExpressionTreeCloner.ShallowPropertyClone<TCloned>(objectToClone); }
}
/// <summary>
/// <summary> /// Creates a deep clone of the specified object, also creating clones of all
/// Creates a deep clone of the specified object, also creating clones of all /// child objects being referenced
/// child objects being referenced /// </summary>
/// </summary> /// <typeparam name="TCloned">Type of the object that will be cloned</typeparam>
/// <typeparam name="TCloned">Type of the object that will be cloned</typeparam> /// <param name="objectToClone">Object that will be cloned</param>
/// <param name="objectToClone">Object that will be cloned</param> /// <returns>A deep clone of the provided object</returns>
/// <returns>A deep clone of the provided object</returns> TCloned ICloneFactory.DeepFieldClone<TCloned>(TCloned objectToClone) {
TCloned ICloneFactory.DeepFieldClone<TCloned>(TCloned objectToClone) { return ExpressionTreeCloner.DeepFieldClone<TCloned>(objectToClone);
return ExpressionTreeCloner.DeepFieldClone<TCloned>(objectToClone); }
}
/// <summary>
/// <summary> /// Creates a deep clone of the specified object, also creating clones of all
/// Creates a deep clone of the specified object, also creating clones of all /// child objects being referenced
/// child objects being referenced /// </summary>
/// </summary> /// <typeparam name="TCloned">Type of the object that will be cloned</typeparam>
/// <typeparam name="TCloned">Type of the object that will be cloned</typeparam> /// <param name="objectToClone">Object that will be cloned</param>
/// <param name="objectToClone">Object that will be cloned</param> /// <returns>A deep clone of the provided object</returns>
/// <returns>A deep clone of the provided object</returns> TCloned ICloneFactory.DeepPropertyClone<TCloned>(TCloned objectToClone) {
TCloned ICloneFactory.DeepPropertyClone<TCloned>(TCloned objectToClone) { return ExpressionTreeCloner.DeepPropertyClone<TCloned>(objectToClone);
return ExpressionTreeCloner.DeepPropertyClone<TCloned>(objectToClone); }
}
#if false
#if false /// <summary>
/// <summary> /// Transfers the state of one object into another, creating clones of referenced objects
/// Transfers the state of one object into another, creating clones of referenced objects /// </summary>
/// </summary> /// <typeparam name="TState">Type of the object whose sate will be transferred</typeparam>
/// <typeparam name="TState">Type of the object whose sate will be transferred</typeparam> /// <param name="original">Original instance the state will be taken from</param>
/// <param name="original">Original instance the state will be taken from</param> /// <param name="target">Target instance the state will be written to</param>
/// <param name="target">Target instance the state will be written to</param> /// <param name="propertyBased">Whether to perform a property-based state copy</param>
/// <param name="propertyBased">Whether to perform a property-based state copy</param> public void DeepCopyState<TState>(TState original, TState target, bool propertyBased)
public void DeepCopyState<TState>(TState original, TState target, bool propertyBased) where TState : class {
where TState : class { throw new NotImplementedException();
throw new NotImplementedException(); }
}
/// <summary>
/// <summary> /// Transfers the state of one object into another, creating clones of referenced objects
/// Transfers the state of one object into another, creating clones of referenced objects /// </summary>
/// </summary> /// <typeparam name="TState">Type of the object whose sate will be transferred</typeparam>
/// <typeparam name="TState">Type of the object whose sate will be transferred</typeparam> /// <param name="original">Original instance the state will be taken from</param>
/// <param name="original">Original instance the state will be taken from</param> /// <param name="target">Target instance the state will be written to</param>
/// <param name="target">Target instance the state will be written to</param> /// <param name="propertyBased">Whether to perform a property-based state copy</param>
/// <param name="propertyBased">Whether to perform a property-based state copy</param> public void DeepCopyState<TState>(ref TState original, ref TState target, bool propertyBased)
public void DeepCopyState<TState>(ref TState original, ref TState target, bool propertyBased) where TState : struct {
where TState : struct { throw new NotImplementedException();
throw new NotImplementedException(); }
}
/// <summary>Transfers the state of one object into another</summary>
/// <summary>Transfers the state of one object into another</summary> /// <typeparam name="TState">Type of the object whose sate will be transferred</typeparam>
/// <typeparam name="TState">Type of the object whose sate will be transferred</typeparam> /// <param name="original">Original instance the state will be taken from</param>
/// <param name="original">Original instance the state will be taken from</param> /// <param name="target">Target instance the state will be written to</param>
/// <param name="target">Target instance the state will be written to</param> /// <param name="propertyBased">Whether to perform a property-based state copy</param>
/// <param name="propertyBased">Whether to perform a property-based state copy</param> public void ShallowCopyState<TState>(TState original, TState target, bool propertyBased)
public void ShallowCopyState<TState>(TState original, TState target, bool propertyBased) where TState : class {
where TState : class { throw new NotImplementedException();
throw new NotImplementedException(); }
}
/// <summary>Transfers the state of one object into another</summary>
/// <summary>Transfers the state of one object into another</summary> /// <typeparam name="TState">Type of the object whose sate will be transferred</typeparam>
/// <typeparam name="TState">Type of the object whose sate will be transferred</typeparam> /// <param name="original">Original instance the state will be taken from</param>
/// <param name="original">Original instance the state will be taken from</param> /// <param name="target">Target instance the state will be written to</param>
/// <param name="target">Target instance the state will be written to</param> /// <param name="propertyBased">Whether to perform a property-based state copy</param>
/// <param name="propertyBased">Whether to perform a property-based state copy</param> public void ShallowCopyState<TState>(ref TState original, ref TState target, bool propertyBased)
public void ShallowCopyState<TState>(ref TState original, ref TState target, bool propertyBased) where TState : struct {
where TState : struct { throw new NotImplementedException();
throw new NotImplementedException(); }
}
#endif
#endif
/// <summary>
/// <summary> /// Retrieves the existing clone method for the specified type or compiles one if
/// Retrieves the existing clone method for the specified type or compiles one if /// none exists for the type yet
/// none exists for the type yet /// </summary>
/// </summary> /// <param name="clonedType">Type for which a clone method will be retrieved</param>
/// <param name="clonedType">Type for which a clone method will be retrieved</param> /// <returns>The clone method for the specified type</returns>
/// <returns>The clone method for the specified type</returns> private static Func<object, object> getOrCreateShallowFieldBasedCloner(Type clonedType) {
private static Func<object, object> getOrCreateShallowFieldBasedCloner(Type clonedType) { Func<object, object> cloner;
Func<object, object> cloner;
if(!shallowFieldBasedCloners.TryGetValue(clonedType, out cloner)) {
if(!shallowFieldBasedCloners.TryGetValue(clonedType, out cloner)) { cloner = createShallowFieldBasedCloner(clonedType);
cloner = createShallowFieldBasedCloner(clonedType); shallowFieldBasedCloners.TryAdd(clonedType, cloner);
shallowFieldBasedCloners.TryAdd(clonedType, cloner); }
}
return cloner;
return cloner; }
}
/// <summary>
/// <summary> /// Retrieves the existing clone method for the specified type or compiles one if
/// Retrieves the existing clone method for the specified type or compiles one if /// none exists for the type yet
/// none exists for the type yet /// </summary>
/// </summary> /// <param name="clonedType">Type for which a clone method will be retrieved</param>
/// <param name="clonedType">Type for which a clone method will be retrieved</param> /// <returns>The clone method for the specified type</returns>
/// <returns>The clone method for the specified type</returns> private static Func<object, object> getOrCreateDeepFieldBasedCloner(Type clonedType) {
private static Func<object, object> getOrCreateDeepFieldBasedCloner(Type clonedType) { Func<object, object> cloner;
Func<object, object> cloner;
if(!deepFieldBasedCloners.TryGetValue(clonedType, out cloner)) {
if(!deepFieldBasedCloners.TryGetValue(clonedType, out cloner)) { cloner = createDeepFieldBasedCloner(clonedType);
cloner = createDeepFieldBasedCloner(clonedType); deepFieldBasedCloners.TryAdd(clonedType, cloner);
deepFieldBasedCloners.TryAdd(clonedType, cloner); }
}
return cloner;
return cloner; }
}
/// <summary>
/// <summary> /// Retrieves the existing clone method for the specified type or compiles one if
/// Retrieves the existing clone method for the specified type or compiles one if /// none exists for the type yet
/// none exists for the type yet /// </summary>
/// </summary> /// <param name="clonedType">Type for which a clone method will be retrieved</param>
/// <param name="clonedType">Type for which a clone method will be retrieved</param> /// <returns>The clone method for the specified type</returns>
/// <returns>The clone method for the specified type</returns> private static Func<object, object> getOrCreateShallowPropertyBasedCloner(Type clonedType) {
private static Func<object, object> getOrCreateShallowPropertyBasedCloner(Type clonedType) { Func<object, object> cloner;
Func<object, object> cloner;
if(!shallowPropertyBasedCloners.TryGetValue(clonedType, out cloner)) {
if(!shallowPropertyBasedCloners.TryGetValue(clonedType, out cloner)) { cloner = createShallowPropertyBasedCloner(clonedType);
cloner = createShallowPropertyBasedCloner(clonedType); shallowPropertyBasedCloners.TryAdd(clonedType, cloner);
shallowPropertyBasedCloners.TryAdd(clonedType, cloner); }
}
return cloner;
return cloner; }
}
/// <summary>
/// <summary> /// Retrieves the existing clone method for the specified type or compiles one if
/// Retrieves the existing clone method for the specified type or compiles one if /// none exists for the type yet
/// none exists for the type yet /// </summary>
/// </summary> /// <param name="clonedType">Type for which a clone method will be retrieved</param>
/// <param name="clonedType">Type for which a clone method will be retrieved</param> /// <returns>The clone method for the specified type</returns>
/// <returns>The clone method for the specified type</returns> private static Func<object, object> getOrCreateDeepPropertyBasedCloner(Type clonedType) {
private static Func<object, object> getOrCreateDeepPropertyBasedCloner(Type clonedType) { Func<object, object> cloner;
Func<object, object> cloner;
if(!deepPropertyBasedCloners.TryGetValue(clonedType, out cloner)) {
if(!deepPropertyBasedCloners.TryGetValue(clonedType, out cloner)) { cloner = createDeepPropertyBasedCloner(clonedType);
cloner = createDeepPropertyBasedCloner(clonedType); deepPropertyBasedCloners.TryAdd(clonedType, cloner);
deepPropertyBasedCloners.TryAdd(clonedType, cloner); }
}
return cloner;
return cloner; }
}
/// <summary>Compiled cloners that perform shallow clone operations</summary>
/// <summary>Compiled cloners that perform shallow clone operations</summary> private static readonly ConcurrentDictionary<
private static readonly ConcurrentDictionary< Type, Func<object, object>
Type, Func<object, object> > shallowFieldBasedCloners;
> shallowFieldBasedCloners; /// <summary>Compiled cloners that perform deep clone operations</summary>
/// <summary>Compiled cloners that perform deep clone operations</summary> private static readonly ConcurrentDictionary<
private static readonly ConcurrentDictionary< Type, Func<object, object>
Type, Func<object, object> > deepFieldBasedCloners;
> deepFieldBasedCloners; /// <summary>Compiled cloners that perform shallow clone operations</summary>
/// <summary>Compiled cloners that perform shallow clone operations</summary> private static readonly ConcurrentDictionary<
private static readonly ConcurrentDictionary< Type, Func<object, object>
Type, Func<object, object> > shallowPropertyBasedCloners;
> shallowPropertyBasedCloners; /// <summary>Compiled cloners that perform deep clone operations</summary>
/// <summary>Compiled cloners that perform deep clone operations</summary> private static readonly ConcurrentDictionary<
private static readonly ConcurrentDictionary< Type, Func<object, object>
Type, Func<object, object> > deepPropertyBasedCloners;
> deepPropertyBasedCloners;
}
}
} // namespace Nuclex.Support.Cloning
} // namespace Nuclex.Support.Cloning
#endif // !NO_SETS
#endif // !NO_SETS

View File

@ -1,100 +1,99 @@
#region CPL License #region Apache License 2.0
/* /*
Nuclex Framework Nuclex .NET Framework
Copyright (C) 2002-2017 Nuclex Development Labs Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
This library is free software; you can redistribute it and/or Licensed under the Apache License, Version 2.0 (the "License");
modify it under the terms of the IBM Common Public License as you may not use this file except in compliance with the License.
published by the IBM Corporation; either version 1.0 of the You may obtain a copy of the License at
License, or (at your option) any later version.
http://www.apache.org/licenses/LICENSE-2.0
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of Unless required by applicable law or agreed to in writing, software
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the distributed under the License is distributed on an "AS IS" BASIS,
IBM Common Public License for more details. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
You should have received a copy of the IBM Common Public limitations under the License.
License along with this library */
*/ #endregion // Apache License 2.0
#endregion
using System;
using System;
namespace Nuclex.Support.Cloning {
namespace Nuclex.Support.Cloning {
/// <summary>Constructs new objects by cloning existing objects</summary>
/// <summary>Constructs new objects by cloning existing objects</summary> public interface ICloneFactory {
public interface ICloneFactory {
/// <summary>
/// <summary> /// Creates a shallow clone of the specified object, reusing any referenced objects
/// Creates a shallow clone of the specified object, reusing any referenced objects /// </summary>
/// </summary> /// <typeparam name="TCloned">Type of the object that will be cloned</typeparam>
/// <typeparam name="TCloned">Type of the object that will be cloned</typeparam> /// <param name="objectToClone">Object that will be cloned</param>
/// <param name="objectToClone">Object that will be cloned</param> /// <returns>A shallow clone of the provided object</returns>
/// <returns>A shallow clone of the provided object</returns> /// <remarks>
/// <remarks> /// Field-based clones are guaranteed to be complete - there will be no missed
/// Field-based clones are guaranteed to be complete - there will be no missed /// members. This type of clone is also able to clone types that do not provide
/// members. This type of clone is also able to clone types that do not provide /// a default constructor.
/// a default constructor. /// </remarks>
/// </remarks> TCloned ShallowFieldClone<TCloned>(TCloned objectToClone);
TCloned ShallowFieldClone<TCloned>(TCloned objectToClone);
/// <summary>
/// <summary> /// Creates a shallow clone of the specified object, reusing any referenced objects
/// Creates a shallow clone of the specified object, reusing any referenced objects /// </summary>
/// </summary> /// <typeparam name="TCloned">Type of the object that will be cloned</typeparam>
/// <typeparam name="TCloned">Type of the object that will be cloned</typeparam> /// <param name="objectToClone">Object that will be cloned</param>
/// <param name="objectToClone">Object that will be cloned</param> /// <returns>A shallow clone of the provided object</returns>
/// <returns>A shallow clone of the provided object</returns> /// <remarks>
/// <remarks> /// <para>
/// <para> /// A property-based clone is useful if you're using dynamically generated proxies,
/// A property-based clone is useful if you're using dynamically generated proxies, /// such as when working with entities returned by an ORM like NHibernate.
/// such as when working with entities returned by an ORM like NHibernate. /// When not using a property-based clone, internal proxy fields would be cloned
/// When not using a property-based clone, internal proxy fields would be cloned /// and might cause problems with the ORM.
/// and might cause problems with the ORM. /// </para>
/// </para> /// <para>
/// <para> /// Property-based clones require a default constructor because there's no guarantee
/// Property-based clones require a default constructor because there's no guarantee /// that all fields will are assignable through properties and starting with
/// that all fields will are assignable through properties and starting with /// an uninitialized object is likely to end up with a broken clone.
/// an uninitialized object is likely to end up with a broken clone. /// </para>
/// </para> /// </remarks>
/// </remarks> TCloned ShallowPropertyClone<TCloned>(TCloned objectToClone);
TCloned ShallowPropertyClone<TCloned>(TCloned objectToClone);
/// <summary>
/// <summary> /// Creates a deep clone of the specified object, also creating clones of all
/// Creates a deep clone of the specified object, also creating clones of all /// child objects being referenced
/// child objects being referenced /// </summary>
/// </summary> /// <typeparam name="TCloned">Type of the object that will be cloned</typeparam>
/// <typeparam name="TCloned">Type of the object that will be cloned</typeparam> /// <param name="objectToClone">Object that will be cloned</param>
/// <param name="objectToClone">Object that will be cloned</param> /// <returns>A deep clone of the provided object</returns>
/// <returns>A deep clone of the provided object</returns> /// <remarks>
/// <remarks> /// Field-based clones are guaranteed to be complete - there will be no missed
/// Field-based clones are guaranteed to be complete - there will be no missed /// members. This type of clone is also able to clone types that do not provide
/// members. This type of clone is also able to clone types that do not provide /// a default constructor.
/// a default constructor. /// </remarks>
/// </remarks> TCloned DeepFieldClone<TCloned>(TCloned objectToClone);
TCloned DeepFieldClone<TCloned>(TCloned objectToClone);
/// <summary>
/// <summary> /// Creates a deep clone of the specified object, also creating clones of all
/// Creates a deep clone of the specified object, also creating clones of all /// child objects being referenced
/// child objects being referenced /// </summary>
/// </summary> /// <typeparam name="TCloned">Type of the object that will be cloned</typeparam>
/// <typeparam name="TCloned">Type of the object that will be cloned</typeparam> /// <param name="objectToClone">Object that will be cloned</param>
/// <param name="objectToClone">Object that will be cloned</param> /// <returns>A deep clone of the provided object</returns>
/// <returns>A deep clone of the provided object</returns> /// <remarks>
/// <remarks> /// <para>
/// <para> /// A property-based clone is useful if you're using dynamically generated proxies,
/// A property-based clone is useful if you're using dynamically generated proxies, /// such as when working with entities returned by an ORM like NHibernate.
/// such as when working with entities returned by an ORM like NHibernate. /// When not using a property-based clone, internal proxy fields would be cloned
/// When not using a property-based clone, internal proxy fields would be cloned /// and might cause problems with the ORM.
/// and might cause problems with the ORM. /// </para>
/// </para> /// <para>
/// <para> /// Property-based clones require a default constructor because there's no guarantee
/// Property-based clones require a default constructor because there's no guarantee /// that all fields will are assignable through properties and starting with
/// that all fields will are assignable through properties and starting with /// an uninitialized object is likely to end up with a broken clone.
/// an uninitialized object is likely to end up with a broken clone. /// </para>
/// </para> /// </remarks>
/// </remarks> TCloned DeepPropertyClone<TCloned>(TCloned objectToClone);
TCloned DeepPropertyClone<TCloned>(TCloned objectToClone);
}
}
} // namespace Nuclex.Support.Cloning
} // namespace Nuclex.Support.Cloning

View File

@ -1,90 +1,89 @@
#region CPL License #region Apache License 2.0
/* /*
Nuclex Framework Nuclex .NET Framework
Copyright (C) 2002-2017 Nuclex Development Labs Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
This library is free software; you can redistribute it and/or Licensed under the Apache License, Version 2.0 (the "License");
modify it under the terms of the IBM Common Public License as you may not use this file except in compliance with the License.
published by the IBM Corporation; either version 1.0 of the You may obtain a copy of the License at
License, or (at your option) any later version.
http://www.apache.org/licenses/LICENSE-2.0
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of Unless required by applicable law or agreed to in writing, software
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the distributed under the License is distributed on an "AS IS" BASIS,
IBM Common Public License for more details. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
You should have received a copy of the IBM Common Public limitations under the License.
License along with this library */
*/ #endregion // Apache License 2.0
#endregion
using System;
using System;
namespace Nuclex.Support.Cloning {
namespace Nuclex.Support.Cloning {
/// <summary>Copies the state of objects</summary>
/// <summary>Copies the state of objects</summary> public interface IStateCopier {
public interface IStateCopier {
/// <summary>
/// <summary> /// Transfers the state of one object into another, creating clones of referenced objects
/// Transfers the state of one object into another, creating clones of referenced objects /// </summary>
/// </summary> /// <typeparam name="TState">Type of the object whose sate will be transferred</typeparam>
/// <typeparam name="TState">Type of the object whose sate will be transferred</typeparam> /// <param name="original">Original instance the state will be taken from</param>
/// <param name="original">Original instance the state will be taken from</param> /// <param name="target">Target instance the state will be written to</param>
/// <param name="target">Target instance the state will be written to</param> /// <param name="propertyBased">Whether to perform a property-based state copy</param>
/// <param name="propertyBased">Whether to perform a property-based state copy</param> /// <remarks>
/// <remarks> /// A property-based copy is useful if you're using dynamically generated proxies,
/// A property-based copy is useful if you're using dynamically generated proxies, /// such as when working with entities returned by an ORM like NHibernate.
/// such as when working with entities returned by an ORM like NHibernate. /// When not using a property-based copy, internal proxy fields would be copied
/// When not using a property-based copy, internal proxy fields would be copied /// and might cause problems with the ORM.
/// and might cause problems with the ORM. /// </remarks>
/// </remarks> void DeepCopyState<TState>(TState original, TState target, bool propertyBased)
void DeepCopyState<TState>(TState original, TState target, bool propertyBased) where TState : class;
where TState : class;
/// <summary>
/// <summary> /// Transfers the state of one object into another, creating clones of referenced objects
/// Transfers the state of one object into another, creating clones of referenced objects /// </summary>
/// </summary> /// <typeparam name="TState">Type of the object whose sate will be transferred</typeparam>
/// <typeparam name="TState">Type of the object whose sate will be transferred</typeparam> /// <param name="original">Original instance the state will be taken from</param>
/// <param name="original">Original instance the state will be taken from</param> /// <param name="target">Target instance the state will be written to</param>
/// <param name="target">Target instance the state will be written to</param> /// <param name="propertyBased">Whether to perform a property-based state copy</param>
/// <param name="propertyBased">Whether to perform a property-based state copy</param> /// <remarks>
/// <remarks> /// A property-based copy is useful if you're using dynamically generated proxies,
/// A property-based copy is useful if you're using dynamically generated proxies, /// such as when working with entities returned by an ORM like NHibernate.
/// such as when working with entities returned by an ORM like NHibernate. /// When not using a property-based copy, internal proxy fields would be copied
/// When not using a property-based copy, internal proxy fields would be copied /// and might cause problems with the ORM.
/// and might cause problems with the ORM. /// </remarks>
/// </remarks> void DeepCopyState<TState>(ref TState original, ref TState target, bool propertyBased)
void DeepCopyState<TState>(ref TState original, ref TState target, bool propertyBased) where TState : struct;
where TState : struct;
/// <summary>Transfers the state of one object into another</summary>
/// <summary>Transfers the state of one object into another</summary> /// <typeparam name="TState">Type of the object whose sate will be transferred</typeparam>
/// <typeparam name="TState">Type of the object whose sate will be transferred</typeparam> /// <param name="original">Original instance the state will be taken from</param>
/// <param name="original">Original instance the state will be taken from</param> /// <param name="target">Target instance the state will be written to</param>
/// <param name="target">Target instance the state will be written to</param> /// <param name="propertyBased">Whether to perform a property-based state copy</param>
/// <param name="propertyBased">Whether to perform a property-based state copy</param> /// <remarks>
/// <remarks> /// A property-based copy is useful if you're using dynamically generated proxies,
/// A property-based copy is useful if you're using dynamically generated proxies, /// such as when working with entities returned by an ORM like NHibernate.
/// such as when working with entities returned by an ORM like NHibernate. /// When not using a property-based copy, internal proxy fields would be copied
/// When not using a property-based copy, internal proxy fields would be copied /// and might cause problems with the ORM.
/// and might cause problems with the ORM. /// </remarks>
/// </remarks> void ShallowCopyState<TState>(TState original, TState target, bool propertyBased)
void ShallowCopyState<TState>(TState original, TState target, bool propertyBased) where TState : class;
where TState : class;
/// <summary>Transfers the state of one object into another</summary>
/// <summary>Transfers the state of one object into another</summary> /// <typeparam name="TState">Type of the object whose sate will be transferred</typeparam>
/// <typeparam name="TState">Type of the object whose sate will be transferred</typeparam> /// <param name="original">Original instance the state will be taken from</param>
/// <param name="original">Original instance the state will be taken from</param> /// <param name="target">Target instance the state will be written to</param>
/// <param name="target">Target instance the state will be written to</param> /// <param name="propertyBased">Whether to perform a property-based state copy</param>
/// <param name="propertyBased">Whether to perform a property-based state copy</param> /// <remarks>
/// <remarks> /// A property-based copy is useful if you're using dynamically generated proxies,
/// A property-based copy is useful if you're using dynamically generated proxies, /// such as when working with entities returned by an ORM like NHibernate.
/// such as when working with entities returned by an ORM like NHibernate. /// When not using a property-based copy, internal proxy fields would be copied
/// When not using a property-based copy, internal proxy fields would be copied /// and might cause problems with the ORM.
/// and might cause problems with the ORM. /// </remarks>
/// </remarks> void ShallowCopyState<TState>(ref TState original, ref TState target, bool propertyBased)
void ShallowCopyState<TState>(ref TState original, ref TState target, bool propertyBased) where TState : struct;
where TState : struct;
}
}
} // namespace Nuclex.Support.Cloning
} // namespace Nuclex.Support.Cloning

View File

@ -1,199 +1,198 @@
#region CPL License #region Apache License 2.0
/* /*
Nuclex Framework Nuclex .NET Framework
Copyright (C) 2002-2017 Nuclex Development Labs Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
This library is free software; you can redistribute it and/or Licensed under the Apache License, Version 2.0 (the "License");
modify it under the terms of the IBM Common Public License as you may not use this file except in compliance with the License.
published by the IBM Corporation; either version 1.0 of the You may obtain a copy of the License at
License, or (at your option) any later version.
http://www.apache.org/licenses/LICENSE-2.0
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of Unless required by applicable law or agreed to in writing, software
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the distributed under the License is distributed on an "AS IS" BASIS,
IBM Common Public License for more details. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
You should have received a copy of the IBM Common Public limitations under the License.
License along with this library */
*/ #endregion // Apache License 2.0
#endregion
#if UNITTEST
#if UNITTEST
using System;
using System; using System.Collections.Generic;
using System.Collections.Generic;
using NUnit.Framework;
using NUnit.Framework;
namespace Nuclex.Support.Cloning {
namespace Nuclex.Support.Cloning {
/// <summary>Unit Test for the reflection-based cloner</summary>
/// <summary>Unit Test for the reflection-based cloner</summary> [TestFixture]
[TestFixture] internal class ReflectionClonerTest : CloneFactoryTest {
internal class ReflectionClonerTest : CloneFactoryTest {
/// <summary>Initializes a new unit test suite for the reflection cloner</summary>
/// <summary>Initializes a new unit test suite for the reflection cloner</summary> public ReflectionClonerTest() {
public ReflectionClonerTest() { this.cloneFactory = new ReflectionCloner();
this.cloneFactory = new ReflectionCloner(); }
}
/// <summary>Verifies that cloning a null object simply returns null</summary>
/// <summary>Verifies that cloning a null object simply returns null</summary> [Test]
[Test] public void CloningNullYieldsNull() {
public void CloningNullYieldsNull() { Assert.IsNull(this.cloneFactory.DeepFieldClone<object>(null));
Assert.IsNull(this.cloneFactory.DeepFieldClone<object>(null)); Assert.IsNull(this.cloneFactory.DeepPropertyClone<object>(null));
Assert.IsNull(this.cloneFactory.DeepPropertyClone<object>(null)); Assert.IsNull(this.cloneFactory.ShallowFieldClone<object>(null));
Assert.IsNull(this.cloneFactory.ShallowFieldClone<object>(null)); Assert.IsNull(this.cloneFactory.ShallowPropertyClone<object>(null));
Assert.IsNull(this.cloneFactory.ShallowPropertyClone<object>(null)); }
}
/// <summary>
/// <summary> /// Verifies that clones of objects whose class doesn't possess a default constructor
/// Verifies that clones of objects whose class doesn't possess a default constructor /// can be made
/// can be made /// </summary>
/// </summary> [Test]
[Test] public void ClassWithoutDefaultConstructorCanBeCloned() {
public void ClassWithoutDefaultConstructorCanBeCloned() { var original = new ClassWithoutDefaultConstructor(1234);
var original = new ClassWithoutDefaultConstructor(1234); ClassWithoutDefaultConstructor clone = this.cloneFactory.DeepFieldClone(original);
ClassWithoutDefaultConstructor clone = this.cloneFactory.DeepFieldClone(original);
Assert.AreNotSame(original, clone);
Assert.AreNotSame(original, clone); Assert.AreEqual(original.Dummy, clone.Dummy);
Assert.AreEqual(original.Dummy, clone.Dummy); }
}
/// <summary>Verifies that clones of primitive types can be created</summary>
/// <summary>Verifies that clones of primitive types can be created</summary> [Test]
[Test] public void PrimitiveTypesCanBeCloned() {
public void PrimitiveTypesCanBeCloned() { int original = 12345;
int original = 12345; int clone = this.cloneFactory.ShallowFieldClone(original);
int clone = this.cloneFactory.ShallowFieldClone(original); Assert.AreEqual(original, clone);
Assert.AreEqual(original, clone); }
}
/// <summary>Verifies that shallow clones of arrays can be made</summary>
/// <summary>Verifies that shallow clones of arrays can be made</summary> [Test]
[Test] public void ShallowClonesOfArraysCanBeMade() {
public void ShallowClonesOfArraysCanBeMade() { var original = new TestReferenceType[] {
var original = new TestReferenceType[] { new TestReferenceType() { TestField = 123, TestProperty = 456 }
new TestReferenceType() { TestField = 123, TestProperty = 456 } };
}; TestReferenceType[] clone = this.cloneFactory.ShallowFieldClone(original);
TestReferenceType[] clone = this.cloneFactory.ShallowFieldClone(original);
Assert.AreSame(original[0], clone[0]);
Assert.AreSame(original[0], clone[0]); }
}
/// <summary>Verifies that deep clones of arrays can be made</summary>
/// <summary>Verifies that deep clones of arrays can be made</summary> [Test]
[Test] public void DeepClonesOfArraysCanBeMade() {
public void DeepClonesOfArraysCanBeMade() { var original = new TestReferenceType[] {
var original = new TestReferenceType[] { new TestReferenceType() { TestField = 123, TestProperty = 456 }
new TestReferenceType() { TestField = 123, TestProperty = 456 } };
}; TestReferenceType[] clone = this.cloneFactory.DeepFieldClone(original);
TestReferenceType[] clone = this.cloneFactory.DeepFieldClone(original);
Assert.AreNotSame(original[0], clone[0]);
Assert.AreNotSame(original[0], clone[0]); Assert.AreEqual(original[0].TestField, clone[0].TestField);
Assert.AreEqual(original[0].TestField, clone[0].TestField); Assert.AreEqual(original[0].TestProperty, clone[0].TestProperty);
Assert.AreEqual(original[0].TestProperty, clone[0].TestProperty); }
}
/// <summary>Verifies that deep clones of a generic list can be made</summary>
/// <summary>Verifies that deep clones of a generic list can be made</summary> [Test]
[Test] public void GenericListsCanBeCloned() {
public void GenericListsCanBeCloned() { var original = new List<int>(new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 });
var original = new List<int>(new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 }); List<int> clone = this.cloneFactory.DeepFieldClone(original);
List<int> clone = this.cloneFactory.DeepFieldClone(original);
CollectionAssert.AreEqual(original, clone);
CollectionAssert.AreEqual(original, clone); }
}
/// <summary>Verifies that deep clones of a generic dictionary can be made</summary>
/// <summary>Verifies that deep clones of a generic dictionary can be made</summary> [Test]
[Test] public void GenericDictionariesCanBeCloned() {
public void GenericDictionariesCanBeCloned() { var original = new Dictionary<int, string>();
var original = new Dictionary<int, string>(); original.Add(1, "one");
original.Add(1, "one"); Dictionary<int, string> clone = this.cloneFactory.DeepFieldClone(original);
Dictionary<int, string> clone = this.cloneFactory.DeepFieldClone(original);
Assert.AreEqual("one", clone[1]);
Assert.AreEqual("one", clone[1]); }
}
/// <summary>
/// <summary> /// Verifies that a field-based shallow clone of a value type can be performed
/// Verifies that a field-based shallow clone of a value type can be performed /// </summary>
/// </summary> [Test]
[Test] public void ShallowFieldBasedClonesOfValueTypesCanBeMade() {
public void ShallowFieldBasedClonesOfValueTypesCanBeMade() { HierarchicalValueType original = CreateValueType();
HierarchicalValueType original = CreateValueType(); HierarchicalValueType clone = this.cloneFactory.ShallowFieldClone(original);
HierarchicalValueType clone = this.cloneFactory.ShallowFieldClone(original); VerifyClone(ref original, ref clone, isDeepClone: false, isPropertyBasedClone: false);
VerifyClone(ref original, ref clone, isDeepClone: false, isPropertyBasedClone: false); }
}
/// <summary>
/// <summary> /// Verifies that a field-based shallow clone of a reference type can be performed
/// Verifies that a field-based shallow clone of a reference type can be performed /// </summary>
/// </summary> [Test]
[Test] public void ShallowFieldBasedClonesOfReferenceTypesCanBeMade() {
public void ShallowFieldBasedClonesOfReferenceTypesCanBeMade() { HierarchicalReferenceType original = CreateReferenceType();
HierarchicalReferenceType original = CreateReferenceType(); HierarchicalReferenceType clone = this.cloneFactory.ShallowFieldClone(original);
HierarchicalReferenceType clone = this.cloneFactory.ShallowFieldClone(original); VerifyClone(original, clone, isDeepClone: false, isPropertyBasedClone: false);
VerifyClone(original, clone, isDeepClone: false, isPropertyBasedClone: false); }
}
/// <summary>
/// <summary> /// Verifies that a field-based deep clone of a value type can be performed
/// Verifies that a field-based deep clone of a value type can be performed /// </summary>
/// </summary> [Test]
[Test] public void DeepFieldBasedClonesOfValueTypesCanBeMade() {
public void DeepFieldBasedClonesOfValueTypesCanBeMade() { HierarchicalValueType original = CreateValueType();
HierarchicalValueType original = CreateValueType(); HierarchicalValueType clone = this.cloneFactory.DeepFieldClone(original);
HierarchicalValueType clone = this.cloneFactory.DeepFieldClone(original); VerifyClone(ref original, ref clone, isDeepClone: true, isPropertyBasedClone: false);
VerifyClone(ref original, ref clone, isDeepClone: true, isPropertyBasedClone: false); }
}
/// <summary>
/// <summary> /// Verifies that a field-based deep clone of a reference type can be performed
/// Verifies that a field-based deep clone of a reference type can be performed /// </summary>
/// </summary> [Test]
[Test] public void DeepFieldBasedClonesOfReferenceTypesCanBeMade() {
public void DeepFieldBasedClonesOfReferenceTypesCanBeMade() { HierarchicalReferenceType original = CreateReferenceType();
HierarchicalReferenceType original = CreateReferenceType(); HierarchicalReferenceType clone = this.cloneFactory.DeepFieldClone(original);
HierarchicalReferenceType clone = this.cloneFactory.DeepFieldClone(original); VerifyClone(original, clone, isDeepClone: true, isPropertyBasedClone: false);
VerifyClone(original, clone, isDeepClone: true, isPropertyBasedClone: false); }
}
/// <summary>
/// <summary> /// Verifies that a property-based shallow clone of a value type can be performed
/// Verifies that a property-based shallow clone of a value type can be performed /// </summary>
/// </summary> [Test]
[Test] public void ShallowPropertyBasedClonesOfValueTypesCanBeMade() {
public void ShallowPropertyBasedClonesOfValueTypesCanBeMade() { HierarchicalValueType original = CreateValueType();
HierarchicalValueType original = CreateValueType(); HierarchicalValueType clone = this.cloneFactory.ShallowPropertyClone(original);
HierarchicalValueType clone = this.cloneFactory.ShallowPropertyClone(original); VerifyClone(ref original, ref clone, isDeepClone: false, isPropertyBasedClone: true);
VerifyClone(ref original, ref clone, isDeepClone: false, isPropertyBasedClone: true); }
}
/// <summary>
/// <summary> /// Verifies that a property-based shallow clone of a reference type can be performed
/// Verifies that a property-based shallow clone of a reference type can be performed /// </summary>
/// </summary> [Test]
[Test] public void ShallowPropertyBasedClonesOfReferenceTypesCanBeMade() {
public void ShallowPropertyBasedClonesOfReferenceTypesCanBeMade() { HierarchicalReferenceType original = CreateReferenceType();
HierarchicalReferenceType original = CreateReferenceType(); HierarchicalReferenceType clone = this.cloneFactory.ShallowPropertyClone(original);
HierarchicalReferenceType clone = this.cloneFactory.ShallowPropertyClone(original); VerifyClone(original, clone, isDeepClone: false, isPropertyBasedClone: true);
VerifyClone(original, clone, isDeepClone: false, isPropertyBasedClone: true); }
}
/// <summary>
/// <summary> /// Verifies that a property-based deep clone of a value type can be performed
/// Verifies that a property-based deep clone of a value type can be performed /// </summary>
/// </summary> [Test]
[Test] public void DeepPropertyBasedClonesOfValueTypesCanBeMade() {
public void DeepPropertyBasedClonesOfValueTypesCanBeMade() { HierarchicalValueType original = CreateValueType();
HierarchicalValueType original = CreateValueType(); HierarchicalValueType clone = this.cloneFactory.DeepPropertyClone(original);
HierarchicalValueType clone = this.cloneFactory.DeepPropertyClone(original); VerifyClone(ref original, ref clone, isDeepClone: true, isPropertyBasedClone: true);
VerifyClone(ref original, ref clone, isDeepClone: true, isPropertyBasedClone: true); }
}
/// <summary>
/// <summary> /// Verifies that a property-based deep clone of a reference type can be performed
/// Verifies that a property-based deep clone of a reference type can be performed /// </summary>
/// </summary> [Test]
[Test] public void DeepPropertyBasedClonesOfReferenceTypesCanBeMade() {
public void DeepPropertyBasedClonesOfReferenceTypesCanBeMade() { HierarchicalReferenceType original = CreateReferenceType();
HierarchicalReferenceType original = CreateReferenceType(); HierarchicalReferenceType clone = this.cloneFactory.DeepPropertyClone(original);
HierarchicalReferenceType clone = this.cloneFactory.DeepPropertyClone(original); VerifyClone(original, clone, isDeepClone: true, isPropertyBasedClone: true);
VerifyClone(original, clone, isDeepClone: true, isPropertyBasedClone: true); }
}
/// <summary>Clone factory being tested</summary>
/// <summary>Clone factory being tested</summary> private ICloneFactory cloneFactory;
private ICloneFactory cloneFactory;
}
}
} // namespace Nuclex.Support.Cloning
} // namespace Nuclex.Support.Cloning
#endif // UNITTEST
#endif // UNITTEST

View File

@ -1,451 +1,450 @@
#region CPL License #region Apache License 2.0
/* /*
Nuclex Framework Nuclex .NET Framework
Copyright (C) 2002-2017 Nuclex Development Labs Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
This library is free software; you can redistribute it and/or Licensed under the Apache License, Version 2.0 (the "License");
modify it under the terms of the IBM Common Public License as you may not use this file except in compliance with the License.
published by the IBM Corporation; either version 1.0 of the You may obtain a copy of the License at
License, or (at your option) any later version.
http://www.apache.org/licenses/LICENSE-2.0
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of Unless required by applicable law or agreed to in writing, software
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the distributed under the License is distributed on an "AS IS" BASIS,
IBM Common Public License for more details. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
You should have received a copy of the IBM Common Public limitations under the License.
License along with this library */
*/ #endregion // Apache License 2.0
#endregion
using System;
using System; using System.Reflection;
using System.Reflection; using System.Runtime.Serialization;
using System.Runtime.Serialization;
namespace Nuclex.Support.Cloning {
namespace Nuclex.Support.Cloning {
/// <summary>Clones objects using reflection</summary>
/// <summary>Clones objects using reflection</summary> /// <remarks>
/// <remarks> /// <para>
/// <para> /// This type of cloning is a lot faster than cloning by serialization and
/// This type of cloning is a lot faster than cloning by serialization and /// incurs no set-up cost, but requires cloned types to provide a default
/// incurs no set-up cost, but requires cloned types to provide a default /// constructor in order to work.
/// constructor in order to work. /// </para>
/// </para> /// </remarks>
/// </remarks> public class ReflectionCloner : ICloneFactory {
public class ReflectionCloner : ICloneFactory {
/// <summary>
/// <summary> /// Creates a shallow clone of the specified object, reusing any referenced objects
/// Creates a shallow clone of the specified object, reusing any referenced objects /// </summary>
/// </summary> /// <typeparam name="TCloned">Type of the object that will be cloned</typeparam>
/// <typeparam name="TCloned">Type of the object that will be cloned</typeparam> /// <param name="objectToClone">Object that will be cloned</param>
/// <param name="objectToClone">Object that will be cloned</param> /// <returns>A shallow clone of the provided object</returns>
/// <returns>A shallow clone of the provided object</returns> public static TCloned ShallowFieldClone<TCloned>(TCloned objectToClone) {
public static TCloned ShallowFieldClone<TCloned>(TCloned objectToClone) { Type originalType = objectToClone.GetType();
Type originalType = objectToClone.GetType(); if(originalType.IsPrimitive || (originalType == typeof(string))) {
if(originalType.IsPrimitive || (originalType == typeof(string))) { return objectToClone; // Being value types, primitives are copied by default
return objectToClone; // Being value types, primitives are copied by default } else if(originalType.IsArray) {
} else if(originalType.IsArray) { return (TCloned)shallowCloneArray(objectToClone);
return (TCloned)shallowCloneArray(objectToClone); } else if(originalType.IsValueType) {
} else if(originalType.IsValueType) { return objectToClone; // Value types can be copied directly
return objectToClone; // Value types can be copied directly } else {
} else { return (TCloned)shallowCloneComplexFieldBased(objectToClone);
return (TCloned)shallowCloneComplexFieldBased(objectToClone); }
} }
}
/// <summary>
/// <summary> /// Creates a shallow clone of the specified object, reusing any referenced objects
/// Creates a shallow clone of the specified object, reusing any referenced objects /// </summary>
/// </summary> /// <typeparam name="TCloned">Type of the object that will be cloned</typeparam>
/// <typeparam name="TCloned">Type of the object that will be cloned</typeparam> /// <param name="objectToClone">Object that will be cloned</param>
/// <param name="objectToClone">Object that will be cloned</param> /// <returns>A shallow clone of the provided object</returns>
/// <returns>A shallow clone of the provided object</returns> public static TCloned ShallowPropertyClone<TCloned>(TCloned objectToClone) {
public static TCloned ShallowPropertyClone<TCloned>(TCloned objectToClone) { Type originalType = objectToClone.GetType();
Type originalType = objectToClone.GetType(); if(originalType.IsPrimitive || (originalType == typeof(string))) {
if(originalType.IsPrimitive || (originalType == typeof(string))) { return objectToClone; // Being value types, primitives are copied by default
return objectToClone; // Being value types, primitives are copied by default } else if(originalType.IsArray) {
} else if(originalType.IsArray) { return (TCloned)shallowCloneArray(objectToClone);
return (TCloned)shallowCloneArray(objectToClone); } else if(originalType.IsValueType) {
} else if(originalType.IsValueType) { return (TCloned)shallowCloneComplexPropertyBased(objectToClone);
return (TCloned)shallowCloneComplexPropertyBased(objectToClone); } else {
} else { return (TCloned)shallowCloneComplexPropertyBased(objectToClone);
return (TCloned)shallowCloneComplexPropertyBased(objectToClone); }
} }
}
/// <summary>
/// <summary> /// Creates a deep clone of the specified object, also creating clones of all
/// Creates a deep clone of the specified object, also creating clones of all /// child objects being referenced
/// child objects being referenced /// </summary>
/// </summary> /// <typeparam name="TCloned">Type of the object that will be cloned</typeparam>
/// <typeparam name="TCloned">Type of the object that will be cloned</typeparam> /// <param name="objectToClone">Object that will be cloned</param>
/// <param name="objectToClone">Object that will be cloned</param> /// <returns>A deep clone of the provided object</returns>
/// <returns>A deep clone of the provided object</returns> public static TCloned DeepFieldClone<TCloned>(TCloned objectToClone) {
public static TCloned DeepFieldClone<TCloned>(TCloned objectToClone) { object objectToCloneAsObject = objectToClone;
object objectToCloneAsObject = objectToClone; if(objectToClone == null) {
if(objectToClone == null) { return default(TCloned);
return default(TCloned); } else {
} else { return (TCloned)deepCloneSingleFieldBased(objectToCloneAsObject);
return (TCloned)deepCloneSingleFieldBased(objectToCloneAsObject); }
} }
}
/// <summary>
/// <summary> /// Creates a deep clone of the specified object, also creating clones of all
/// Creates a deep clone of the specified object, also creating clones of all /// child objects being referenced
/// child objects being referenced /// </summary>
/// </summary> /// <typeparam name="TCloned">Type of the object that will be cloned</typeparam>
/// <typeparam name="TCloned">Type of the object that will be cloned</typeparam> /// <param name="objectToClone">Object that will be cloned</param>
/// <param name="objectToClone">Object that will be cloned</param> /// <returns>A deep clone of the provided object</returns>
/// <returns>A deep clone of the provided object</returns> public static TCloned DeepPropertyClone<TCloned>(TCloned objectToClone) {
public static TCloned DeepPropertyClone<TCloned>(TCloned objectToClone) { object objectToCloneAsObject = objectToClone;
object objectToCloneAsObject = objectToClone; if(objectToClone == null) {
if(objectToClone == null) { return default(TCloned);
return default(TCloned); } else {
} else { return (TCloned)deepCloneSinglePropertyBased(objectToCloneAsObject);
return (TCloned)deepCloneSinglePropertyBased(objectToCloneAsObject); }
} }
}
/// <summary>
/// <summary> /// Creates a shallow clone of the specified object, reusing any referenced objects
/// Creates a shallow clone of the specified object, reusing any referenced objects /// </summary>
/// </summary> /// <typeparam name="TCloned">Type of the object that will be cloned</typeparam>
/// <typeparam name="TCloned">Type of the object that will be cloned</typeparam> /// <param name="objectToClone">Object that will be cloned</param>
/// <param name="objectToClone">Object that will be cloned</param> /// <returns>A shallow clone of the provided object</returns>
/// <returns>A shallow clone of the provided object</returns> TCloned ICloneFactory.ShallowFieldClone<TCloned>(TCloned objectToClone) {
TCloned ICloneFactory.ShallowFieldClone<TCloned>(TCloned objectToClone) { if(typeof(TCloned).IsClass || typeof(TCloned).IsArray) {
if(typeof(TCloned).IsClass || typeof(TCloned).IsArray) { if(ReferenceEquals(objectToClone, null)) {
if(ReferenceEquals(objectToClone, null)) { return default(TCloned);
return default(TCloned); }
} }
} return ReflectionCloner.ShallowFieldClone<TCloned>(objectToClone);
return ReflectionCloner.ShallowFieldClone<TCloned>(objectToClone); }
}
/// <summary>
/// <summary> /// Creates a shallow clone of the specified object, reusing any referenced objects
/// Creates a shallow clone of the specified object, reusing any referenced objects /// </summary>
/// </summary> /// <typeparam name="TCloned">Type of the object that will be cloned</typeparam>
/// <typeparam name="TCloned">Type of the object that will be cloned</typeparam> /// <param name="objectToClone">Object that will be cloned</param>
/// <param name="objectToClone">Object that will be cloned</param> /// <returns>A shallow clone of the provided object</returns>
/// <returns>A shallow clone of the provided object</returns> TCloned ICloneFactory.ShallowPropertyClone<TCloned>(TCloned objectToClone) {
TCloned ICloneFactory.ShallowPropertyClone<TCloned>(TCloned objectToClone) { if(typeof(TCloned).IsClass || typeof(TCloned).IsArray) {
if(typeof(TCloned).IsClass || typeof(TCloned).IsArray) { if(ReferenceEquals(objectToClone, null)) {
if(ReferenceEquals(objectToClone, null)) { return default(TCloned);
return default(TCloned); }
} }
} return ReflectionCloner.ShallowPropertyClone<TCloned>(objectToClone);
return ReflectionCloner.ShallowPropertyClone<TCloned>(objectToClone); }
}
/// <summary>
/// <summary> /// Creates a deep clone of the specified object, also creating clones of all
/// Creates a deep clone of the specified object, also creating clones of all /// child objects being referenced
/// child objects being referenced /// </summary>
/// </summary> /// <typeparam name="TCloned">Type of the object that will be cloned</typeparam>
/// <typeparam name="TCloned">Type of the object that will be cloned</typeparam> /// <param name="objectToClone">Object that will be cloned</param>
/// <param name="objectToClone">Object that will be cloned</param> /// <returns>A deep clone of the provided object</returns>
/// <returns>A deep clone of the provided object</returns> TCloned ICloneFactory.DeepFieldClone<TCloned>(TCloned objectToClone) {
TCloned ICloneFactory.DeepFieldClone<TCloned>(TCloned objectToClone) { return ReflectionCloner.DeepFieldClone<TCloned>(objectToClone);
return ReflectionCloner.DeepFieldClone<TCloned>(objectToClone); }
}
/// <summary>
/// <summary> /// Creates a deep clone of the specified object, also creating clones of all
/// Creates a deep clone of the specified object, also creating clones of all /// child objects being referenced
/// child objects being referenced /// </summary>
/// </summary> /// <typeparam name="TCloned">Type of the object that will be cloned</typeparam>
/// <typeparam name="TCloned">Type of the object that will be cloned</typeparam> /// <param name="objectToClone">Object that will be cloned</param>
/// <param name="objectToClone">Object that will be cloned</param> /// <returns>A deep clone of the provided object</returns>
/// <returns>A deep clone of the provided object</returns> TCloned ICloneFactory.DeepPropertyClone<TCloned>(TCloned objectToClone) {
TCloned ICloneFactory.DeepPropertyClone<TCloned>(TCloned objectToClone) { return ReflectionCloner.DeepPropertyClone<TCloned>(objectToClone);
return ReflectionCloner.DeepPropertyClone<TCloned>(objectToClone); }
}
/// <summary>Clones a complex type using field-based value transfer</summary>
/// <summary>Clones a complex type using field-based value transfer</summary> /// <param name="original">Original instance that will be cloned</param>
/// <param name="original">Original instance that will be cloned</param> /// <returns>A clone of the original instance</returns>
/// <returns>A clone of the original instance</returns> private static object shallowCloneComplexFieldBased(object original) {
private static object shallowCloneComplexFieldBased(object original) { Type originalType = original.GetType();
Type originalType = original.GetType(); object clone = FormatterServices.GetUninitializedObject(originalType);
object clone = FormatterServices.GetUninitializedObject(originalType);
FieldInfo[] fieldInfos = originalType.GetFieldInfosIncludingBaseClasses(
FieldInfo[] fieldInfos = originalType.GetFieldInfosIncludingBaseClasses( BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance
BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance );
); for(int index = 0; index < fieldInfos.Length; ++index) {
for(int index = 0; index < fieldInfos.Length; ++index) { FieldInfo fieldInfo = fieldInfos[index];
FieldInfo fieldInfo = fieldInfos[index]; object originalValue = fieldInfo.GetValue(original);
object originalValue = fieldInfo.GetValue(original); if(originalValue != null) {
if(originalValue != null) { // Everything's just directly assigned in a shallow clone
// Everything's just directly assigned in a shallow clone fieldInfo.SetValue(clone, originalValue);
fieldInfo.SetValue(clone, originalValue); }
} }
}
return clone;
return clone; }
}
/// <summary>Clones a complex type using property-based value transfer</summary>
/// <summary>Clones a complex type using property-based value transfer</summary> /// <param name="original">Original instance that will be cloned</param>
/// <param name="original">Original instance that will be cloned</param> /// <returns>A clone of the original instance</returns>
/// <returns>A clone of the original instance</returns> private static object shallowCloneComplexPropertyBased(object original) {
private static object shallowCloneComplexPropertyBased(object original) { Type originalType = original.GetType();
Type originalType = original.GetType(); object clone = Activator.CreateInstance(originalType);
object clone = Activator.CreateInstance(originalType);
PropertyInfo[] propertyInfos = originalType.GetProperties(
PropertyInfo[] propertyInfos = originalType.GetProperties( BindingFlags.Public | BindingFlags.NonPublic |
BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.FlattenHierarchy
BindingFlags.Instance | BindingFlags.FlattenHierarchy );
); for(int index = 0; index < propertyInfos.Length; ++index) {
for(int index = 0; index < propertyInfos.Length; ++index) { PropertyInfo propertyInfo = propertyInfos[index];
PropertyInfo propertyInfo = propertyInfos[index]; if(propertyInfo.CanRead && propertyInfo.CanWrite) {
if(propertyInfo.CanRead && propertyInfo.CanWrite) { Type propertyType = propertyInfo.PropertyType;
Type propertyType = propertyInfo.PropertyType; object originalValue = propertyInfo.GetValue(original, null);
object originalValue = propertyInfo.GetValue(original, null); if(originalValue != null) {
if(originalValue != null) { if(propertyType.IsPrimitive || (propertyType == typeof(string))) {
if(propertyType.IsPrimitive || (propertyType == typeof(string))) { // Primitive types can be assigned directly
// Primitive types can be assigned directly propertyInfo.SetValue(clone, originalValue, null);
propertyInfo.SetValue(clone, originalValue, null); } else if(propertyType.IsValueType) {
} else if(propertyType.IsValueType) { // Value types are seen as part of the original type and are thus recursed into
// Value types are seen as part of the original type and are thus recursed into propertyInfo.SetValue(clone, shallowCloneComplexPropertyBased(originalValue), null);
propertyInfo.SetValue(clone, shallowCloneComplexPropertyBased(originalValue), null); } else if(propertyType.IsArray) { // Arrays are assigned directly in a shallow clone
} else if(propertyType.IsArray) { // Arrays are assigned directly in a shallow clone propertyInfo.SetValue(clone, originalValue, null);
propertyInfo.SetValue(clone, originalValue, null); } else { // Complex types are directly assigned without creating a copy
} else { // Complex types are directly assigned without creating a copy propertyInfo.SetValue(clone, originalValue, null);
propertyInfo.SetValue(clone, originalValue, null); }
} }
} }
} }
}
return clone;
return clone; }
}
/// <summary>Clones an array using field-based value transfer</summary>
/// <summary>Clones an array using field-based value transfer</summary> /// <param name="original">Original array that will be cloned</param>
/// <param name="original">Original array that will be cloned</param> /// <returns>A clone of the original array</returns>
/// <returns>A clone of the original array</returns> private static object shallowCloneArray(object original) {
private static object shallowCloneArray(object original) { return ((Array)original).Clone();
return ((Array)original).Clone(); }
}
/// <summary>Copies a single object using field-based value transfer</summary>
/// <summary>Copies a single object using field-based value transfer</summary> /// <param name="original">Original object that will be cloned</param>
/// <param name="original">Original object that will be cloned</param> /// <returns>A clone of the original object</returns>
/// <returns>A clone of the original object</returns> private static object deepCloneSingleFieldBased(object original) {
private static object deepCloneSingleFieldBased(object original) { Type originalType = original.GetType();
Type originalType = original.GetType(); if(originalType.IsPrimitive || (originalType == typeof(string))) {
if(originalType.IsPrimitive || (originalType == typeof(string))) { return original; // Creates another box, does not reference boxed primitive
return original; // Creates another box, does not reference boxed primitive } else if(originalType.IsArray) {
} else if(originalType.IsArray) { return deepCloneArrayFieldBased((Array)original, originalType.GetElementType());
return deepCloneArrayFieldBased((Array)original, originalType.GetElementType()); } else {
} else { return deepCloneComplexFieldBased(original);
return deepCloneComplexFieldBased(original); }
} }
}
/// <summary>Clones a complex type using field-based value transfer</summary>
/// <summary>Clones a complex type using field-based value transfer</summary> /// <param name="original">Original instance that will be cloned</param>
/// <param name="original">Original instance that will be cloned</param> /// <returns>A clone of the original instance</returns>
/// <returns>A clone of the original instance</returns> private static object deepCloneComplexFieldBased(object original) {
private static object deepCloneComplexFieldBased(object original) { Type originalType = original.GetType();
Type originalType = original.GetType(); object clone = FormatterServices.GetUninitializedObject(originalType);
object clone = FormatterServices.GetUninitializedObject(originalType);
FieldInfo[] fieldInfos = originalType.GetFieldInfosIncludingBaseClasses(
FieldInfo[] fieldInfos = originalType.GetFieldInfosIncludingBaseClasses( BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance
BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance );
); for(int index = 0; index < fieldInfos.Length; ++index) {
for(int index = 0; index < fieldInfos.Length; ++index) { FieldInfo fieldInfo = fieldInfos[index];
FieldInfo fieldInfo = fieldInfos[index]; Type fieldType = fieldInfo.FieldType;
Type fieldType = fieldInfo.FieldType; object originalValue = fieldInfo.GetValue(original);
object originalValue = fieldInfo.GetValue(original); if(originalValue != null) {
if(originalValue != null) { // Primitive types can be assigned directly
// Primitive types can be assigned directly if(fieldType.IsPrimitive || (fieldType == typeof(string))) {
if(fieldType.IsPrimitive || (fieldType == typeof(string))) { fieldInfo.SetValue(clone, originalValue);
fieldInfo.SetValue(clone, originalValue); } else if(fieldType.IsArray) { // Arrays need to be cloned element-by-element
} else if(fieldType.IsArray) { // Arrays need to be cloned element-by-element fieldInfo.SetValue(
fieldInfo.SetValue( clone,
clone, deepCloneArrayFieldBased((Array)originalValue, fieldType.GetElementType())
deepCloneArrayFieldBased((Array)originalValue, fieldType.GetElementType()) );
); } else { // Complex types need to be cloned member-by-member
} else { // Complex types need to be cloned member-by-member fieldInfo.SetValue(clone, deepCloneSingleFieldBased(originalValue));
fieldInfo.SetValue(clone, deepCloneSingleFieldBased(originalValue)); }
} }
} }
}
return clone;
return clone; }
}
/// <summary>Clones an array using field-based value transfer</summary>
/// <summary>Clones an array using field-based value transfer</summary> /// <param name="original">Original array that will be cloned</param>
/// <param name="original">Original array that will be cloned</param> /// <param name="elementType">Type of elements the original array contains</param>
/// <param name="elementType">Type of elements the original array contains</param> /// <returns>A clone of the original array</returns>
/// <returns>A clone of the original array</returns> private static object deepCloneArrayFieldBased(Array original, Type elementType) {
private static object deepCloneArrayFieldBased(Array original, Type elementType) { if(elementType.IsPrimitive || (elementType == typeof(string))) {
if(elementType.IsPrimitive || (elementType == typeof(string))) { return original.Clone();
return original.Clone(); }
}
int dimensionCount = original.Rank;
int dimensionCount = original.Rank;
// Find out the length of each of the array's dimensions, also calculate how
// Find out the length of each of the array's dimensions, also calculate how // many elements there are in the array in total.
// many elements there are in the array in total. var lengths = new int[dimensionCount];
var lengths = new int[dimensionCount]; int totalElementCount = 0;
int totalElementCount = 0; for(int index = 0; index < dimensionCount; ++index) {
for(int index = 0; index < dimensionCount; ++index) { lengths[index] = original.GetLength(index);
lengths[index] = original.GetLength(index); if(index == 0) {
if(index == 0) { totalElementCount = lengths[index];
totalElementCount = lengths[index]; } else {
} else { totalElementCount *= lengths[index];
totalElementCount *= lengths[index]; }
} }
}
// Knowing the number of dimensions and the length of each dimension, we can
// Knowing the number of dimensions and the length of each dimension, we can // create another array of the exact same sizes.
// create another array of the exact same sizes. Array clone = Array.CreateInstance(elementType, lengths);
Array clone = Array.CreateInstance(elementType, lengths);
// If this is a one-dimensional array (most common type), do an optimized copy
// If this is a one-dimensional array (most common type), do an optimized copy // directly specifying the indices
// directly specifying the indices if(dimensionCount == 1) {
if(dimensionCount == 1) {
// Clone each element of the array directly
// Clone each element of the array directly for(int index = 0; index < totalElementCount; ++index) {
for(int index = 0; index < totalElementCount; ++index) { object originalElement = original.GetValue(index);
object originalElement = original.GetValue(index); if(originalElement != null) {
if(originalElement != null) { clone.SetValue(deepCloneSingleFieldBased(originalElement), index);
clone.SetValue(deepCloneSingleFieldBased(originalElement), index); }
} }
}
} else { // Otherwise use the generic code for multi-dimensional arrays
} else { // Otherwise use the generic code for multi-dimensional arrays
var indices = new int[dimensionCount];
var indices = new int[dimensionCount]; for(int index = 0; index < totalElementCount; ++index) {
for(int index = 0; index < totalElementCount; ++index) {
// Determine the index for each of the array's dimensions
// Determine the index for each of the array's dimensions int elementIndex = index;
int elementIndex = index; for(int dimensionIndex = dimensionCount - 1; dimensionIndex >= 0; --dimensionIndex) {
for(int dimensionIndex = dimensionCount - 1; dimensionIndex >= 0; --dimensionIndex) { indices[dimensionIndex] = elementIndex % lengths[dimensionIndex];
indices[dimensionIndex] = elementIndex % lengths[dimensionIndex]; elementIndex /= lengths[dimensionIndex];
elementIndex /= lengths[dimensionIndex]; }
}
// Clone the current array element
// Clone the current array element object originalElement = original.GetValue(indices);
object originalElement = original.GetValue(indices); if(originalElement != null) {
if(originalElement != null) { clone.SetValue(deepCloneSingleFieldBased(originalElement), indices);
clone.SetValue(deepCloneSingleFieldBased(originalElement), indices); }
}
}
}
}
}
return clone;
return clone; }
}
/// <summary>Copies a single object using property-based value transfer</summary>
/// <summary>Copies a single object using property-based value transfer</summary> /// <param name="original">Original object that will be cloned</param>
/// <param name="original">Original object that will be cloned</param> /// <returns>A clone of the original object</returns>
/// <returns>A clone of the original object</returns> private static object deepCloneSinglePropertyBased(object original) {
private static object deepCloneSinglePropertyBased(object original) { Type originalType = original.GetType();
Type originalType = original.GetType(); if(originalType.IsPrimitive || (originalType == typeof(string))) {
if(originalType.IsPrimitive || (originalType == typeof(string))) { return original; // Creates another box, does not reference boxed primitive
return original; // Creates another box, does not reference boxed primitive } else if(originalType.IsArray) {
} else if(originalType.IsArray) { return deepCloneArrayPropertyBased((Array)original, originalType.GetElementType());
return deepCloneArrayPropertyBased((Array)original, originalType.GetElementType()); } else {
} else { return deepCloneComplexPropertyBased(original);
return deepCloneComplexPropertyBased(original); }
} }
}
/// <summary>Clones a complex type using property-based value transfer</summary>
/// <summary>Clones a complex type using property-based value transfer</summary> /// <param name="original">Original instance that will be cloned</param>
/// <param name="original">Original instance that will be cloned</param> /// <returns>A clone of the original instance</returns>
/// <returns>A clone of the original instance</returns> private static object deepCloneComplexPropertyBased(object original) {
private static object deepCloneComplexPropertyBased(object original) { Type originalType = original.GetType();
Type originalType = original.GetType(); object clone = Activator.CreateInstance(originalType);
object clone = Activator.CreateInstance(originalType);
PropertyInfo[] propertyInfos = originalType.GetProperties(
PropertyInfo[] propertyInfos = originalType.GetProperties( BindingFlags.Public | BindingFlags.NonPublic |
BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.FlattenHierarchy
BindingFlags.Instance | BindingFlags.FlattenHierarchy );
); for(int index = 0; index < propertyInfos.Length; ++index) {
for(int index = 0; index < propertyInfos.Length; ++index) { PropertyInfo propertyInfo = propertyInfos[index];
PropertyInfo propertyInfo = propertyInfos[index]; if(propertyInfo.CanRead && propertyInfo.CanWrite) {
if(propertyInfo.CanRead && propertyInfo.CanWrite) { Type propertyType = propertyInfo.PropertyType;
Type propertyType = propertyInfo.PropertyType; object originalValue = propertyInfo.GetValue(original, null);
object originalValue = propertyInfo.GetValue(original, null); if(originalValue != null) {
if(originalValue != null) { if(propertyType.IsPrimitive || (propertyType == typeof(string))) {
if(propertyType.IsPrimitive || (propertyType == typeof(string))) { // Primitive types can be assigned directly
// Primitive types can be assigned directly propertyInfo.SetValue(clone, originalValue, null);
propertyInfo.SetValue(clone, originalValue, null); } else if(propertyType.IsArray) { // Arrays need to be cloned element-by-element
} else if(propertyType.IsArray) { // Arrays need to be cloned element-by-element propertyInfo.SetValue(
propertyInfo.SetValue( clone,
clone, deepCloneArrayPropertyBased((Array)originalValue, propertyType.GetElementType()),
deepCloneArrayPropertyBased((Array)originalValue, propertyType.GetElementType()), null
null );
); } else { // Complex types need to be cloned member-by-member
} else { // Complex types need to be cloned member-by-member propertyInfo.SetValue(clone, deepCloneSinglePropertyBased(originalValue), null);
propertyInfo.SetValue(clone, deepCloneSinglePropertyBased(originalValue), null); }
} }
} }
} }
}
return clone;
return clone; }
}
/// <summary>Clones an array using property-based value transfer</summary>
/// <summary>Clones an array using property-based value transfer</summary> /// <param name="original">Original array that will be cloned</param>
/// <param name="original">Original array that will be cloned</param> /// <param name="elementType">Type of elements the original array contains</param>
/// <param name="elementType">Type of elements the original array contains</param> /// <returns>A clone of the original array</returns>
/// <returns>A clone of the original array</returns> private static object deepCloneArrayPropertyBased(Array original, Type elementType) {
private static object deepCloneArrayPropertyBased(Array original, Type elementType) { if(elementType.IsPrimitive || (elementType == typeof(string))) {
if(elementType.IsPrimitive || (elementType == typeof(string))) { return original.Clone();
return original.Clone(); }
}
int dimensionCount = original.Rank;
int dimensionCount = original.Rank;
// Find out the length of each of the array's dimensions, also calculate how
// Find out the length of each of the array's dimensions, also calculate how // many elements there are in the array in total.
// many elements there are in the array in total. var lengths = new int[dimensionCount];
var lengths = new int[dimensionCount]; int totalElementCount = 0;
int totalElementCount = 0; for(int index = 0; index < dimensionCount; ++index) {
for(int index = 0; index < dimensionCount; ++index) { lengths[index] = original.GetLength(index);
lengths[index] = original.GetLength(index); if(index == 0) {
if(index == 0) { totalElementCount = lengths[index];
totalElementCount = lengths[index]; } else {
} else { totalElementCount *= lengths[index];
totalElementCount *= lengths[index]; }
} }
}
// Knowing the number of dimensions and the length of each dimension, we can
// Knowing the number of dimensions and the length of each dimension, we can // create another array of the exact same sizes.
// create another array of the exact same sizes. Array clone = Array.CreateInstance(elementType, lengths);
Array clone = Array.CreateInstance(elementType, lengths);
// If this is a one-dimensional array (most common type), do an optimized copy
// If this is a one-dimensional array (most common type), do an optimized copy // directly specifying the indices
// directly specifying the indices if(dimensionCount == 1) {
if(dimensionCount == 1) {
// Clone each element of the array directly
// Clone each element of the array directly for(int index = 0; index < totalElementCount; ++index) {
for(int index = 0; index < totalElementCount; ++index) { object originalElement = original.GetValue(index);
object originalElement = original.GetValue(index); if(originalElement != null) {
if(originalElement != null) { clone.SetValue(deepCloneSinglePropertyBased(originalElement), index);
clone.SetValue(deepCloneSinglePropertyBased(originalElement), index); }
} }
}
} else { // Otherwise use the generic code for multi-dimensional arrays
} else { // Otherwise use the generic code for multi-dimensional arrays
var indices = new int[dimensionCount];
var indices = new int[dimensionCount]; for(int index = 0; index < totalElementCount; ++index) {
for(int index = 0; index < totalElementCount; ++index) {
// Determine the index for each of the array's dimensions
// Determine the index for each of the array's dimensions int elementIndex = index;
int elementIndex = index; for(int dimensionIndex = dimensionCount - 1; dimensionIndex >= 0; --dimensionIndex) {
for(int dimensionIndex = dimensionCount - 1; dimensionIndex >= 0; --dimensionIndex) { indices[dimensionIndex] = elementIndex % lengths[dimensionIndex];
indices[dimensionIndex] = elementIndex % lengths[dimensionIndex]; elementIndex /= lengths[dimensionIndex];
elementIndex /= lengths[dimensionIndex]; }
}
// Clone the current array element
// Clone the current array element object originalElement = original.GetValue(indices);
object originalElement = original.GetValue(indices); if(originalElement != null) {
if(originalElement != null) { clone.SetValue(deepCloneSinglePropertyBased(originalElement), indices);
clone.SetValue(deepCloneSinglePropertyBased(originalElement), indices); }
}
}
}
}
}
return clone;
return clone; }
}
}
}
} // namespace Nuclex.Support.Cloning
} // namespace Nuclex.Support.Cloning

View File

@ -1,146 +1,145 @@
#region CPL License #region Apache License 2.0
/* /*
Nuclex Framework Nuclex .NET Framework
Copyright (C) 2002-2017 Nuclex Development Labs Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
This library is free software; you can redistribute it and/or Licensed under the Apache License, Version 2.0 (the "License");
modify it under the terms of the IBM Common Public License as you may not use this file except in compliance with the License.
published by the IBM Corporation; either version 1.0 of the You may obtain a copy of the License at
License, or (at your option) any later version.
http://www.apache.org/licenses/LICENSE-2.0
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of Unless required by applicable law or agreed to in writing, software
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the distributed under the License is distributed on an "AS IS" BASIS,
IBM Common Public License for more details. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
You should have received a copy of the IBM Common Public limitations under the License.
License along with this library */
*/ #endregion // Apache License 2.0
#endregion
#if UNITTEST
#if UNITTEST
using System;
using System; using System.Collections.Generic;
using System.Collections.Generic;
using NUnit.Framework;
using NUnit.Framework;
namespace Nuclex.Support.Cloning {
namespace Nuclex.Support.Cloning {
/// <summary>Unit Test for the binary serializer-based cloner</summary>
/// <summary>Unit Test for the binary serializer-based cloner</summary> [TestFixture]
[TestFixture] internal class SerializationClonerTest : CloneFactoryTest {
internal class SerializationClonerTest : CloneFactoryTest {
/// <summary>Initializes a new unit test suite for the reflection cloner</summary>
/// <summary>Initializes a new unit test suite for the reflection cloner</summary> public SerializationClonerTest() {
public SerializationClonerTest() { this.cloneFactory = new SerializationCloner();
this.cloneFactory = new SerializationCloner(); }
}
/// <summary>Verifies that cloning a null object simply returns null</summary>
/// <summary>Verifies that cloning a null object simply returns null</summary> [Test]
[Test] public void CloningNullYieldsNull() {
public void CloningNullYieldsNull() { Assert.IsNull(this.cloneFactory.DeepFieldClone<object>(null));
Assert.IsNull(this.cloneFactory.DeepFieldClone<object>(null)); Assert.IsNull(this.cloneFactory.DeepPropertyClone<object>(null));
Assert.IsNull(this.cloneFactory.DeepPropertyClone<object>(null)); }
}
/// <summary>
/// <summary> /// Verifies that clones of objects whose class doesn't possess a default constructor
/// Verifies that clones of objects whose class doesn't possess a default constructor /// can be made
/// can be made /// </summary>
/// </summary> [Test]
[Test] public void ClassWithoutDefaultConstructorCanBeCloned() {
public void ClassWithoutDefaultConstructorCanBeCloned() { var original = new ClassWithoutDefaultConstructor(1234);
var original = new ClassWithoutDefaultConstructor(1234); ClassWithoutDefaultConstructor clone = this.cloneFactory.DeepFieldClone(original);
ClassWithoutDefaultConstructor clone = this.cloneFactory.DeepFieldClone(original);
Assert.AreNotSame(original, clone);
Assert.AreNotSame(original, clone); Assert.AreEqual(original.Dummy, clone.Dummy);
Assert.AreEqual(original.Dummy, clone.Dummy); }
}
/// <summary>Verifies that clones of primitive types can be created</summary>
/// <summary>Verifies that clones of primitive types can be created</summary> [Test]
[Test] public void PrimitiveTypesCanBeCloned() {
public void PrimitiveTypesCanBeCloned() { int original = 12345;
int original = 12345; int clone = this.cloneFactory.DeepFieldClone(original);
int clone = this.cloneFactory.DeepFieldClone(original); Assert.AreEqual(original, clone);
Assert.AreEqual(original, clone); }
}
/// <summary>Verifies that deep clones of arrays can be made</summary>
/// <summary>Verifies that deep clones of arrays can be made</summary> [Test]
[Test] public void DeepClonesOfArraysCanBeMade() {
public void DeepClonesOfArraysCanBeMade() { var original = new TestReferenceType[] {
var original = new TestReferenceType[] { new TestReferenceType() { TestField = 123, TestProperty = 456 }
new TestReferenceType() { TestField = 123, TestProperty = 456 } };
}; TestReferenceType[] clone = this.cloneFactory.DeepFieldClone(original);
TestReferenceType[] clone = this.cloneFactory.DeepFieldClone(original);
Assert.AreNotSame(original[0], clone[0]);
Assert.AreNotSame(original[0], clone[0]); Assert.AreEqual(original[0].TestField, clone[0].TestField);
Assert.AreEqual(original[0].TestField, clone[0].TestField); Assert.AreEqual(original[0].TestProperty, clone[0].TestProperty);
Assert.AreEqual(original[0].TestProperty, clone[0].TestProperty); }
}
/// <summary>Verifies that deep clones of a generic list can be made</summary>
/// <summary>Verifies that deep clones of a generic list can be made</summary> [Test]
[Test] public void GenericListsCanBeCloned() {
public void GenericListsCanBeCloned() { var original = new List<int>(new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 });
var original = new List<int>(new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 }); List<int> clone = this.cloneFactory.DeepFieldClone(original);
List<int> clone = this.cloneFactory.DeepFieldClone(original);
CollectionAssert.AreEqual(original, clone);
CollectionAssert.AreEqual(original, clone); }
}
/// <summary>Verifies that deep clones of a generic dictionary can be made</summary>
/// <summary>Verifies that deep clones of a generic dictionary can be made</summary> [Test]
[Test] public void GenericDictionariesCanBeCloned() {
public void GenericDictionariesCanBeCloned() { var original = new Dictionary<int, string>();
var original = new Dictionary<int, string>(); original.Add(1, "one");
original.Add(1, "one"); Dictionary<int, string> clone = this.cloneFactory.DeepFieldClone(original);
Dictionary<int, string> clone = this.cloneFactory.DeepFieldClone(original);
Assert.AreEqual("one", clone[1]);
Assert.AreEqual("one", clone[1]); }
}
/// <summary>
/// <summary> /// Verifies that a field-based deep clone of a value type can be performed
/// Verifies that a field-based deep clone of a value type can be performed /// </summary>
/// </summary> [Test]
[Test] public void DeepFieldBasedClonesOfValueTypesCanBeMade() {
public void DeepFieldBasedClonesOfValueTypesCanBeMade() { HierarchicalValueType original = CreateValueType();
HierarchicalValueType original = CreateValueType(); HierarchicalValueType clone = this.cloneFactory.DeepFieldClone(original);
HierarchicalValueType clone = this.cloneFactory.DeepFieldClone(original); VerifyClone(ref original, ref clone, isDeepClone: true, isPropertyBasedClone: false);
VerifyClone(ref original, ref clone, isDeepClone: true, isPropertyBasedClone: false); }
}
/// <summary>
/// <summary> /// Verifies that a field-based deep clone of a reference type can be performed
/// Verifies that a field-based deep clone of a reference type can be performed /// </summary>
/// </summary> [Test]
[Test] public void DeepFieldBasedClonesOfReferenceTypesCanBeMade() {
public void DeepFieldBasedClonesOfReferenceTypesCanBeMade() { HierarchicalReferenceType original = CreateReferenceType();
HierarchicalReferenceType original = CreateReferenceType(); HierarchicalReferenceType clone = this.cloneFactory.DeepFieldClone(original);
HierarchicalReferenceType clone = this.cloneFactory.DeepFieldClone(original); VerifyClone(original, clone, isDeepClone: true, isPropertyBasedClone: false);
VerifyClone(original, clone, isDeepClone: true, isPropertyBasedClone: false); }
}
/// <summary>
/// <summary> /// Verifies that a property-based deep clone of a value type can be performed
/// Verifies that a property-based deep clone of a value type can be performed /// </summary>
/// </summary> [Test]
[Test] public void DeepPropertyBasedClonesOfValueTypesCanBeMade() {
public void DeepPropertyBasedClonesOfValueTypesCanBeMade() { HierarchicalValueType original = CreateValueType();
HierarchicalValueType original = CreateValueType(); HierarchicalValueType clone = this.cloneFactory.DeepPropertyClone(original);
HierarchicalValueType clone = this.cloneFactory.DeepPropertyClone(original); VerifyClone(ref original, ref clone, isDeepClone: true, isPropertyBasedClone: true);
VerifyClone(ref original, ref clone, isDeepClone: true, isPropertyBasedClone: true); }
}
/// <summary>
/// <summary> /// Verifies that a property-based deep clone of a reference type can be performed
/// Verifies that a property-based deep clone of a reference type can be performed /// </summary>
/// </summary> [Test]
[Test] public void DeepPropertyBasedClonesOfReferenceTypesCanBeMade() {
public void DeepPropertyBasedClonesOfReferenceTypesCanBeMade() { HierarchicalReferenceType original = CreateReferenceType();
HierarchicalReferenceType original = CreateReferenceType(); HierarchicalReferenceType clone = this.cloneFactory.DeepPropertyClone(original);
HierarchicalReferenceType clone = this.cloneFactory.DeepPropertyClone(original); VerifyClone(original, clone, isDeepClone: true, isPropertyBasedClone: true);
VerifyClone(original, clone, isDeepClone: true, isPropertyBasedClone: true); }
}
/// <summary>Clone factory being tested</summary>
/// <summary>Clone factory being tested</summary> private ICloneFactory cloneFactory;
private ICloneFactory cloneFactory;
}
}
} // namespace Nuclex.Support.Cloning
} // namespace Nuclex.Support.Cloning
#endif // UNITTEST
#endif // UNITTEST

View File

@ -1,328 +1,327 @@
#region CPL License #region Apache License 2.0
/* /*
Nuclex Framework Nuclex .NET Framework
Copyright (C) 2002-2017 Nuclex Development Labs Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
This library is free software; you can redistribute it and/or Licensed under the Apache License, Version 2.0 (the "License");
modify it under the terms of the IBM Common Public License as you may not use this file except in compliance with the License.
published by the IBM Corporation; either version 1.0 of the You may obtain a copy of the License at
License, or (at your option) any later version.
http://www.apache.org/licenses/LICENSE-2.0
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of Unless required by applicable law or agreed to in writing, software
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the distributed under the License is distributed on an "AS IS" BASIS,
IBM Common Public License for more details. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
You should have received a copy of the IBM Common Public limitations under the License.
License along with this library */
*/ #endregion // Apache License 2.0
#endregion
using System;
using System; using System.IO;
using System.IO; using System.Reflection;
using System.Reflection; using System.Runtime.Serialization;
using System.Runtime.Serialization; using System.Runtime.Serialization.Formatters.Binary;
using System.Runtime.Serialization.Formatters.Binary;
namespace Nuclex.Support.Cloning {
namespace Nuclex.Support.Cloning {
/// <summary>Clones objects via serialization</summary>
/// <summary>Clones objects via serialization</summary> /// <remarks>
/// <remarks> /// <para>
/// <para> /// This type of cloning uses the binary formatter to persist the state of
/// This type of cloning uses the binary formatter to persist the state of /// an object and then restores it into a clone. It has the advantage of even
/// an object and then restores it into a clone. It has the advantage of even /// working with types that don't provide a default constructor, but is
/// working with types that don't provide a default constructor, but is /// terribly slow.
/// terribly slow. /// </para>
/// </para> /// <para>
/// <para> /// Inspired by the "A Generic Method for Deep Cloning in C# 3.0" article
/// Inspired by the "A Generic Method for Deep Cloning in C# 3.0" article /// on CodeProject: http://www.codeproject.com/KB/cs/generic_deep_cloning.aspx
/// on CodeProject: http://www.codeproject.com/KB/cs/generic_deep_cloning.aspx /// </para>
/// </para> /// </remarks>
/// </remarks> public class SerializationCloner : ICloneFactory {
public class SerializationCloner : ICloneFactory {
#region class StaticSurrogateSelector
#region class StaticSurrogateSelector
/// <summary>Selects a static surrogate for any non-primitive types</summary>
/// <summary>Selects a static surrogate for any non-primitive types</summary> private class StaticSurrogateSelector : ISurrogateSelector {
private class StaticSurrogateSelector : ISurrogateSelector {
/// <summary>Initializes a new static surrogate selector</summary>
/// <summary>Initializes a new static surrogate selector</summary> /// <param name="staticSurrogate">Surrogate that will be selected</param>
/// <param name="staticSurrogate">Surrogate that will be selected</param> public StaticSurrogateSelector(ISerializationSurrogate staticSurrogate) {
public StaticSurrogateSelector(ISerializationSurrogate staticSurrogate) { this.staticSurrogate = staticSurrogate;
this.staticSurrogate = staticSurrogate; }
}
/// <summary>
/// <summary> /// Sets the next selector to escalate to if this one can't provide a surrogate
/// Sets the next selector to escalate to if this one can't provide a surrogate /// </summary>
/// </summary> /// <param name="selector">Selector to escalate to</param>
/// <param name="selector">Selector to escalate to</param> public void ChainSelector(ISurrogateSelector selector) {
public void ChainSelector(ISurrogateSelector selector) { this.chainedSelector = selector;
this.chainedSelector = selector; }
}
/// <summary>
/// <summary> /// Returns the selector this one will escalate to if it can't provide a surrogate
/// Returns the selector this one will escalate to if it can't provide a surrogate /// </summary>
/// </summary> /// <returns>The selector this one will escalate to</returns>
/// <returns>The selector this one will escalate to</returns> public ISurrogateSelector GetNextSelector() {
public ISurrogateSelector GetNextSelector() { return this.chainedSelector;
return this.chainedSelector; }
}
/// <summary>Attempts to provides a surrogate for the specified type</summary>
/// <summary>Attempts to provides a surrogate for the specified type</summary> /// <param name="type">Type a surrogate will be provided for</param>
/// <param name="type">Type a surrogate will be provided for</param> /// <param name="context">Context </param>
/// <param name="context">Context </param> /// <param name="selector"></param>
/// <param name="selector"></param> /// <returns></returns>
/// <returns></returns> public ISerializationSurrogate GetSurrogate(
public ISerializationSurrogate GetSurrogate( Type type, StreamingContext context, out ISurrogateSelector selector
Type type, StreamingContext context, out ISurrogateSelector selector ) {
) { if(type.IsPrimitive || type.IsArray || (type == typeof(string))) {
if(type.IsPrimitive || type.IsArray || (type == typeof(string))) { if(this.chainedSelector == null) {
if(this.chainedSelector == null) { selector = null;
selector = null; return null;
return null; } else {
} else { return this.chainedSelector.GetSurrogate(type, context, out selector);
return this.chainedSelector.GetSurrogate(type, context, out selector); }
} } else {
} else { selector = this;
selector = this; return this.staticSurrogate;
return this.staticSurrogate; }
} }
}
/// <summary>Surrogate the that will be selected for any non-primitive types</summary>
/// <summary>Surrogate the that will be selected for any non-primitive types</summary> private readonly ISerializationSurrogate staticSurrogate;
private readonly ISerializationSurrogate staticSurrogate; /// <summary>Surrogate selector to escalate to if no surrogate can be provided</summary>
/// <summary>Surrogate selector to escalate to if no surrogate can be provided</summary> private ISurrogateSelector chainedSelector;
private ISurrogateSelector chainedSelector;
}
}
#endregion // class StaticSurrogateSelector
#endregion // class StaticSurrogateSelector
#region class FieldSerializationSurrogate
#region class FieldSerializationSurrogate
/// <summary>Serializes a type based on its fields</summary>
/// <summary>Serializes a type based on its fields</summary> private class FieldSerializationSurrogate : ISerializationSurrogate {
private class FieldSerializationSurrogate : ISerializationSurrogate {
/// <summary>Extracts the data to be serialized from an object</summary>
/// <summary>Extracts the data to be serialized from an object</summary> /// <param name="objectToSerialize">Object that is being serialized</param>
/// <param name="objectToSerialize">Object that is being serialized</param> /// <param name="info">Stores the serialized informations</param>
/// <param name="info">Stores the serialized informations</param> /// <param name="context">
/// <param name="context"> /// Provides additional informations about the serialization process
/// Provides additional informations about the serialization process /// </param>
/// </param> public void GetObjectData(
public void GetObjectData( object objectToSerialize,
object objectToSerialize, SerializationInfo info,
SerializationInfo info, StreamingContext context
StreamingContext context ) {
) { Type originalType = objectToSerialize.GetType();
Type originalType = objectToSerialize.GetType();
FieldInfo[] fieldInfos = originalType.GetFieldInfosIncludingBaseClasses(
FieldInfo[] fieldInfos = originalType.GetFieldInfosIncludingBaseClasses( BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance
BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance );
); for(int index = 0; index < fieldInfos.Length; ++index) {
for(int index = 0; index < fieldInfos.Length; ++index) { FieldInfo fieldInfo = fieldInfos[index];
FieldInfo fieldInfo = fieldInfos[index]; info.AddValue(fieldInfo.Name, fieldInfo.GetValue(objectToSerialize));
info.AddValue(fieldInfo.Name, fieldInfo.GetValue(objectToSerialize)); }
} }
}
/// <summary>Reinserts saved data into a deserializd object</summary>
/// <summary>Reinserts saved data into a deserializd object</summary> /// <param name="deserializedObject">Object the saved data will be inserted into</param>
/// <param name="deserializedObject">Object the saved data will be inserted into</param> /// <param name="info">Contains the serialized informations</param>
/// <param name="info">Contains the serialized informations</param> /// <param name="context">
/// <param name="context"> /// Provides additional informations about the serialization process
/// Provides additional informations about the serialization process /// </param>
/// </param> /// <param name="selector">Surrogate selector that specified this surrogate</param>
/// <param name="selector">Surrogate selector that specified this surrogate</param> /// <returns>The deserialized object</returns>
/// <returns>The deserialized object</returns> public object SetObjectData(
public object SetObjectData( object deserializedObject,
object deserializedObject, SerializationInfo info,
SerializationInfo info, StreamingContext context,
StreamingContext context, ISurrogateSelector selector
ISurrogateSelector selector ) {
) { Type originalType = deserializedObject.GetType();
Type originalType = deserializedObject.GetType();
FieldInfo[] fieldInfos = originalType.GetFieldInfosIncludingBaseClasses(
FieldInfo[] fieldInfos = originalType.GetFieldInfosIncludingBaseClasses( BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance
BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance );
); for(int index = 0; index < fieldInfos.Length; ++index) {
for(int index = 0; index < fieldInfos.Length; ++index) { FieldInfo fieldInfo = fieldInfos[index];
FieldInfo fieldInfo = fieldInfos[index]; fieldInfo.SetValue(deserializedObject, info.GetValue(fieldInfo.Name, fieldInfo.FieldType));
fieldInfo.SetValue(deserializedObject, info.GetValue(fieldInfo.Name, fieldInfo.FieldType)); }
}
return deserializedObject;
return deserializedObject; }
}
}
}
#endregion // class FieldSerializationSurrogate
#endregion // class FieldSerializationSurrogate
#region class PropertySerializationSurrogate
#region class PropertySerializationSurrogate
/// <summary>Serializes a type based on its properties</summary>
/// <summary>Serializes a type based on its properties</summary> private class PropertySerializationSurrogate : ISerializationSurrogate {
private class PropertySerializationSurrogate : ISerializationSurrogate {
/// <summary>Extracts the data to be serialized from an object</summary>
/// <summary>Extracts the data to be serialized from an object</summary> /// <param name="objectToSerialize">Object that is being serialized</param>
/// <param name="objectToSerialize">Object that is being serialized</param> /// <param name="info">Stores the serialized informations</param>
/// <param name="info">Stores the serialized informations</param> /// <param name="context">
/// <param name="context"> /// Provides additional informations about the serialization process
/// Provides additional informations about the serialization process /// </param>
/// </param> public void GetObjectData(
public void GetObjectData( object objectToSerialize,
object objectToSerialize, SerializationInfo info,
SerializationInfo info, StreamingContext context
StreamingContext context ) {
) { Type originalType = objectToSerialize.GetType();
Type originalType = objectToSerialize.GetType();
PropertyInfo[] propertyInfos = originalType.GetProperties(
PropertyInfo[] propertyInfos = originalType.GetProperties( BindingFlags.Public | BindingFlags.NonPublic |
BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.FlattenHierarchy
BindingFlags.Instance | BindingFlags.FlattenHierarchy );
); for(int index = 0; index < propertyInfos.Length; ++index) {
for(int index = 0; index < propertyInfos.Length; ++index) { PropertyInfo propertyInfo = propertyInfos[index];
PropertyInfo propertyInfo = propertyInfos[index]; if(propertyInfo.CanRead && propertyInfo.CanWrite) {
if(propertyInfo.CanRead && propertyInfo.CanWrite) { info.AddValue(propertyInfo.Name, propertyInfo.GetValue(objectToSerialize, null));
info.AddValue(propertyInfo.Name, propertyInfo.GetValue(objectToSerialize, null)); }
} }
} }
}
/// <summary>Reinserts saved data into a deserializd object</summary>
/// <summary>Reinserts saved data into a deserializd object</summary> /// <param name="deserializedObject">Object the saved data will be inserted into</param>
/// <param name="deserializedObject">Object the saved data will be inserted into</param> /// <param name="info">Contains the serialized informations</param>
/// <param name="info">Contains the serialized informations</param> /// <param name="context">
/// <param name="context"> /// Provides additional informations about the serialization process
/// Provides additional informations about the serialization process /// </param>
/// </param> /// <param name="selector">Surrogate selector that specified this surrogate</param>
/// <param name="selector">Surrogate selector that specified this surrogate</param> /// <returns>The deserialized object</returns>
/// <returns>The deserialized object</returns> public object SetObjectData(
public object SetObjectData( object deserializedObject,
object deserializedObject, SerializationInfo info,
SerializationInfo info, StreamingContext context,
StreamingContext context, ISurrogateSelector selector
ISurrogateSelector selector ) {
) { Type originalType = deserializedObject.GetType();
Type originalType = deserializedObject.GetType();
PropertyInfo[] propertyInfos = originalType.GetProperties(
PropertyInfo[] propertyInfos = originalType.GetProperties( BindingFlags.Public | BindingFlags.NonPublic |
BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.FlattenHierarchy
BindingFlags.Instance | BindingFlags.FlattenHierarchy );
); for(int index = 0; index < propertyInfos.Length; ++index) {
for(int index = 0; index < propertyInfos.Length; ++index) { PropertyInfo propertyInfo = propertyInfos[index];
PropertyInfo propertyInfo = propertyInfos[index]; if(propertyInfo.CanRead && propertyInfo.CanWrite) {
if(propertyInfo.CanRead && propertyInfo.CanWrite) { propertyInfo.SetValue(
propertyInfo.SetValue( deserializedObject,
deserializedObject, info.GetValue(propertyInfo.Name, propertyInfo.PropertyType),
info.GetValue(propertyInfo.Name, propertyInfo.PropertyType), null
null );
); }
} }
}
return deserializedObject;
return deserializedObject; }
}
}
}
#endregion // class PropertySerializationSurrogate
#endregion // class PropertySerializationSurrogate
/// <summary>Initializes the static members of the serialization-based cloner</summary>
/// <summary>Initializes the static members of the serialization-based cloner</summary> static SerializationCloner() {
static SerializationCloner() { fieldBasedFormatter = new BinaryFormatter(
fieldBasedFormatter = new BinaryFormatter( new StaticSurrogateSelector(new FieldSerializationSurrogate()),
new StaticSurrogateSelector(new FieldSerializationSurrogate()), new StreamingContext(StreamingContextStates.All)
new StreamingContext(StreamingContextStates.All) );
); propertyBasedFormatter = new BinaryFormatter(
propertyBasedFormatter = new BinaryFormatter( new StaticSurrogateSelector(new PropertySerializationSurrogate()),
new StaticSurrogateSelector(new PropertySerializationSurrogate()), new StreamingContext(StreamingContextStates.All)
new StreamingContext(StreamingContextStates.All) );
); }
}
/// <summary>
/// <summary> /// Creates a deep clone of the specified object, also creating clones of all
/// Creates a deep clone of the specified object, also creating clones of all /// child objects being referenced
/// child objects being referenced /// </summary>
/// </summary> /// <typeparam name="TCloned">Type of the object that will be cloned</typeparam>
/// <typeparam name="TCloned">Type of the object that will be cloned</typeparam> /// <param name="objectToClone">Object that will be cloned</param>
/// <param name="objectToClone">Object that will be cloned</param> /// <returns>A deep clone of the provided object</returns>
/// <returns>A deep clone of the provided object</returns> public static TCloned DeepFieldClone<TCloned>(TCloned objectToClone) {
public static TCloned DeepFieldClone<TCloned>(TCloned objectToClone) { if(typeof(TCloned).IsClass || typeof(TCloned).IsArray) {
if(typeof(TCloned).IsClass || typeof(TCloned).IsArray) { if(ReferenceEquals(objectToClone, null)) {
if(ReferenceEquals(objectToClone, null)) { return default(TCloned);
return default(TCloned); }
} }
} using(var memoryStream = new MemoryStream()) {
using(var memoryStream = new MemoryStream()) { fieldBasedFormatter.Serialize(memoryStream, objectToClone);
fieldBasedFormatter.Serialize(memoryStream, objectToClone); memoryStream.Position = 0;
memoryStream.Position = 0; return (TCloned)fieldBasedFormatter.Deserialize(memoryStream);
return (TCloned)fieldBasedFormatter.Deserialize(memoryStream); }
} }
}
/// <summary>
/// <summary> /// Creates a deep clone of the specified object, also creating clones of all
/// Creates a deep clone of the specified object, also creating clones of all /// child objects being referenced
/// child objects being referenced /// </summary>
/// </summary> /// <typeparam name="TCloned">Type of the object that will be cloned</typeparam>
/// <typeparam name="TCloned">Type of the object that will be cloned</typeparam> /// <param name="objectToClone">Object that will be cloned</param>
/// <param name="objectToClone">Object that will be cloned</param> /// <returns>A deep clone of the provided object</returns>
/// <returns>A deep clone of the provided object</returns> public static TCloned DeepPropertyClone<TCloned>(TCloned objectToClone) {
public static TCloned DeepPropertyClone<TCloned>(TCloned objectToClone) { if(typeof(TCloned).IsClass || typeof(TCloned).IsArray) {
if(typeof(TCloned).IsClass || typeof(TCloned).IsArray) { if(ReferenceEquals(objectToClone, null)) {
if(ReferenceEquals(objectToClone, null)) { return default(TCloned);
return default(TCloned); }
} }
} using(var memoryStream = new MemoryStream()) {
using(var memoryStream = new MemoryStream()) { propertyBasedFormatter.Serialize(memoryStream, objectToClone);
propertyBasedFormatter.Serialize(memoryStream, objectToClone); memoryStream.Position = 0;
memoryStream.Position = 0; return (TCloned)propertyBasedFormatter.Deserialize(memoryStream);
return (TCloned)propertyBasedFormatter.Deserialize(memoryStream); }
} }
}
/// <summary>
/// <summary> /// Creates a shallow clone of the specified object, reusing any referenced objects
/// Creates a shallow clone of the specified object, reusing any referenced objects /// </summary>
/// </summary> /// <typeparam name="TCloned">Type of the object that will be cloned</typeparam>
/// <typeparam name="TCloned">Type of the object that will be cloned</typeparam> /// <param name="objectToClone">Object that will be cloned</param>
/// <param name="objectToClone">Object that will be cloned</param> /// <returns>A shallow clone of the provided object</returns>
/// <returns>A shallow clone of the provided object</returns> TCloned ICloneFactory.ShallowFieldClone<TCloned>(TCloned objectToClone) {
TCloned ICloneFactory.ShallowFieldClone<TCloned>(TCloned objectToClone) { throw new NotSupportedException("The serialization cloner cannot create shallow clones");
throw new NotSupportedException("The serialization cloner cannot create shallow clones"); }
}
/// <summary>
/// <summary> /// Creates a shallow clone of the specified object, reusing any referenced objects
/// Creates a shallow clone of the specified object, reusing any referenced objects /// </summary>
/// </summary> /// <typeparam name="TCloned">Type of the object that will be cloned</typeparam>
/// <typeparam name="TCloned">Type of the object that will be cloned</typeparam> /// <param name="objectToClone">Object that will be cloned</param>
/// <param name="objectToClone">Object that will be cloned</param> /// <returns>A shallow clone of the provided object</returns>
/// <returns>A shallow clone of the provided object</returns> TCloned ICloneFactory.ShallowPropertyClone<TCloned>(TCloned objectToClone) {
TCloned ICloneFactory.ShallowPropertyClone<TCloned>(TCloned objectToClone) { throw new NotSupportedException("The serialization cloner cannot create shallow clones");
throw new NotSupportedException("The serialization cloner cannot create shallow clones"); }
}
/// <summary>
/// <summary> /// Creates a deep clone of the specified object, also creating clones of all
/// Creates a deep clone of the specified object, also creating clones of all /// child objects being referenced
/// child objects being referenced /// </summary>
/// </summary> /// <typeparam name="TCloned">Type of the object that will be cloned</typeparam>
/// <typeparam name="TCloned">Type of the object that will be cloned</typeparam> /// <param name="objectToClone">Object that will be cloned</param>
/// <param name="objectToClone">Object that will be cloned</param> /// <returns>A deep clone of the provided object</returns>
/// <returns>A deep clone of the provided object</returns> TCloned ICloneFactory.DeepFieldClone<TCloned>(TCloned objectToClone) {
TCloned ICloneFactory.DeepFieldClone<TCloned>(TCloned objectToClone) { return SerializationCloner.DeepFieldClone<TCloned>(objectToClone);
return SerializationCloner.DeepFieldClone<TCloned>(objectToClone); }
}
/// <summary>
/// <summary> /// Creates a deep clone of the specified object, also creating clones of all
/// Creates a deep clone of the specified object, also creating clones of all /// child objects being referenced
/// child objects being referenced /// </summary>
/// </summary> /// <typeparam name="TCloned">Type of the object that will be cloned</typeparam>
/// <typeparam name="TCloned">Type of the object that will be cloned</typeparam> /// <param name="objectToClone">Object that will be cloned</param>
/// <param name="objectToClone">Object that will be cloned</param> /// <returns>A deep clone of the provided object</returns>
/// <returns>A deep clone of the provided object</returns> TCloned ICloneFactory.DeepPropertyClone<TCloned>(TCloned objectToClone) {
TCloned ICloneFactory.DeepPropertyClone<TCloned>(TCloned objectToClone) { return SerializationCloner.DeepPropertyClone<TCloned>(objectToClone);
return SerializationCloner.DeepPropertyClone<TCloned>(objectToClone); }
}
/// <summary>Serializes objects by storing their fields</summary>
/// <summary>Serializes objects by storing their fields</summary> private static readonly BinaryFormatter fieldBasedFormatter;
private static readonly BinaryFormatter fieldBasedFormatter; /// <summary>Serializes objects by storing their properties</summary>
/// <summary>Serializes objects by storing their properties</summary> private static readonly BinaryFormatter propertyBasedFormatter;
private static readonly BinaryFormatter propertyBasedFormatter;
}
}
} // namespace Nuclex.Support.Cloning
} // namespace Nuclex.Support.Cloning

View File

@ -1,53 +1,52 @@
#region CPL License #region Apache License 2.0
/* /*
Nuclex Framework Nuclex .NET Framework
Copyright (C) 2002-2017 Nuclex Development Labs Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
This library is free software; you can redistribute it and/or Licensed under the Apache License, Version 2.0 (the "License");
modify it under the terms of the IBM Common Public License as you may not use this file except in compliance with the License.
published by the IBM Corporation; either version 1.0 of the You may obtain a copy of the License at
License, or (at your option) any later version.
http://www.apache.org/licenses/LICENSE-2.0
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of Unless required by applicable law or agreed to in writing, software
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the distributed under the License is distributed on an "AS IS" BASIS,
IBM Common Public License for more details. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
You should have received a copy of the IBM Common Public limitations under the License.
License along with this library */
*/ #endregion // Apache License 2.0
#endregion
#if UNITTEST
#if UNITTEST
using System;
using System; using System.Collections.Specialized;
using System.Collections.Specialized;
using NUnit.Framework;
using NUnit.Framework;
namespace Nuclex.Support.Collections {
namespace Nuclex.Support.Collections {
/// <summary>Unit Test for the collection constants</summary>
/// <summary>Unit Test for the collection constants</summary> [TestFixture]
[TestFixture] internal class ConstantsTest {
internal class ConstantsTest {
#if !NO_SPECIALIZED_COLLECTIONS
#if !NO_SPECIALIZED_COLLECTIONS
/// <summary>
/// <summary> /// Verifies that the collection reset event arguments have 'reset' specified as
/// Verifies that the collection reset event arguments have 'reset' specified as /// their action
/// their action /// </summary>
/// </summary> [Test]
[Test] public void CollectionResetEventArgsHaveResetActionSet() {
public void CollectionResetEventArgsHaveResetActionSet() { Assert.AreEqual(
Assert.AreEqual( NotifyCollectionChangedAction.Reset, Constants.NotifyCollectionResetEventArgs.Action
NotifyCollectionChangedAction.Reset, Constants.NotifyCollectionResetEventArgs.Action );
); }
}
#endif // !NO_SPECIALIZED_COLLECTIONS
#endif // !NO_SPECIALIZED_COLLECTIONS
}
}
} // namespace Nuclex.Support.Collections
} // namespace Nuclex.Support.Collections
#endif // UNITTEST
#endif // UNITTEST

View File

@ -1,39 +1,38 @@
#region CPL License #region Apache License 2.0
/* /*
Nuclex Framework Nuclex .NET Framework
Copyright (C) 2002-2017 Nuclex Development Labs Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
This library is free software; you can redistribute it and/or Licensed under the Apache License, Version 2.0 (the "License");
modify it under the terms of the IBM Common Public License as you may not use this file except in compliance with the License.
published by the IBM Corporation; either version 1.0 of the You may obtain a copy of the License at
License, or (at your option) any later version.
http://www.apache.org/licenses/LICENSE-2.0
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of Unless required by applicable law or agreed to in writing, software
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the distributed under the License is distributed on an "AS IS" BASIS,
IBM Common Public License for more details. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
You should have received a copy of the IBM Common Public limitations under the License.
License along with this library */
*/ #endregion // Apache License 2.0
#endregion
using System;
using System; #if !NO_SPECIALIZED_COLLECTIONS
#if !NO_SPECIALIZED_COLLECTIONS using System.Collections.Specialized;
using System.Collections.Specialized; #endif
#endif
namespace Nuclex.Support.Collections {
namespace Nuclex.Support.Collections {
/// <summary>Contains fixed constants used by some collections</summary>
/// <summary>Contains fixed constants used by some collections</summary> public static class Constants {
public static class Constants {
#if !NO_SPECIALIZED_COLLECTIONS
#if !NO_SPECIALIZED_COLLECTIONS /// <summary>Fixed event args used to notify that the collection has reset</summary>
/// <summary>Fixed event args used to notify that the collection has reset</summary> public static readonly NotifyCollectionChangedEventArgs NotifyCollectionResetEventArgs =
public static readonly NotifyCollectionChangedEventArgs NotifyCollectionResetEventArgs = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset);
new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset); #endif
#endif
}
}
} // namespace Nuclex.Support.Collections
} // namespace Nuclex.Support.Collections

View File

@ -1,211 +1,210 @@
#region CPL License #region Apache License 2.0
/* /*
Nuclex Framework Nuclex .NET Framework
Copyright (C) 2002-2017 Nuclex Development Labs Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
This library is free software; you can redistribute it and/or Licensed under the Apache License, Version 2.0 (the "License");
modify it under the terms of the IBM Common Public License as you may not use this file except in compliance with the License.
published by the IBM Corporation; either version 1.0 of the You may obtain a copy of the License at
License, or (at your option) any later version.
http://www.apache.org/licenses/LICENSE-2.0
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of Unless required by applicable law or agreed to in writing, software
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the distributed under the License is distributed on an "AS IS" BASIS,
IBM Common Public License for more details. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
You should have received a copy of the IBM Common Public limitations under the License.
License along with this library */
*/ #endregion // Apache License 2.0
#endregion
using System;
using System;
namespace Nuclex.Support.Collections {
namespace Nuclex.Support.Collections {
partial class Deque<TItem> {
partial class Deque<TItem> {
/// <summary>Inserts an item at the beginning of the double-ended queue</summary>
/// <summary>Inserts an item at the beginning of the double-ended queue</summary> /// <param name="item">Item that will be inserted into the queue</param>
/// <param name="item">Item that will be inserted into the queue</param> public void AddFirst(TItem item) {
public void AddFirst(TItem item) { if(this.firstBlockStartIndex > 0) {
if(this.firstBlockStartIndex > 0) { --this.firstBlockStartIndex;
--this.firstBlockStartIndex; } else { // Need to allocate a new block
} else { // Need to allocate a new block this.blocks.Insert(0, new TItem[this.blockSize]);
this.blocks.Insert(0, new TItem[this.blockSize]); this.firstBlockStartIndex = this.blockSize - 1;
this.firstBlockStartIndex = this.blockSize - 1; }
}
this.blocks[0][this.firstBlockStartIndex] = item;
this.blocks[0][this.firstBlockStartIndex] = item; ++this.count;
++this.count; #if DEBUG
#if DEBUG ++this.version;
++this.version; #endif
#endif }
}
/// <summary>Appends an item to the end of the double-ended queue</summary>
/// <summary>Appends an item to the end of the double-ended queue</summary> /// <param name="item">Item that will be appended to the queue</param>
/// <param name="item">Item that will be appended to the queue</param> public void AddLast(TItem item) {
public void AddLast(TItem item) { if(this.lastBlockEndIndex < this.blockSize) {
if(this.lastBlockEndIndex < this.blockSize) { ++this.lastBlockEndIndex;
++this.lastBlockEndIndex; } else { // Need to allocate a new block
} else { // Need to allocate a new block this.blocks.Add(new TItem[this.blockSize]);
this.blocks.Add(new TItem[this.blockSize]); this.lastBlockEndIndex = 1;
this.lastBlockEndIndex = 1; }
}
this.blocks[this.blocks.Count - 1][this.lastBlockEndIndex - 1] = item;
this.blocks[this.blocks.Count - 1][this.lastBlockEndIndex - 1] = item; ++this.count;
++this.count; #if DEBUG
#if DEBUG ++this.version;
++this.version; #endif
#endif }
}
/// <summary>Inserts the item at the specified index</summary>
/// <summary>Inserts the item at the specified index</summary> /// <param name="index">Index the item will be inserted at</param>
/// <param name="index">Index the item will be inserted at</param> /// <param name="item">Item that will be inserted</param>
/// <param name="item">Item that will be inserted</param> public void Insert(int index, TItem item) {
public void Insert(int index, TItem item) { int distanceToRightEnd = this.count - index;
int distanceToRightEnd = this.count - index; if(index < distanceToRightEnd) { // Are we closer to the left end?
if(index < distanceToRightEnd) { // Are we closer to the left end? shiftLeftAndInsert(index, item);
shiftLeftAndInsert(index, item); } else { // Nope, we're closer to the right end
} else { // Nope, we're closer to the right end shiftRightAndInsert(index, item);
shiftRightAndInsert(index, item); }
} #if DEBUG
#if DEBUG ++this.version;
++this.version; #endif
#endif }
}
/// <summary>
/// <summary> /// Shifts all items before the insertion point to the left and inserts
/// Shifts all items before the insertion point to the left and inserts /// the item at the specified index
/// the item at the specified index /// </summary>
/// </summary> /// <param name="index">Index the item will be inserted at</param>
/// <param name="index">Index the item will be inserted at</param> /// <param name="item">Item that will be inserted</param>
/// <param name="item">Item that will be inserted</param> private void shiftLeftAndInsert(int index, TItem item) {
private void shiftLeftAndInsert(int index, TItem item) { if(index == 0) {
if(index == 0) { AddFirst(item);
AddFirst(item); } else {
} else { int blockIndex, subIndex;
int blockIndex, subIndex; findIndex(index, out blockIndex, out subIndex);
findIndex(index, out blockIndex, out subIndex);
int firstBlock = 0;
int firstBlock = 0; int blockStart;
int blockStart;
// If the first block is full, we need to add another block
// If the first block is full, we need to add another block if(this.firstBlockStartIndex == 0) {
if(this.firstBlockStartIndex == 0) { this.blocks.Insert(0, new TItem[this.blockSize]);
this.blocks.Insert(0, new TItem[this.blockSize]); this.blocks[0][this.blockSize - 1] = this.blocks[1][0];
this.blocks[0][this.blockSize - 1] = this.blocks[1][0]; this.firstBlockStartIndex = this.blockSize - 1;
this.firstBlockStartIndex = this.blockSize - 1;
blockStart = 1;
blockStart = 1; --subIndex;
--subIndex; if(subIndex < 0) {
if(subIndex < 0) { subIndex = this.blockSize - 1;
subIndex = this.blockSize - 1; } else {
} else { ++blockIndex;
++blockIndex; }
} ++firstBlock;
++firstBlock; } else {
} else { blockStart = this.firstBlockStartIndex;
blockStart = this.firstBlockStartIndex; --this.firstBlockStartIndex;
--this.firstBlockStartIndex;
--subIndex;
--subIndex; if(subIndex < 0) {
if(subIndex < 0) { subIndex = this.blockSize - 1;
subIndex = this.blockSize - 1; --blockIndex;
--blockIndex; }
} }
}
// If the insertion point is not in the first block
// If the insertion point is not in the first block if(blockIndex != firstBlock) {
if(blockIndex != firstBlock) { Array.Copy(
Array.Copy( this.blocks[firstBlock], blockStart,
this.blocks[firstBlock], blockStart, this.blocks[firstBlock], blockStart - 1,
this.blocks[firstBlock], blockStart - 1, this.blockSize - blockStart
this.blockSize - blockStart );
); this.blocks[firstBlock][this.blockSize - 1] = this.blocks[firstBlock + 1][0];
this.blocks[firstBlock][this.blockSize - 1] = this.blocks[firstBlock + 1][0];
// Move all the blocks following the insertion point to the right by one item.
// Move all the blocks following the insertion point to the right by one item. // If there are no blocks inbetween, this for loop will not run.
// If there are no blocks inbetween, this for loop will not run. for(int tempIndex = firstBlock + 1; tempIndex < blockIndex; ++tempIndex) {
for(int tempIndex = firstBlock + 1; tempIndex < blockIndex; ++tempIndex) { Array.Copy(
Array.Copy( this.blocks[tempIndex], 1, this.blocks[tempIndex], 0, this.blockSize - 1
this.blocks[tempIndex], 1, this.blocks[tempIndex], 0, this.blockSize - 1 );
); this.blocks[tempIndex][this.blockSize - 1] = this.blocks[tempIndex + 1][0];
this.blocks[tempIndex][this.blockSize - 1] = this.blocks[tempIndex + 1][0]; }
}
blockStart = 1;
blockStart = 1; }
}
// Finally, move the items in the block the insertion takes place in
// Finally, move the items in the block the insertion takes place in Array.Copy(
Array.Copy( this.blocks[blockIndex], blockStart,
this.blocks[blockIndex], blockStart, this.blocks[blockIndex], blockStart - 1,
this.blocks[blockIndex], blockStart - 1, subIndex - blockStart + 1
subIndex - blockStart + 1 );
);
this.blocks[blockIndex][subIndex] = item;
this.blocks[blockIndex][subIndex] = item; ++this.count;
++this.count; }
} }
}
/// <summary>
/// <summary> /// Shifts all items after the insertion point to the right and inserts
/// Shifts all items after the insertion point to the right and inserts /// the item at the specified index
/// the item at the specified index /// </summary>
/// </summary> /// <param name="index">Index the item will be inserted at</param>
/// <param name="index">Index the item will be inserted at</param> /// <param name="item">Item that will be inserted</param>
/// <param name="item">Item that will be inserted</param> private void shiftRightAndInsert(int index, TItem item) {
private void shiftRightAndInsert(int index, TItem item) { if(index == this.count) {
if(index == this.count) { AddLast(item);
AddLast(item); } else {
} else { int blockIndex, subIndex;
int blockIndex, subIndex; findIndex(index, out blockIndex, out subIndex);
findIndex(index, out blockIndex, out subIndex);
int lastBlock = this.blocks.Count - 1;
int lastBlock = this.blocks.Count - 1; int blockLength;
int blockLength;
// If the lastmost block is full, we need to add another block
// If the lastmost block is full, we need to add another block if(this.lastBlockEndIndex == this.blockSize) {
if(this.lastBlockEndIndex == this.blockSize) { this.blocks.Add(new TItem[this.blockSize]);
this.blocks.Add(new TItem[this.blockSize]); this.blocks[lastBlock + 1][0] = this.blocks[lastBlock][this.blockSize - 1];
this.blocks[lastBlock + 1][0] = this.blocks[lastBlock][this.blockSize - 1]; this.lastBlockEndIndex = 1;
this.lastBlockEndIndex = 1;
blockLength = this.blockSize - 1;
blockLength = this.blockSize - 1; } else {
} else { blockLength = this.lastBlockEndIndex;
blockLength = this.lastBlockEndIndex; ++this.lastBlockEndIndex;
++this.lastBlockEndIndex; }
}
// If the insertion point is not in the lastmost block
// If the insertion point is not in the lastmost block if(blockIndex != lastBlock) {
if(blockIndex != lastBlock) { Array.Copy(
Array.Copy( this.blocks[lastBlock], 0, this.blocks[lastBlock], 1, blockLength
this.blocks[lastBlock], 0, this.blocks[lastBlock], 1, blockLength );
); this.blocks[lastBlock][0] = this.blocks[lastBlock - 1][this.blockSize - 1];
this.blocks[lastBlock][0] = this.blocks[lastBlock - 1][this.blockSize - 1];
// Move all the blocks following the insertion point to the right by one item.
// Move all the blocks following the insertion point to the right by one item. // If there are no blocks inbetween, this for loop will not run.
// If there are no blocks inbetween, this for loop will not run. for(int tempIndex = lastBlock - 1; tempIndex > blockIndex; --tempIndex) {
for(int tempIndex = lastBlock - 1; tempIndex > blockIndex; --tempIndex) { Array.Copy(
Array.Copy( this.blocks[tempIndex], 0, this.blocks[tempIndex], 1, this.blockSize - 1
this.blocks[tempIndex], 0, this.blocks[tempIndex], 1, this.blockSize - 1 );
); this.blocks[tempIndex][0] = this.blocks[tempIndex - 1][this.blockSize - 1];
this.blocks[tempIndex][0] = this.blocks[tempIndex - 1][this.blockSize - 1]; }
}
blockLength = this.blockSize - 1;
blockLength = this.blockSize - 1; }
}
// Finally, move the items in the block the insertion takes place in
// Finally, move the items in the block the insertion takes place in Array.Copy(
Array.Copy( this.blocks[blockIndex], subIndex,
this.blocks[blockIndex], subIndex, this.blocks[blockIndex], subIndex + 1,
this.blocks[blockIndex], subIndex + 1, blockLength - subIndex
blockLength - subIndex );
);
this.blocks[blockIndex][subIndex] = item;
this.blocks[blockIndex][subIndex] = item; ++this.count;
++this.count; }
} }
}
}
}
} // namespace Nuclex.Support.Collections
} // namespace Nuclex.Support.Collections

View File

@ -1,150 +1,149 @@
#region CPL License #region Apache License 2.0
/* /*
Nuclex Framework Nuclex .NET Framework
Copyright (C) 2002-2017 Nuclex Development Labs Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
This library is free software; you can redistribute it and/or Licensed under the Apache License, Version 2.0 (the "License");
modify it under the terms of the IBM Common Public License as you may not use this file except in compliance with the License.
published by the IBM Corporation; either version 1.0 of the You may obtain a copy of the License at
License, or (at your option) any later version.
http://www.apache.org/licenses/LICENSE-2.0
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of Unless required by applicable law or agreed to in writing, software
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the distributed under the License is distributed on an "AS IS" BASIS,
IBM Common Public License for more details. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
You should have received a copy of the IBM Common Public limitations under the License.
License along with this library */
*/ #endregion // Apache License 2.0
#endregion
using System;
using System; using System.Collections.Generic;
using System.Collections.Generic; using System.Collections;
using System.Collections;
namespace Nuclex.Support.Collections {
namespace Nuclex.Support.Collections {
partial class Deque<TItem> {
partial class Deque<TItem> {
#region IEnumerable members
#region IEnumerable members
/// <summary>Obtains a new enumerator for the contents of the deque</summary>
/// <summary>Obtains a new enumerator for the contents of the deque</summary> /// <returns>The new enumerator</returns>
/// <returns>The new enumerator</returns> IEnumerator IEnumerable.GetEnumerator() {
IEnumerator IEnumerable.GetEnumerator() { return new Enumerator(this);
return new Enumerator(this); }
}
#endregion
#endregion
#region IList Members
#region IList Members
/// <summary>Adds an item to the deque</summary>
/// <summary>Adds an item to the deque</summary> /// <param name="value">Item that will be added to the deque</param>
/// <param name="value">Item that will be added to the deque</param> /// <returns>The index at which the new item was added</returns>
/// <returns>The index at which the new item was added</returns> int IList.Add(object value) {
int IList.Add(object value) { verifyCompatibleObject(value);
verifyCompatibleObject(value);
AddLast((TItem)value);
AddLast((TItem)value); return this.count - 1;
return this.count - 1; }
}
/// <summary>Checks whether the deque contains the specified item</summary>
/// <summary>Checks whether the deque contains the specified item</summary> /// <param name="value">Item the deque will be scanned for</param>
/// <param name="value">Item the deque will be scanned for</param> /// <returns>True if the deque contained the specified item</returns>
/// <returns>True if the deque contained the specified item</returns> bool IList.Contains(object value) {
bool IList.Contains(object value) { return isCompatibleObject(value) && Contains((TItem)value);
return isCompatibleObject(value) && Contains((TItem)value); }
}
/// <summary>Determines the index of the item in the deque</summary>
/// <summary>Determines the index of the item in the deque</summary> /// <param name="value">Item whose index will be determined</param>
/// <param name="value">Item whose index will be determined</param> /// <returns>The index of the specified item in the deque</returns>
/// <returns>The index of the specified item in the deque</returns> int IList.IndexOf(object value) {
int IList.IndexOf(object value) { if(isCompatibleObject(value)) {
if(isCompatibleObject(value)) { return IndexOf((TItem)value);
return IndexOf((TItem)value); } else {
} else { return -1;
return -1; }
} }
}
/// <summary>Inserts an item into the deque at the specified location</summary>
/// <summary>Inserts an item into the deque at the specified location</summary> /// <param name="index">Index at which the item will be inserted</param>
/// <param name="index">Index at which the item will be inserted</param> /// <param name="value">Item that will be inserted</param>
/// <param name="value">Item that will be inserted</param> void IList.Insert(int index, object value) {
void IList.Insert(int index, object value) { verifyCompatibleObject(value);
verifyCompatibleObject(value); Insert(index, (TItem)value);
Insert(index, (TItem)value); }
}
/// <summary>Whether the deque has a fixed size</summary>
/// <summary>Whether the deque has a fixed size</summary> bool IList.IsFixedSize {
bool IList.IsFixedSize { get { return false; }
get { return false; } }
}
/// <summary>Whether the deque is read-only</summary>
/// <summary>Whether the deque is read-only</summary> bool IList.IsReadOnly {
bool IList.IsReadOnly { get { return false; }
get { return false; } }
}
/// <summary>Removes the specified item from the deque</summary>
/// <summary>Removes the specified item from the deque</summary> /// <param name="value">Item that will be removed from the deque</param>
/// <param name="value">Item that will be removed from the deque</param> void IList.Remove(object value) {
void IList.Remove(object value) { if(isCompatibleObject(value)) {
if(isCompatibleObject(value)) { Remove((TItem)value);
Remove((TItem)value); }
} }
}
/// <summary>Accesses an item in the deque by its index</summary>
/// <summary>Accesses an item in the deque by its index</summary> /// <param name="index">Index of the item that will be accessed</param>
/// <param name="index">Index of the item that will be accessed</param> /// <returns>The item at the specified index</returns>
/// <returns>The item at the specified index</returns> object IList.this[int index] {
object IList.this[int index] { get { return this[index]; }
get { return this[index]; } set {
set { verifyCompatibleObject(value);
verifyCompatibleObject(value); this[index] = (TItem)value;
this[index] = (TItem)value; }
} }
}
#endregion
#endregion
#region ICollection<ItemType> Members
#region ICollection<ItemType> Members
/// <summary>Adds an item into the deque</summary>
/// <summary>Adds an item into the deque</summary> /// <param name="item">Item that will be added to the deque</param>
/// <param name="item">Item that will be added to the deque</param> void ICollection<TItem>.Add(TItem item) {
void ICollection<TItem>.Add(TItem item) { AddLast(item);
AddLast(item); }
}
/// <summary>Whether the collection is read-only</summary>
/// <summary>Whether the collection is read-only</summary> bool ICollection<TItem>.IsReadOnly {
bool ICollection<TItem>.IsReadOnly { get { return false; }
get { return false; } }
}
#endregion
#endregion
#region ICollection Members
#region ICollection Members
/// <summary>Copies the contents of the deque into an array</summary>
/// <summary>Copies the contents of the deque into an array</summary> /// <param name="array">Array the contents of the deque will be copied into</param>
/// <param name="array">Array the contents of the deque will be copied into</param> /// <param name="index">Index at which writing into the array will begin</param>
/// <param name="index">Index at which writing into the array will begin</param> void ICollection.CopyTo(Array array, int index) {
void ICollection.CopyTo(Array array, int index) { if(!(array is TItem[])) {
if(!(array is TItem[])) { throw new ArgumentException("Incompatible array type", "array");
throw new ArgumentException("Incompatible array type", "array"); }
}
CopyTo((TItem[])array, index);
CopyTo((TItem[])array, index); }
}
/// <summary>Whether the deque is thread-synchronized</summary>
/// <summary>Whether the deque is thread-synchronized</summary> bool ICollection.IsSynchronized {
bool ICollection.IsSynchronized { get { return false; }
get { return false; } }
}
/// <summary>Synchronization root of the instance</summary>
/// <summary>Synchronization root of the instance</summary> object ICollection.SyncRoot {
object ICollection.SyncRoot { get { return this; }
get { return this; } }
}
#endregion
#endregion
}
}
} // namespace Nuclex.Support.Collections
} // namespace Nuclex.Support.Collections

View File

@ -1,259 +1,258 @@
#region CPL License #region Apache License 2.0
/* /*
Nuclex Framework Nuclex .NET Framework
Copyright (C) 2002-2017 Nuclex Development Labs Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
This library is free software; you can redistribute it and/or Licensed under the Apache License, Version 2.0 (the "License");
modify it under the terms of the IBM Common Public License as you may not use this file except in compliance with the License.
published by the IBM Corporation; either version 1.0 of the You may obtain a copy of the License at
License, or (at your option) any later version.
http://www.apache.org/licenses/LICENSE-2.0
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of Unless required by applicable law or agreed to in writing, software
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the distributed under the License is distributed on an "AS IS" BASIS,
IBM Common Public License for more details. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
You should have received a copy of the IBM Common Public limitations under the License.
License along with this library */
*/ #endregion // Apache License 2.0
#endregion
using System;
using System;
namespace Nuclex.Support.Collections {
namespace Nuclex.Support.Collections {
partial class Deque<TItem> {
partial class Deque<TItem> {
/// <summary>Removes all items from the deque</summary>
/// <summary>Removes all items from the deque</summary> public void Clear() {
public void Clear() { if(this.blocks.Count > 1) { // Are there multiple blocks?
if(this.blocks.Count > 1) { // Are there multiple blocks?
// Clear the items in the first block to avoid holding on to references
// Clear the items in the first block to avoid holding on to references // in memory unreachable to the user
// in memory unreachable to the user for(int index = this.firstBlockStartIndex; index < this.blockSize; ++index) {
for(int index = this.firstBlockStartIndex; index < this.blockSize; ++index) { this.blocks[0][index] = default(TItem);
this.blocks[0][index] = default(TItem); }
}
// Remove any other blocks
// Remove any other blocks this.blocks.RemoveRange(1, this.blocks.Count - 1);
this.blocks.RemoveRange(1, this.blocks.Count - 1);
} else { // Nope, only a single block exists
} else { // Nope, only a single block exists
// Clear the items in the block to release any reference we may be keeping alive
// Clear the items in the block to release any reference we may be keeping alive for(
for( int index = this.firstBlockStartIndex; index < this.lastBlockEndIndex; ++index
int index = this.firstBlockStartIndex; index < this.lastBlockEndIndex; ++index ) {
) { this.blocks[0][index] = default(TItem);
this.blocks[0][index] = default(TItem); }
}
}
}
// Reset the counters to restart the deque from scratch
// Reset the counters to restart the deque from scratch this.firstBlockStartIndex = 0;
this.firstBlockStartIndex = 0; this.lastBlockEndIndex = 0;
this.lastBlockEndIndex = 0; this.count = 0;
this.count = 0; #if DEBUG
#if DEBUG ++this.version;
++this.version; #endif
#endif }
}
/// <summary>Removes the specified item from the deque</summary>
/// <summary>Removes the specified item from the deque</summary> /// <param name="item">Item that will be removed from the deque</param>
/// <param name="item">Item that will be removed from the deque</param> /// <returns>True if the item was found and removed</returns>
/// <returns>True if the item was found and removed</returns> public bool Remove(TItem item) {
public bool Remove(TItem item) { int index = IndexOf(item);
int index = IndexOf(item); if(index == -1) {
if(index == -1) { return false;
return false; }
}
RemoveAt(index);
RemoveAt(index); #if DEBUG
#if DEBUG ++this.version;
++this.version; #endif
#endif return true;
return true; }
}
/// <summary>Removes the first item in the double-ended queue</summary>
/// <summary>Removes the first item in the double-ended queue</summary> public void RemoveFirst() {
public void RemoveFirst() { if(this.count == 0) {
if(this.count == 0) { throw new InvalidOperationException("Cannot remove items from empty deque");
throw new InvalidOperationException("Cannot remove items from empty deque"); }
}
// This is necessary to make sure the deque doesn't hold dead objects alive
// This is necessary to make sure the deque doesn't hold dead objects alive // in unreachable spaces of its memory.
// in unreachable spaces of its memory. this.blocks[0][this.firstBlockStartIndex] = default(TItem);
this.blocks[0][this.firstBlockStartIndex] = default(TItem);
// Cut off the item from the first block. If the block became empty and it's
// Cut off the item from the first block. If the block became empty and it's // not the last remaining block, remove it as well.
// not the last remaining block, remove it as well. ++this.firstBlockStartIndex;
++this.firstBlockStartIndex; if(this.firstBlockStartIndex >= this.blockSize) { // Block became empty
if(this.firstBlockStartIndex >= this.blockSize) { // Block became empty if(this.count > 1) { // Still more blocks in queue, remove block
if(this.count > 1) { // Still more blocks in queue, remove block this.blocks.RemoveAt(0);
this.blocks.RemoveAt(0); this.firstBlockStartIndex = 0;
this.firstBlockStartIndex = 0; } else { // Last block - do not remove
} else { // Last block - do not remove this.firstBlockStartIndex = 0;
this.firstBlockStartIndex = 0; this.lastBlockEndIndex = 0;
this.lastBlockEndIndex = 0; }
} }
} --this.count;
--this.count; #if DEBUG
#if DEBUG ++this.version;
++this.version; #endif
#endif }
}
/// <summary>Removes the last item in the double-ended queue</summary>
/// <summary>Removes the last item in the double-ended queue</summary> public void RemoveLast() {
public void RemoveLast() { if(this.count == 0) {
if(this.count == 0) { throw new InvalidOperationException("Cannot remove items from empty deque");
throw new InvalidOperationException("Cannot remove items from empty deque"); }
}
// This is necessary to make sure the deque doesn't hold dead objects alive
// This is necessary to make sure the deque doesn't hold dead objects alive // in unreachable spaces of its memory.
// in unreachable spaces of its memory. int lastBlock = this.blocks.Count - 1;
int lastBlock = this.blocks.Count - 1; this.blocks[lastBlock][this.lastBlockEndIndex - 1] = default(TItem);
this.blocks[lastBlock][this.lastBlockEndIndex - 1] = default(TItem);
// Cut off the last item in the last block. If the block became empty and it's
// Cut off the last item in the last block. If the block became empty and it's // not the last remaining block, remove it as well.
// not the last remaining block, remove it as well. --this.lastBlockEndIndex;
--this.lastBlockEndIndex; if(this.lastBlockEndIndex == 0) { // Block became empty
if(this.lastBlockEndIndex == 0) { // Block became empty if(this.count > 1) {
if(this.count > 1) { this.blocks.RemoveAt(lastBlock);
this.blocks.RemoveAt(lastBlock); this.lastBlockEndIndex = this.blockSize;
this.lastBlockEndIndex = this.blockSize; } else { // Last block - do not remove
} else { // Last block - do not remove this.firstBlockStartIndex = 0;
this.firstBlockStartIndex = 0; this.lastBlockEndIndex = 0;
this.lastBlockEndIndex = 0; }
} }
} --this.count;
--this.count; #if DEBUG
#if DEBUG ++this.version;
++this.version; #endif
#endif }
}
/// <summary>Removes the item at the specified index</summary>
/// <summary>Removes the item at the specified index</summary> /// <param name="index">Index of the item that will be removed</param>
/// <param name="index">Index of the item that will be removed</param> public void RemoveAt(int index) {
public void RemoveAt(int index) { int distanceToRightEnd = this.count - index;
int distanceToRightEnd = this.count - index; if(index < distanceToRightEnd) { // Are we closer to the left end?
if(index < distanceToRightEnd) { // Are we closer to the left end? removeFromLeft(index);
removeFromLeft(index); } else { // Nope, we're closer to the right end
} else { // Nope, we're closer to the right end removeFromRight(index);
removeFromRight(index); }
} #if DEBUG
#if DEBUG ++this.version;
++this.version; #endif
#endif }
}
/// <summary>
/// <summary> /// Removes an item from the left side of the queue by shifting all items that
/// Removes an item from the left side of the queue by shifting all items that /// come before it to the right by one
/// come before it to the right by one /// </summary>
/// </summary> /// <param name="index">Index of the item that will be removed</param>
/// <param name="index">Index of the item that will be removed</param> private void removeFromLeft(int index) {
private void removeFromLeft(int index) { if(index == 0) {
if(index == 0) { RemoveFirst();
RemoveFirst(); } else {
} else { int blockIndex, subIndex;
int blockIndex, subIndex; findIndex(index, out blockIndex, out subIndex);
findIndex(index, out blockIndex, out subIndex);
int firstBlock = 0;
int firstBlock = 0; int endIndex;
int endIndex;
if(blockIndex > firstBlock) {
if(blockIndex > firstBlock) { Array.Copy(
Array.Copy( this.blocks[blockIndex], 0,
this.blocks[blockIndex], 0, this.blocks[blockIndex], 1,
this.blocks[blockIndex], 1, subIndex
subIndex );
); this.blocks[blockIndex][0] = this.blocks[blockIndex - 1][this.blockSize - 1];
this.blocks[blockIndex][0] = this.blocks[blockIndex - 1][this.blockSize - 1];
for(int tempIndex = blockIndex - 1; tempIndex > firstBlock; --tempIndex) {
for(int tempIndex = blockIndex - 1; tempIndex > firstBlock; --tempIndex) { Array.Copy(
Array.Copy( this.blocks[tempIndex], 0,
this.blocks[tempIndex], 0, this.blocks[tempIndex], 1,
this.blocks[tempIndex], 1, this.blockSize - 1
this.blockSize - 1 );
); this.blocks[tempIndex][0] = this.blocks[tempIndex - 1][this.blockSize - 1];
this.blocks[tempIndex][0] = this.blocks[tempIndex - 1][this.blockSize - 1]; }
}
endIndex = this.blockSize - 1;
endIndex = this.blockSize - 1; } else {
} else { endIndex = subIndex;
endIndex = subIndex; }
}
Array.Copy(
Array.Copy( this.blocks[firstBlock], this.firstBlockStartIndex,
this.blocks[firstBlock], this.firstBlockStartIndex, this.blocks[firstBlock], this.firstBlockStartIndex + 1,
this.blocks[firstBlock], this.firstBlockStartIndex + 1, endIndex - this.firstBlockStartIndex
endIndex - this.firstBlockStartIndex );
);
if(this.firstBlockStartIndex == this.blockSize - 1) {
if(this.firstBlockStartIndex == this.blockSize - 1) { this.blocks.RemoveAt(0);
this.blocks.RemoveAt(0); this.firstBlockStartIndex = 0;
this.firstBlockStartIndex = 0; } else {
} else { this.blocks[0][this.firstBlockStartIndex] = default(TItem);
this.blocks[0][this.firstBlockStartIndex] = default(TItem); ++this.firstBlockStartIndex;
++this.firstBlockStartIndex; }
}
--this.count;
--this.count; }
} }
}
/// <summary>
/// <summary> /// Removes an item from the right side of the queue by shifting all items that
/// Removes an item from the right side of the queue by shifting all items that /// come after it to the left by one
/// come after it to the left by one /// </summary>
/// </summary> /// <param name="index">Index of the item that will be removed</param>
/// <param name="index">Index of the item that will be removed</param> private void removeFromRight(int index) {
private void removeFromRight(int index) { if(index == this.count - 1) {
if(index == this.count - 1) { RemoveLast();
RemoveLast(); } else {
} else { int blockIndex, subIndex;
int blockIndex, subIndex; findIndex(index, out blockIndex, out subIndex);
findIndex(index, out blockIndex, out subIndex);
int lastBlock = this.blocks.Count - 1;
int lastBlock = this.blocks.Count - 1; int startIndex;
int startIndex;
if(blockIndex < lastBlock) {
if(blockIndex < lastBlock) { Array.Copy(
Array.Copy( this.blocks[blockIndex], subIndex + 1,
this.blocks[blockIndex], subIndex + 1, this.blocks[blockIndex], subIndex,
this.blocks[blockIndex], subIndex, this.blockSize - subIndex - 1
this.blockSize - subIndex - 1 );
); this.blocks[blockIndex][this.blockSize - 1] = this.blocks[blockIndex + 1][0];
this.blocks[blockIndex][this.blockSize - 1] = this.blocks[blockIndex + 1][0];
for(int tempIndex = blockIndex + 1; tempIndex < lastBlock; ++tempIndex) {
for(int tempIndex = blockIndex + 1; tempIndex < lastBlock; ++tempIndex) { Array.Copy(
Array.Copy( this.blocks[tempIndex], 1,
this.blocks[tempIndex], 1, this.blocks[tempIndex], 0,
this.blocks[tempIndex], 0, this.blockSize - 1
this.blockSize - 1 );
); this.blocks[tempIndex][this.blockSize - 1] = this.blocks[tempIndex + 1][0];
this.blocks[tempIndex][this.blockSize - 1] = this.blocks[tempIndex + 1][0]; }
}
startIndex = 0;
startIndex = 0; } else {
} else { startIndex = subIndex;
startIndex = subIndex; }
}
Array.Copy(
Array.Copy( this.blocks[lastBlock], startIndex + 1,
this.blocks[lastBlock], startIndex + 1, this.blocks[lastBlock], startIndex,
this.blocks[lastBlock], startIndex, this.lastBlockEndIndex - startIndex - 1
this.lastBlockEndIndex - startIndex - 1 );
);
if(this.lastBlockEndIndex == 1) {
if(this.lastBlockEndIndex == 1) { this.blocks.RemoveAt(lastBlock);
this.blocks.RemoveAt(lastBlock); this.lastBlockEndIndex = this.blockSize;
this.lastBlockEndIndex = this.blockSize; } else {
} else { this.blocks[lastBlock][this.lastBlockEndIndex - 1] = default(TItem);
this.blocks[lastBlock][this.lastBlockEndIndex - 1] = default(TItem); --this.lastBlockEndIndex;
--this.lastBlockEndIndex; }
}
--this.count;
--this.count; }
} }
}
}
}
} // namespace Nuclex.Support.Collections
} // namespace Nuclex.Support.Collections

View File

@ -1,84 +1,83 @@
#region CPL License #region Apache License 2.0
/* /*
Nuclex Framework Nuclex .NET Framework
Copyright (C) 2002-2017 Nuclex Development Labs Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
This library is free software; you can redistribute it and/or Licensed under the Apache License, Version 2.0 (the "License");
modify it under the terms of the IBM Common Public License as you may not use this file except in compliance with the License.
published by the IBM Corporation; either version 1.0 of the You may obtain a copy of the License at
License, or (at your option) any later version.
http://www.apache.org/licenses/LICENSE-2.0
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of Unless required by applicable law or agreed to in writing, software
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the distributed under the License is distributed on an "AS IS" BASIS,
IBM Common Public License for more details. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
You should have received a copy of the IBM Common Public limitations under the License.
License along with this library */
*/ #endregion // Apache License 2.0
#endregion
using System;
using System;
namespace Nuclex.Support.Collections {
namespace Nuclex.Support.Collections {
partial class Deque<TItem> {
partial class Deque<TItem> {
/// <summary>
/// <summary> /// Determines the index of the first occurence of the specified item in the deque
/// Determines the index of the first occurence of the specified item in the deque /// </summary>
/// </summary> /// <param name="item">Item that will be located in the deque</param>
/// <param name="item">Item that will be located in the deque</param> /// <returns>The index of the item or -1 if it wasn't found</returns>
/// <returns>The index of the item or -1 if it wasn't found</returns> public int IndexOf(TItem item) {
public int IndexOf(TItem item) { if(this.blocks.Count == 1) { // Only one block to scan?
if(this.blocks.Count == 1) { // Only one block to scan? int length = this.lastBlockEndIndex - this.firstBlockStartIndex;
int length = this.lastBlockEndIndex - this.firstBlockStartIndex; int index = Array.IndexOf<TItem>(
int index = Array.IndexOf<TItem>( this.blocks[0], item, this.firstBlockStartIndex, length
this.blocks[0], item, this.firstBlockStartIndex, length );
);
// If we found something, we need to adjust its index so the first item in
// If we found something, we need to adjust its index so the first item in // the deque always appears at index 0 to the user
// the deque always appears at index 0 to the user if(index != -1) {
if(index != -1) { return (index - this.firstBlockStartIndex);
return (index - this.firstBlockStartIndex); } else {
} else { return -1;
return -1; }
} } else { // At least two blocks exist
} else { // At least two blocks exist
// Scan the first block for the item and if found, return the index
// Scan the first block for the item and if found, return the index int length = this.blockSize - this.firstBlockStartIndex;
int length = this.blockSize - this.firstBlockStartIndex; int index = Array.IndexOf<TItem>(
int index = Array.IndexOf<TItem>( this.blocks[0], item, this.firstBlockStartIndex, length
this.blocks[0], item, this.firstBlockStartIndex, length );
);
// If we found something, we need to adjust its index
// If we found something, we need to adjust its index if(index != -1) {
if(index != -1) { return (index - this.firstBlockStartIndex);
return (index - this.firstBlockStartIndex); }
}
int lastBlock = this.blocks.Count - 1;
int lastBlock = this.blocks.Count - 1; for(int tempIndex = 1; tempIndex < lastBlock; ++tempIndex) {
for(int tempIndex = 1; tempIndex < lastBlock; ++tempIndex) { index = Array.IndexOf<TItem>(
index = Array.IndexOf<TItem>( this.blocks[tempIndex], item, 0, this.blockSize
this.blocks[tempIndex], item, 0, this.blockSize );
); if(index != -1) {
if(index != -1) { return (index - this.firstBlockStartIndex + tempIndex * this.blockSize);
return (index - this.firstBlockStartIndex + tempIndex * this.blockSize); }
} }
}
// Nothing found, continue the search in the
// Nothing found, continue the search in the index = Array.IndexOf<TItem>(
index = Array.IndexOf<TItem>( this.blocks[lastBlock], item, 0, this.lastBlockEndIndex
this.blocks[lastBlock], item, 0, this.lastBlockEndIndex );
); if(index == -1) {
if(index == -1) { return -1;
return -1; } else {
} else { return (index - this.firstBlockStartIndex + lastBlock * this.blockSize);
return (index - this.firstBlockStartIndex + lastBlock * this.blockSize); }
}
}
} }
}
}
}
} // namespace Nuclex.Support.Collections
} // namespace Nuclex.Support.Collections

File diff suppressed because it is too large Load Diff

View File

@ -1,334 +1,333 @@
#region CPL License #region Apache License 2.0
/* /*
Nuclex Framework Nuclex .NET Framework
Copyright (C) 2002-2017 Nuclex Development Labs Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
This library is free software; you can redistribute it and/or Licensed under the Apache License, Version 2.0 (the "License");
modify it under the terms of the IBM Common Public License as you may not use this file except in compliance with the License.
published by the IBM Corporation; either version 1.0 of the You may obtain a copy of the License at
License, or (at your option) any later version.
http://www.apache.org/licenses/LICENSE-2.0
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of Unless required by applicable law or agreed to in writing, software
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the distributed under the License is distributed on an "AS IS" BASIS,
IBM Common Public License for more details. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
You should have received a copy of the IBM Common Public limitations under the License.
License along with this library */
*/ #endregion // Apache License 2.0
#endregion
using System;
using System; using System.Collections.Generic;
using System.Collections.Generic; using System.Collections;
using System.Collections;
namespace Nuclex.Support.Collections {
namespace Nuclex.Support.Collections {
/// <summary>A double-ended queue that allocates memory in blocks</summary>
/// <summary>A double-ended queue that allocates memory in blocks</summary> /// <typeparam name="TItem">Type of the items being stored in the queue</typeparam>
/// <typeparam name="TItem">Type of the items being stored in the queue</typeparam> /// <remarks>
/// <remarks> /// <para>
/// <para> /// The double-ended queue allows items to be appended to either side of the queue
/// The double-ended queue allows items to be appended to either side of the queue /// without a hefty toll on performance. Like its namesake in C++, it is implemented
/// without a hefty toll on performance. Like its namesake in C++, it is implemented /// using multiple arrays.
/// using multiple arrays. /// </para>
/// </para> /// <para>
/// <para> /// Therefore, it's not only good at coping with lists that are modified at their
/// Therefore, it's not only good at coping with lists that are modified at their /// beginning, but also at handling huge data sets since enlarging the deque doesn't
/// beginning, but also at handling huge data sets since enlarging the deque doesn't /// require items to be copied around and it still can be accessed by index.
/// require items to be copied around and it still can be accessed by index. /// </para>
/// </para> /// </remarks>
/// </remarks> public partial class Deque<TItem> : IList<TItem>, IList {
public partial class Deque<TItem> : IList<TItem>, IList {
#region class Enumerator
#region class Enumerator
/// <summary>Enumerates over the items in a deque</summary>
/// <summary>Enumerates over the items in a deque</summary> private class Enumerator : IEnumerator<TItem>, IEnumerator {
private class Enumerator : IEnumerator<TItem>, IEnumerator {
/// <summary>Initializes a new deque enumerator</summary>
/// <summary>Initializes a new deque enumerator</summary> /// <param name="deque">Deque whose items will be enumerated</param>
/// <param name="deque">Deque whose items will be enumerated</param> public Enumerator(Deque<TItem> deque) {
public Enumerator(Deque<TItem> deque) { this.deque = deque;
this.deque = deque; this.blockSize = this.deque.blockSize;
this.blockSize = this.deque.blockSize; this.lastBlock = this.deque.blocks.Count - 1;
this.lastBlock = this.deque.blocks.Count - 1; this.lastBlockEndIndex = this.deque.lastBlockEndIndex - 1;
this.lastBlockEndIndex = this.deque.lastBlockEndIndex - 1;
Reset();
Reset(); }
}
/// <summary>Immediately releases all resources owned by the instance</summary>
/// <summary>Immediately releases all resources owned by the instance</summary> public void Dispose() {
public void Dispose() { this.deque = null;
this.deque = null; this.currentBlock = null;
this.currentBlock = null; }
}
/// <summary>The item at the enumerator's current position</summary>
/// <summary>The item at the enumerator's current position</summary> public TItem Current {
public TItem Current { get {
get { #if DEBUG
#if DEBUG checkVersion();
checkVersion(); #endif
#endif if (this.currentBlock == null) {
if (this.currentBlock == null) { throw new InvalidOperationException("Enumerator is not on a valid position");
throw new InvalidOperationException("Enumerator is not on a valid position"); }
}
return this.currentBlock[this.subIndex];
return this.currentBlock[this.subIndex]; }
} }
}
/// <summary>Advances the enumerator to the next item</summary>
/// <summary>Advances the enumerator to the next item</summary> /// <returns>True if there was a next item</returns>
/// <returns>True if there was a next item</returns> public bool MoveNext() {
public bool MoveNext() {
#if DEBUG
#if DEBUG checkVersion();
checkVersion(); #endif
#endif
// If we haven't reached the last block yet
// If we haven't reached the last block yet if (this.currentBlockIndex < this.lastBlock) {
if (this.currentBlockIndex < this.lastBlock) {
// Advance to the next item. If the end of the current block is reached,
// Advance to the next item. If the end of the current block is reached, // go to the next block's first item
// go to the next block's first item ++this.subIndex;
++this.subIndex; if (this.subIndex >= this.blockSize) {
if (this.subIndex >= this.blockSize) { ++this.currentBlockIndex;
++this.currentBlockIndex; this.currentBlock = this.deque.blocks[this.currentBlockIndex];
this.currentBlock = this.deque.blocks[this.currentBlockIndex]; if (this.currentBlockIndex == 0) {
if (this.currentBlockIndex == 0) { this.subIndex = this.deque.firstBlockStartIndex;
this.subIndex = this.deque.firstBlockStartIndex; } else {
} else { this.subIndex = 0;
this.subIndex = 0; }
} }
}
// Item found. If the current block wasn't the last block, an item *has*
// Item found. If the current block wasn't the last block, an item *has* // to follow since otherwise, no further blocks would exist!
// to follow since otherwise, no further blocks would exist! return true;
return true;
} else { // We in or beyond the last block
} else { // We in or beyond the last block
// Are there any items left to advance to?
// Are there any items left to advance to? if (this.subIndex < this.lastBlockEndIndex) {
if (this.subIndex < this.lastBlockEndIndex) { ++this.subIndex;
++this.subIndex; return true;
return true; } else { // Nope, we've reached the end of the deque
} else { // Nope, we've reached the end of the deque this.currentBlock = null;
this.currentBlock = null; return false;
return false; }
}
}
}
}
}
/// <summary>Resets the enumerator to its initial position</summary>
/// <summary>Resets the enumerator to its initial position</summary> public void Reset() {
public void Reset() { this.currentBlock = null;
this.currentBlock = null; this.currentBlockIndex = -1;
this.currentBlockIndex = -1; this.subIndex = this.deque.blockSize - 1;
this.subIndex = this.deque.blockSize - 1; #if DEBUG
#if DEBUG this.expectedVersion = this.deque.version;
this.expectedVersion = this.deque.version; #endif
#endif }
}
/// <summary>The item at the enumerator's current position</summary>
/// <summary>The item at the enumerator's current position</summary> object IEnumerator.Current {
object IEnumerator.Current { get { return Current; }
get { return Current; } }
}
#if DEBUG
#if DEBUG /// <summary>Ensures that the deque has not changed</summary>
/// <summary>Ensures that the deque has not changed</summary> private void checkVersion() {
private void checkVersion() { if(this.expectedVersion != this.deque.version)
if(this.expectedVersion != this.deque.version) throw new InvalidOperationException("Deque has been modified");
throw new InvalidOperationException("Deque has been modified"); }
} #endif
#endif
/// <summary>Deque the enumerator belongs to</summary>
/// <summary>Deque the enumerator belongs to</summary> private Deque<TItem> deque;
private Deque<TItem> deque; /// <summary>Size of the blocks in the deque</summary>
/// <summary>Size of the blocks in the deque</summary> private int blockSize;
private int blockSize; /// <summary>Index of the last block in the deque</summary>
/// <summary>Index of the last block in the deque</summary> private int lastBlock;
private int lastBlock; /// <summary>End index of the items in the deque's last block</summary>
/// <summary>End index of the items in the deque's last block</summary> private int lastBlockEndIndex;
private int lastBlockEndIndex;
/// <summary>Index of the block the enumerator currently is in</summary>
/// <summary>Index of the block the enumerator currently is in</summary> private int currentBlockIndex;
private int currentBlockIndex; /// <summary>Reference to the block being enumerated</summary>
/// <summary>Reference to the block being enumerated</summary> private TItem[] currentBlock;
private TItem[] currentBlock; /// <summary>Index in the current block</summary>
/// <summary>Index in the current block</summary> private int subIndex;
private int subIndex; #if DEBUG
#if DEBUG /// <summary>Version the deque is expected to have</summary>
/// <summary>Version the deque is expected to have</summary> private int expectedVersion;
private int expectedVersion; #endif
#endif }
}
#endregion // class Enumerator
#endregion // class Enumerator
/// <summary>Initializes a new deque</summary>
/// <summary>Initializes a new deque</summary> public Deque() : this(512) { }
public Deque() : this(512) { }
/// <summary>Initializes a new deque using the specified block size</summary>
/// <summary>Initializes a new deque using the specified block size</summary> /// <param name="blockSize">Size of the individual memory blocks used</param>
/// <param name="blockSize">Size of the individual memory blocks used</param> public Deque(int blockSize) {
public Deque(int blockSize) { this.blockSize = blockSize;
this.blockSize = blockSize;
this.blocks = new List<TItem[]>();
this.blocks = new List<TItem[]>(); this.blocks.Add(new TItem[this.blockSize]);
this.blocks.Add(new TItem[this.blockSize]); }
}
/// <summary>Number of items contained in the double ended queue</summary>
/// <summary>Number of items contained in the double ended queue</summary> public int Count {
public int Count { get { return this.count; }
get { return this.count; } }
}
/// <summary>Accesses an item by its index</summary>
/// <summary>Accesses an item by its index</summary> /// <param name="index">Index of the item that will be accessed</param>
/// <param name="index">Index of the item that will be accessed</param> /// <returns>The item at the specified index</returns>
/// <returns>The item at the specified index</returns> public TItem this[int index] {
public TItem this[int index] { get {
get { int blockIndex, subIndex;
int blockIndex, subIndex; findIndex(index, out blockIndex, out subIndex);
findIndex(index, out blockIndex, out subIndex);
return this.blocks[blockIndex][subIndex];
return this.blocks[blockIndex][subIndex]; }
} set {
set { int blockIndex, subIndex;
int blockIndex, subIndex; findIndex(index, out blockIndex, out subIndex);
findIndex(index, out blockIndex, out subIndex);
this.blocks[blockIndex][subIndex] = value;
this.blocks[blockIndex][subIndex] = value; }
} }
}
/// <summary>The first item in the double-ended queue</summary>
/// <summary>The first item in the double-ended queue</summary> public TItem First {
public TItem First { get {
get { if (this.count == 0) {
if (this.count == 0) { throw new InvalidOperationException("The deque is empty");
throw new InvalidOperationException("The deque is empty"); }
} return this.blocks[0][this.firstBlockStartIndex];
return this.blocks[0][this.firstBlockStartIndex]; }
} }
}
/// <summary>The last item in the double-ended queue</summary>
/// <summary>The last item in the double-ended queue</summary> public TItem Last {
public TItem Last { get {
get { if (this.count == 0) {
if (this.count == 0) { throw new InvalidOperationException("The deque is empty");
throw new InvalidOperationException("The deque is empty"); }
} return this.blocks[this.blocks.Count - 1][this.lastBlockEndIndex - 1];
return this.blocks[this.blocks.Count - 1][this.lastBlockEndIndex - 1]; }
} }
}
/// <summary>Determines whether the deque contains the specified item</summary>
/// <summary>Determines whether the deque contains the specified item</summary> /// <param name="item">Item the deque will be scanned for</param>
/// <param name="item">Item the deque will be scanned for</param> /// <returns>True if the deque contains the item, false otherwise</returns>
/// <returns>True if the deque contains the item, false otherwise</returns> public bool Contains(TItem item) {
public bool Contains(TItem item) { return (IndexOf(item) != -1);
return (IndexOf(item) != -1); }
}
/// <summary>Copies the contents of the deque into an array</summary>
/// <summary>Copies the contents of the deque into an array</summary> /// <param name="array">Array the contents of the deque will be copied into</param>
/// <param name="array">Array the contents of the deque will be copied into</param> /// <param name="arrayIndex">Array index the deque contents will begin at</param>
/// <param name="arrayIndex">Array index the deque contents will begin at</param> public void CopyTo(TItem[] array, int arrayIndex) {
public void CopyTo(TItem[] array, int arrayIndex) { if (this.count > (array.Length - arrayIndex)) {
if (this.count > (array.Length - arrayIndex)) { throw new ArgumentException(
throw new ArgumentException( "Array too small to hold the collection items starting at the specified index"
"Array too small to hold the collection items starting at the specified index" );
); }
}
if (this.blocks.Count == 1) { // Does only one block exist?
if (this.blocks.Count == 1) { // Does only one block exist?
// Copy the one and only block there is
// Copy the one and only block there is Array.Copy(
Array.Copy( this.blocks[0], this.firstBlockStartIndex,
this.blocks[0], this.firstBlockStartIndex, array, arrayIndex,
array, arrayIndex, this.lastBlockEndIndex - this.firstBlockStartIndex
this.lastBlockEndIndex - this.firstBlockStartIndex );
);
} else { // Multiple blocks exist
} else { // Multiple blocks exist
// Copy the first block which is filled from the start index to its end
// Copy the first block which is filled from the start index to its end int length = this.blockSize - this.firstBlockStartIndex;
int length = this.blockSize - this.firstBlockStartIndex; Array.Copy(
Array.Copy( this.blocks[0], this.firstBlockStartIndex,
this.blocks[0], this.firstBlockStartIndex, array, arrayIndex,
array, arrayIndex, length
length );
); arrayIndex += length;
arrayIndex += length;
// Copy all intermediate blocks (if there are any). These are completely filled
// Copy all intermediate blocks (if there are any). These are completely filled int lastBlock = this.blocks.Count - 1;
int lastBlock = this.blocks.Count - 1; for (int index = 1; index < lastBlock; ++index) {
for (int index = 1; index < lastBlock; ++index) { Array.Copy(
Array.Copy( this.blocks[index], 0,
this.blocks[index], 0, array, arrayIndex,
array, arrayIndex, this.blockSize
this.blockSize );
); arrayIndex += this.blockSize;
arrayIndex += this.blockSize; }
}
// Copy the final block which is filled from the beginning to the end index
// Copy the final block which is filled from the beginning to the end index Array.Copy(
Array.Copy( this.blocks[lastBlock], 0,
this.blocks[lastBlock], 0, array, arrayIndex,
array, arrayIndex, this.lastBlockEndIndex
this.lastBlockEndIndex );
);
}
} }
}
/// <summary>Obtains a new enumerator for the contents of the deque</summary>
/// <summary>Obtains a new enumerator for the contents of the deque</summary> /// <returns>The new enumerator</returns>
/// <returns>The new enumerator</returns> public IEnumerator<TItem> GetEnumerator() {
public IEnumerator<TItem> GetEnumerator() { return new Enumerator(this);
return new Enumerator(this); }
}
/// <summary>Calculates the block index and local sub index of an entry</summary>
/// <summary>Calculates the block index and local sub index of an entry</summary> /// <param name="index">Index of the entry that will be located</param>
/// <param name="index">Index of the entry that will be located</param> /// <param name="blockIndex">Index of the block the entry is contained in</param>
/// <param name="blockIndex">Index of the block the entry is contained in</param> /// <param name="subIndex">Local sub index of the entry within the block</param>
/// <param name="subIndex">Local sub index of the entry within the block</param> private void findIndex(int index, out int blockIndex, out int subIndex) {
private void findIndex(int index, out int blockIndex, out int subIndex) { if ((index < 0) || (index >= this.count)) {
if ((index < 0) || (index >= this.count)) { throw new ArgumentOutOfRangeException("Index out of range", "index");
throw new ArgumentOutOfRangeException("Index out of range", "index"); }
}
index += this.firstBlockStartIndex;
index += this.firstBlockStartIndex; blockIndex = Math.DivRem(index, this.blockSize, out subIndex);
blockIndex = Math.DivRem(index, this.blockSize, out subIndex); }
}
/// <summary>
/// <summary> /// Determines whether the provided object can be placed in the deque
/// Determines whether the provided object can be placed in the deque /// </summary>
/// </summary> /// <param name="value">Value that will be checked for compatibility</param>
/// <param name="value">Value that will be checked for compatibility</param> /// <returns>True if the value can be placed in the deque</returns>
/// <returns>True if the value can be placed in the deque</returns> private static bool isCompatibleObject(object value) {
private static bool isCompatibleObject(object value) { return ((value is TItem) || ((value == null) && !typeof(TItem).IsValueType));
return ((value is TItem) || ((value == null) && !typeof(TItem).IsValueType)); }
}
/// <summary>Verifies that the provided object matches the deque's type</summary>
/// <summary>Verifies that the provided object matches the deque's type</summary> /// <param name="value">Value that will be checked for compatibility</param>
/// <param name="value">Value that will be checked for compatibility</param> private static void verifyCompatibleObject(object value) {
private static void verifyCompatibleObject(object value) { if (!isCompatibleObject(value)) {
if (!isCompatibleObject(value)) { throw new ArgumentException("Value does not match the deque's type", "value");
throw new ArgumentException("Value does not match the deque's type", "value"); }
} }
}
/// <summary>Number if items currently stored in the deque</summary>
/// <summary>Number if items currently stored in the deque</summary> private int count;
private int count; /// <summary>Size of a single deque block</summary>
/// <summary>Size of a single deque block</summary> private int blockSize;
private int blockSize; /// <summary>Memory blocks being used to store the deque's data</summary>
/// <summary>Memory blocks being used to store the deque's data</summary> private List<TItem[]> blocks;
private List<TItem[]> blocks; /// <summary>Starting index of data in the first block</summary>
/// <summary>Starting index of data in the first block</summary> private int firstBlockStartIndex;
private int firstBlockStartIndex; /// <summary>End index of data in the last block</summary>
/// <summary>End index of data in the last block</summary> private int lastBlockEndIndex;
private int lastBlockEndIndex; #if DEBUG
#if DEBUG /// <summary>Used to detect when enumerators go out of sync</summary>
/// <summary>Used to detect when enumerators go out of sync</summary> private int version;
private int version; #endif
#endif
}
}
} // namespace Nuclex.Support.Collections
} // namespace Nuclex.Support.Collections

View File

@ -1,137 +1,136 @@
#region CPL License #region Apache License 2.0
/* /*
Nuclex Framework Nuclex .NET Framework
Copyright (C) 2002-2017 Nuclex Development Labs Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
This library is free software; you can redistribute it and/or Licensed under the Apache License, Version 2.0 (the "License");
modify it under the terms of the IBM Common Public License as you may not use this file except in compliance with the License.
published by the IBM Corporation; either version 1.0 of the You may obtain a copy of the License at
License, or (at your option) any later version.
http://www.apache.org/licenses/LICENSE-2.0
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of Unless required by applicable law or agreed to in writing, software
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the distributed under the License is distributed on an "AS IS" BASIS,
IBM Common Public License for more details. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
You should have received a copy of the IBM Common Public limitations under the License.
License along with this library */
*/ #endregion // Apache License 2.0
#endregion
using System;
using System; using System.Collections.Generic;
using System.Collections.Generic; using System.IO;
using System.IO;
#if UNITTEST
#if UNITTEST
using NUnit.Framework;
using NUnit.Framework;
namespace Nuclex.Support.Collections {
namespace Nuclex.Support.Collections {
/// <summary>Unit Test for the IList extension methods</summary>
/// <summary>Unit Test for the IList extension methods</summary> [TestFixture]
[TestFixture] internal class IListExtensionsTest {
internal class IListExtensionsTest {
/// <summary>Tests whether the insertion sort algorithm sorts a list correctly</summary>
/// <summary>Tests whether the insertion sort algorithm sorts a list correctly</summary> [Test]
[Test] public void InsertionSortCanSortWholeList() {
public void InsertionSortCanSortWholeList() { var testList = new List<int>(capacity: 5) { 1, 5, 2, 4, 3 };
var testList = new List<int>(capacity: 5) { 1, 5, 2, 4, 3 }; var testListAsIList = (IList<int>)testList;
var testListAsIList = (IList<int>)testList;
testListAsIList.InsertionSort();
testListAsIList.InsertionSort();
CollectionAssert.AreEqual(
CollectionAssert.AreEqual( new List<int>(capacity: 5) { 1, 2, 3, 4, 5 },
new List<int>(capacity: 5) { 1, 2, 3, 4, 5 }, testList
testList );
); }
}
/// <summary>Tests whether the insertion sort algorithm works on big lists</summary>
/// <summary>Tests whether the insertion sort algorithm works on big lists</summary> [Test]
[Test] public void InsertionSortCanSortBigList() {
public void InsertionSortCanSortBigList() { const int ListSize = 16384;
const int ListSize = 16384;
var testList = new List<int>(capacity: ListSize);
var testList = new List<int>(capacity: ListSize); {
{ var random = new Random();
var random = new Random(); for(int index = 0; index < ListSize; ++index) {
for(int index = 0; index < ListSize; ++index) { testList.Add(random.Next());
testList.Add(random.Next()); }
} }
}
var testListAsIList = (IList<int>)testList;
var testListAsIList = (IList<int>)testList; testListAsIList.InsertionSort();
testListAsIList.InsertionSort();
for(int index = 1; index < ListSize; ++index) {
for(int index = 1; index < ListSize; ++index) { Assert.LessOrEqual(testListAsIList[index - 1], testListAsIList[index]);
Assert.LessOrEqual(testListAsIList[index - 1], testListAsIList[index]); }
} }
}
/// <summary>Tests whether the insertion sort algorithm respects custom boundaries</summary>
/// <summary>Tests whether the insertion sort algorithm respects custom boundaries</summary> [Test]
[Test] public void InsertionSortCanSortListSegment() {
public void InsertionSortCanSortListSegment() { var testList = new List<int>(capacity: 7) { 9, 1, 5, 2, 4, 3, 0 };
var testList = new List<int>(capacity: 7) { 9, 1, 5, 2, 4, 3, 0 }; var testListAsIList = (IList<int>)testList;
var testListAsIList = (IList<int>)testList;
testListAsIList.InsertionSort(1, 5, Comparer<int>.Default);
testListAsIList.InsertionSort(1, 5, Comparer<int>.Default);
CollectionAssert.AreEqual(
CollectionAssert.AreEqual( new List<int>(capacity: 7) { 9, 1, 2, 3, 4, 5, 0 },
new List<int>(capacity: 7) { 9, 1, 2, 3, 4, 5, 0 }, testList
testList );
); }
}
/// <summary>Tests whether the quicksort algorithm sorts a list correctly</summary>
/// <summary>Tests whether the quicksort algorithm sorts a list correctly</summary> [Test]
[Test] public void QuickSortCanSortWholeList() {
public void QuickSortCanSortWholeList() { var testList = new List<int>(capacity: 5) { 1, 5, 2, 4, 3 };
var testList = new List<int>(capacity: 5) { 1, 5, 2, 4, 3 }; var testListAsIList = (IList<int>)testList;
var testListAsIList = (IList<int>)testList;
testListAsIList.QuickSort();
testListAsIList.QuickSort();
CollectionAssert.AreEqual(
CollectionAssert.AreEqual( new List<int>(capacity: 5) { 1, 2, 3, 4, 5 },
new List<int>(capacity: 5) { 1, 2, 3, 4, 5 }, testList
testList );
); }
}
/// <summary>Tests whether the quicksort algorithm works on big lists</summary>
/// <summary>Tests whether the quicksort algorithm works on big lists</summary> [Test]
[Test] public void QuickSortCanSortBigList() {
public void QuickSortCanSortBigList() { const int ListSize = 16384;
const int ListSize = 16384;
var testList = new List<int>(capacity: ListSize);
var testList = new List<int>(capacity: ListSize); {
{ var random = new Random();
var random = new Random(); for(int index = 0; index < ListSize; ++index) {
for(int index = 0; index < ListSize; ++index) { testList.Add(random.Next());
testList.Add(random.Next()); }
} }
}
var testListAsIList = (IList<int>)testList;
var testListAsIList = (IList<int>)testList; testListAsIList.QuickSort();
testListAsIList.QuickSort();
for(int index = 1; index < ListSize; ++index) {
for(int index = 1; index < ListSize; ++index) { Assert.LessOrEqual(testListAsIList[index - 1], testListAsIList[index]);
Assert.LessOrEqual(testListAsIList[index - 1], testListAsIList[index]); }
} }
}
/// <summary>Tests whether the quicksort algorithm respects custom boundaries</summary>
/// <summary>Tests whether the quicksort algorithm respects custom boundaries</summary> [Test]
[Test] public void QuickSortCanSortListSegment() {
public void QuickSortCanSortListSegment() { var testList = new List<int>(capacity: 7) { 9, 1, 5, 2, 4, 3, 0 };
var testList = new List<int>(capacity: 7) { 9, 1, 5, 2, 4, 3, 0 }; var testListAsIList = (IList<int>)testList;
var testListAsIList = (IList<int>)testList;
testListAsIList.QuickSort(1, 5, Comparer<int>.Default);
testListAsIList.QuickSort(1, 5, Comparer<int>.Default);
CollectionAssert.AreEqual(
CollectionAssert.AreEqual( new List<int>(capacity: 7) { 9, 1, 2, 3, 4, 5, 0 },
new List<int>(capacity: 7) { 9, 1, 2, 3, 4, 5, 0 }, testList
testList );
); }
}
}
}
} // namespace Nuclex.Support.Collections
} // namespace Nuclex.Support.Collections
#endif // UNITTEST
#endif // UNITTEST

View File

@ -1,219 +1,218 @@
#region CPL License #region Apache License 2.0
/* /*
Nuclex Framework Nuclex .NET Framework
Copyright (C) 2002-2017 Nuclex Development Labs Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
This library is free software; you can redistribute it and/or Licensed under the Apache License, Version 2.0 (the "License");
modify it under the terms of the IBM Common Public License as you may not use this file except in compliance with the License.
published by the IBM Corporation; either version 1.0 of the You may obtain a copy of the License at
License, or (at your option) any later version.
http://www.apache.org/licenses/LICENSE-2.0
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of Unless required by applicable law or agreed to in writing, software
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the distributed under the License is distributed on an "AS IS" BASIS,
IBM Common Public License for more details. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
You should have received a copy of the IBM Common Public limitations under the License.
License along with this library */
*/ #endregion // Apache License 2.0
#endregion
using System;
using System; using System.Collections.Generic;
using System.Collections.Generic;
namespace Nuclex.Support.Collections {
namespace Nuclex.Support.Collections {
/// <summary>Extension methods for the IList interface</summary>
/// <summary>Extension methods for the IList interface</summary> public static class ListExtensions {
public static class ListExtensions {
#region struct Partition
#region struct Partition
/// <summary>
/// <summary> /// Stores the left and right index of a partition for the quicksort algorithm
/// Stores the left and right index of a partition for the quicksort algorithm /// </summary>
/// </summary> private struct Partition {
private struct Partition {
/// <summary>
/// <summary> /// Initializes a new partition using the specified left and right index
/// Initializes a new partition using the specified left and right index /// </summary>
/// </summary> /// <param name="leftmostIndex">Index of the leftmost element in the partition</param>
/// <param name="leftmostIndex">Index of the leftmost element in the partition</param> /// <param name="rightmostIndex">Index of the rightmost element in the partition</param>
/// <param name="rightmostIndex">Index of the rightmost element in the partition</param> public Partition(int leftmostIndex, int rightmostIndex) {
public Partition(int leftmostIndex, int rightmostIndex) { this.LeftmostIndex = leftmostIndex;
this.LeftmostIndex = leftmostIndex; this.RightmostIndex = rightmostIndex;
this.RightmostIndex = rightmostIndex; }
}
/// <summary>Index of the leftmost element in the partition</summary>
/// <summary>Index of the leftmost element in the partition</summary> public int LeftmostIndex;
public int LeftmostIndex; /// <summary>Index of the rightmost element in the partition</summary>
/// <summary>Index of the rightmost element in the partition</summary> public int RightmostIndex;
public int RightmostIndex;
}
}
#endregion // struct Partition
#endregion // struct Partition
/// <summary>
/// <summary> /// Sorts a subset of the elements in an IList&lt;T&gt; using the insertion sort algorithm
/// Sorts a subset of the elements in an IList&lt;T&gt; using the insertion sort algorithm /// </summary>
/// </summary> /// <typeparam name="TElement">Type of elements the list contains</typeparam>
/// <typeparam name="TElement">Type of elements the list contains</typeparam> /// <param name="list">List in which a subset will be sorted</param>
/// <param name="list">List in which a subset will be sorted</param> /// <param name="startIndex">Index at which the sorting process will begin</param>
/// <param name="startIndex">Index at which the sorting process will begin</param> /// <param name="count">Index one past the last element that will be sorted</param>
/// <param name="count">Index one past the last element that will be sorted</param> /// <param name="comparer">Comparison function to use for comparing list elements</param>
/// <param name="comparer">Comparison function to use for comparing list elements</param> public static void InsertionSort<TElement>(
public static void InsertionSort<TElement>( this IList<TElement> list, int startIndex, int count, IComparer<TElement> comparer
this IList<TElement> list, int startIndex, int count, IComparer<TElement> comparer ) {
) { int rightIndex = startIndex;
int rightIndex = startIndex;
int lastIndex = startIndex + count - 1;
int lastIndex = startIndex + count - 1; for(int index = startIndex + 1; index <= lastIndex; ++index) {
for(int index = startIndex + 1; index <= lastIndex; ++index) { TElement temp = list[index];
TElement temp = list[index];
while(rightIndex >= startIndex) {
while(rightIndex >= startIndex) { if(comparer.Compare(list[rightIndex], temp) < 0) {
if(comparer.Compare(list[rightIndex], temp) < 0) { break;
break; }
}
list[rightIndex + 1] = list[rightIndex];
list[rightIndex + 1] = list[rightIndex]; --rightIndex;
--rightIndex; }
}
list[rightIndex + 1] = temp;
list[rightIndex + 1] = temp;
rightIndex = index;
rightIndex = index; }
} }
}
/// <summary>
/// <summary> /// Sorts all the elements in an IList&lt;T&gt; using the insertion sort algorithm
/// Sorts all the elements in an IList&lt;T&gt; using the insertion sort algorithm /// </summary>
/// </summary> /// <typeparam name="TElement">Type of elements the list contains</typeparam>
/// <typeparam name="TElement">Type of elements the list contains</typeparam> /// <param name="list">List in which a subset will be sorted</param>
/// <param name="list">List in which a subset will be sorted</param> /// <param name="comparer">Comparison function to use for comparing list elements</param>
/// <param name="comparer">Comparison function to use for comparing list elements</param> public static void InsertionSort<TElement>(
public static void InsertionSort<TElement>( this IList<TElement> list, IComparer<TElement> comparer
this IList<TElement> list, IComparer<TElement> comparer ) {
) { InsertionSort(list, 0, list.Count, comparer);
InsertionSort(list, 0, list.Count, comparer); }
}
/// <summary>
/// <summary> /// Sorts all the elements in an IList&lt;T&gt; using the insertion sort algorithm
/// Sorts all the elements in an IList&lt;T&gt; using the insertion sort algorithm /// </summary>
/// </summary> /// <typeparam name="TElement">Type of elements the list contains</typeparam>
/// <typeparam name="TElement">Type of elements the list contains</typeparam> /// <param name="list">List in which a subset will be sorted</param>
/// <param name="list">List in which a subset will be sorted</param> public static void InsertionSort<TElement>(this IList<TElement> list) {
public static void InsertionSort<TElement>(this IList<TElement> list) { InsertionSort(list, 0, list.Count, Comparer<TElement>.Default);
InsertionSort(list, 0, list.Count, Comparer<TElement>.Default); }
}
/// <summary>
/// <summary> /// Sorts all the elements in an IList&lt;T&gt; using the quicksort algorithm
/// Sorts all the elements in an IList&lt;T&gt; using the quicksort algorithm /// </summary>
/// </summary> /// <typeparam name="TElement">Type of elements the list contains</typeparam>
/// <typeparam name="TElement">Type of elements the list contains</typeparam> /// <param name="list">List in which a subset will be sorted</param>
/// <param name="list">List in which a subset will be sorted</param> /// <param name="startIndex">Index at which the sorting process will begin</param>
/// <param name="startIndex">Index at which the sorting process will begin</param> /// <param name="count">Index one past the last element that will be sorted</param>
/// <param name="count">Index one past the last element that will be sorted</param> /// <param name="comparer">Comparison function to use for comparing list elements</param>
/// <param name="comparer">Comparison function to use for comparing list elements</param> public static void QuickSort<TElement>(
public static void QuickSort<TElement>( this IList<TElement> list, int startIndex, int count, IComparer<TElement> comparer
this IList<TElement> list, int startIndex, int count, IComparer<TElement> comparer ) {
) { var remainingPartitions = new Stack<Partition>();
var remainingPartitions = new Stack<Partition>();
int lastIndex = startIndex + count - 1;
int lastIndex = startIndex + count - 1; for(; ; ) {
for(; ; ) { int pivotIndex = quicksortPartition(list, startIndex, lastIndex, comparer);
int pivotIndex = quicksortPartition(list, startIndex, lastIndex, comparer);
// This block just queues the next partitions left of the pivot point and right
// This block just queues the next partitions left of the pivot point and right // of the pivot point (if they contain at least 2 elements). It's fattened up
// of the pivot point (if they contain at least 2 elements). It's fattened up // a bit by trying to forego the stack and adjusting the startIndex/lastIndex
// a bit by trying to forego the stack and adjusting the startIndex/lastIndex // directly where it's clear the next loop can process these partitions.
// directly where it's clear the next loop can process these partitions. if(pivotIndex - 1 > startIndex) { // Are the elements to sort right of the pivot?
if(pivotIndex - 1 > startIndex) { // Are the elements to sort right of the pivot? if(pivotIndex + 1 < lastIndex) { // Are the elements left of the pivot as well?
if(pivotIndex + 1 < lastIndex) { // Are the elements left of the pivot as well? remainingPartitions.Push(new Partition(startIndex, pivotIndex - 1));
remainingPartitions.Push(new Partition(startIndex, pivotIndex - 1)); startIndex = pivotIndex + 1;
startIndex = pivotIndex + 1; } else { // Elements to sort are only right of the pivot
} else { // Elements to sort are only right of the pivot lastIndex = pivotIndex - 1;
lastIndex = pivotIndex - 1; }
} } else if(pivotIndex + 1 < lastIndex) { // Are elements to sort only left of the pivot?
} else if(pivotIndex + 1 < lastIndex) { // Are elements to sort only left of the pivot? startIndex = pivotIndex + 1;
startIndex = pivotIndex + 1; } else { // Partition was fully sorted
} else { // Partition was fully sorted
// Did we process all queued partitions? If so, the list is sorted
// Did we process all queued partitions? If so, the list is sorted if(remainingPartitions.Count == 0) {
if(remainingPartitions.Count == 0) { return;
return; }
}
// Pull the next partition that needs to be sorted from the stack
// Pull the next partition that needs to be sorted from the stack Partition current = remainingPartitions.Pop();
Partition current = remainingPartitions.Pop(); startIndex = current.LeftmostIndex;
startIndex = current.LeftmostIndex; lastIndex = current.RightmostIndex;
lastIndex = current.RightmostIndex;
} // if sortable sub-partitions exist left/right/nowhere
} // if sortable sub-partitions exist left/right/nowhere } // for ever (termination inside loop)
} // for ever (termination inside loop) }
}
/// <summary>
/// <summary> /// Sorts all the elements in an IList&lt;T&gt; using the insertion sort algorithm
/// Sorts all the elements in an IList&lt;T&gt; using the insertion sort algorithm /// </summary>
/// </summary> /// <typeparam name="TElement">Type of elements the list contains</typeparam>
/// <typeparam name="TElement">Type of elements the list contains</typeparam> /// <param name="list">List in which a subset will be sorted</param>
/// <param name="list">List in which a subset will be sorted</param> /// <param name="comparer">Comparison function to use for comparing list elements</param>
/// <param name="comparer">Comparison function to use for comparing list elements</param> public static void QuickSort<TElement>(
public static void QuickSort<TElement>( this IList<TElement> list, IComparer<TElement> comparer
this IList<TElement> list, IComparer<TElement> comparer ) {
) { QuickSort(list, 0, list.Count, comparer);
QuickSort(list, 0, list.Count, comparer); }
}
/// <summary>
/// <summary> /// Sorts all the elements in an IList&lt;T&gt; using the insertion sort algorithm
/// Sorts all the elements in an IList&lt;T&gt; using the insertion sort algorithm /// </summary>
/// </summary> /// <typeparam name="TElement">Type of elements the list contains</typeparam>
/// <typeparam name="TElement">Type of elements the list contains</typeparam> /// <param name="list">List in which a subset will be sorted</param>
/// <param name="list">List in which a subset will be sorted</param> public static void QuickSort<TElement>(this IList<TElement> list) {
public static void QuickSort<TElement>(this IList<TElement> list) { QuickSort(list, 0, list.Count, Comparer<TElement>.Default);
QuickSort(list, 0, list.Count, Comparer<TElement>.Default); }
}
/// <summary>
/// <summary> /// Moves an element downward over all elements that precede it in the sort order
/// Moves an element downward over all elements that precede it in the sort order /// </summary>
/// </summary> /// <typeparam name="TElement">Type of elements stored in the sorted list</typeparam>
/// <typeparam name="TElement">Type of elements stored in the sorted list</typeparam> /// <param name="list">List that is being sorted</param>
/// <param name="list">List that is being sorted</param> /// <param name="firstIndex">Index of the first element in the partition</param>
/// <param name="firstIndex">Index of the first element in the partition</param> /// <param name="lastIndex">Index of hte last element in the partition</param>
/// <param name="lastIndex">Index of hte last element in the partition</param> /// <param name="comparer">
/// <param name="comparer"> /// Comparison function that decides the ordering of elements
/// Comparison function that decides the ordering of elements /// </param>
/// </param> /// <returns>The index of the next pivot element</returns>
/// <returns>The index of the next pivot element</returns> private static int quicksortPartition<TElement>(
private static int quicksortPartition<TElement>( IList<TElement> list, int firstIndex, int lastIndex, IComparer<TElement> comparer
IList<TElement> list, int firstIndex, int lastIndex, IComparer<TElement> comparer ) {
) {
// Step through all elements in the partition and accumulate those that are smaller
// Step through all elements in the partition and accumulate those that are smaller // than the last element on the left (by swapping). At the end 'firstIndex' will be
// than the last element on the left (by swapping). At the end 'firstIndex' will be // the new pivot point, left of which are all elements smaller than the element at
// the new pivot point, left of which are all elements smaller than the element at // 'lastIndex' and right of it will be all elements which are larger.
// 'lastIndex' and right of it will be all elements which are larger. for(int index = firstIndex; index < lastIndex; ++index) {
for(int index = firstIndex; index < lastIndex; ++index) { if(comparer.Compare(list[index], list[lastIndex]) < 0) {
if(comparer.Compare(list[index], list[lastIndex]) < 0) { TElement temp = list[firstIndex];
TElement temp = list[firstIndex]; list[firstIndex] = list[index];
list[firstIndex] = list[index]; list[index] = temp;
list[index] = temp;
++firstIndex;
++firstIndex; }
} }
}
// The element at 'lastIndex' as a sort value that's in the middle of the two sides,
// The element at 'lastIndex' as a sort value that's in the middle of the two sides, // so we'll have to swap it, too, putting it in the middle and making it the new pivot.
// so we'll have to swap it, too, putting it in the middle and making it the new pivot. {
{ TElement temp = list[firstIndex];
TElement temp = list[firstIndex]; list[firstIndex] = list[lastIndex];
list[firstIndex] = list[lastIndex]; list[lastIndex] = temp;
list[lastIndex] = temp; }
}
// Return the index of the new pivot position
// Return the index of the new pivot position return firstIndex;
return firstIndex;
}
}
}
}
} // namespace Nuclex.Support.Collections
} // namespace Nuclex.Support.Collections

View File

@ -1,68 +1,67 @@
#region CPL License #region Apache License 2.0
/* /*
Nuclex Framework Nuclex .NET Framework
Copyright (C) 2002-2017 Nuclex Development Labs Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
This library is free software; you can redistribute it and/or Licensed under the Apache License, Version 2.0 (the "License");
modify it under the terms of the IBM Common Public License as you may not use this file except in compliance with the License.
published by the IBM Corporation; either version 1.0 of the You may obtain a copy of the License at
License, or (at your option) any later version.
http://www.apache.org/licenses/LICENSE-2.0
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of Unless required by applicable law or agreed to in writing, software
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the distributed under the License is distributed on an "AS IS" BASIS,
IBM Common Public License for more details. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
You should have received a copy of the IBM Common Public limitations under the License.
License along with this library */
*/ #endregion // Apache License 2.0
#endregion
using System;
using System; using System.Collections;
using System.Collections; using System.Collections.Generic;
using System.Collections.Generic;
namespace Nuclex.Support.Collections {
namespace Nuclex.Support.Collections {
/// <summary>
/// <summary> /// Associative collection that can store several values under one key and vice versa
/// Associative collection that can store several values under one key and vice versa /// </summary>
/// </summary> /// <typeparam name="TKey">Type of keys used within the dictionary</typeparam>
/// <typeparam name="TKey">Type of keys used within the dictionary</typeparam> /// <typeparam name="TValue">Type of values stored in the dictionary</typeparam>
/// <typeparam name="TValue">Type of values stored in the dictionary</typeparam> public interface IMultiDictionary<TKey, TValue> :
public interface IMultiDictionary<TKey, TValue> : IDictionary<TKey, ICollection<TValue>>,
IDictionary<TKey, ICollection<TValue>>, IDictionary,
IDictionary, ICollection<KeyValuePair<TKey, TValue>>,
ICollection<KeyValuePair<TKey, TValue>>, IEnumerable<KeyValuePair<TKey, TValue>>,
IEnumerable<KeyValuePair<TKey, TValue>>, IEnumerable {
IEnumerable {
/// <summary>Adds a value into the dictionary under the provided key</summary>
/// <summary>Adds a value into the dictionary under the provided key</summary> /// <param name="key">Key the value will be stored under</param>
/// <param name="key">Key the value will be stored under</param> /// <param name="value">Value that will be stored under the specified key</param>
/// <param name="value">Value that will be stored under the specified key</param> void Add(TKey key, TValue value);
void Add(TKey key, TValue value);
/// <summary>Determines the number of values stored under the specified key</summary>
/// <summary>Determines the number of values stored under the specified key</summary> /// <param name="key">Key whose values will be counted</param>
/// <param name="key">Key whose values will be counted</param> /// <returns>The number of values stored under the specified key</returns>
/// <returns>The number of values stored under the specified key</returns> int CountValues(TKey key);
int CountValues(TKey key);
/// <summary>
/// <summary> /// Removes the item with the specified key and value from the dictionary
/// Removes the item with the specified key and value from the dictionary /// </summary>
/// </summary> /// <param name="key">Key of the item that will be removed</param>
/// <param name="key">Key of the item that will be removed</param> /// <param name="value">Value of the item that will be removed</param>
/// <param name="value">Value of the item that will be removed</param> /// <returns>
/// <returns> /// True if the specified item was contained in the dictionary and was removed
/// True if the specified item was contained in the dictionary and was removed /// </returns>
/// </returns> /// <exception cref="NotSupportedException">If the dictionary is read-only</exception>
/// <exception cref="NotSupportedException">If the dictionary is read-only</exception> bool Remove(TKey key, TValue value);
bool Remove(TKey key, TValue value);
/// <summary>Removes all items with the specified key from the dictionary</summary>
/// <summary>Removes all items with the specified key from the dictionary</summary> /// <param name="key">Key of the item that will be removed</param>
/// <param name="key">Key of the item that will be removed</param> /// <returns>The number of items that have been removed from the dictionary</returns>
/// <returns>The number of items that have been removed from the dictionary</returns> /// <exception cref="NotSupportedException">If the dictionary is read-only</exception>
/// <exception cref="NotSupportedException">If the dictionary is read-only</exception> int RemoveKey(TKey key);
int RemoveKey(TKey key);
}
}
} // namespace Nuclex.Support.Collections
} // namespace Nuclex.Support.Collections

View File

@ -1,51 +1,50 @@
#region CPL License #region Apache License 2.0
/* /*
Nuclex Framework Nuclex .NET Framework
Copyright (C) 2002-2017 Nuclex Development Labs Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
This library is free software; you can redistribute it and/or Licensed under the Apache License, Version 2.0 (the "License");
modify it under the terms of the IBM Common Public License as you may not use this file except in compliance with the License.
published by the IBM Corporation; either version 1.0 of the You may obtain a copy of the License at
License, or (at your option) any later version.
http://www.apache.org/licenses/LICENSE-2.0
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of Unless required by applicable law or agreed to in writing, software
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the distributed under the License is distributed on an "AS IS" BASIS,
IBM Common Public License for more details. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
You should have received a copy of the IBM Common Public limitations under the License.
License along with this library */
*/ #endregion // Apache License 2.0
#endregion
using System;
using System;
namespace Nuclex.Support.Collections {
namespace Nuclex.Support.Collections {
/// <summary>Interface for collections that can be observed</summary>
/// <summary>Interface for collections that can be observed</summary> /// <typeparam name="TItem">Type of items managed in the collection</typeparam>
/// <typeparam name="TItem">Type of items managed in the collection</typeparam> public interface IObservableCollection<TItem> {
public interface IObservableCollection<TItem> {
/// <summary>Raised when an item has been added to the collection</summary>
/// <summary>Raised when an item has been added to the collection</summary> event EventHandler<ItemEventArgs<TItem>> ItemAdded;
event EventHandler<ItemEventArgs<TItem>> ItemAdded;
/// <summary>Raised when an item is removed from the collection</summary>
/// <summary>Raised when an item is removed from the collection</summary> event EventHandler<ItemEventArgs<TItem>> ItemRemoved;
event EventHandler<ItemEventArgs<TItem>> ItemRemoved;
/// <summary>Raised when an item is replaced in the collection</summary>
/// <summary>Raised when an item is replaced in the collection</summary> event EventHandler<ItemReplaceEventArgs<TItem>> ItemReplaced;
event EventHandler<ItemReplaceEventArgs<TItem>> ItemReplaced;
/// <summary>Raised when the collection is about to be cleared</summary>
/// <summary>Raised when the collection is about to be cleared</summary> /// <remarks>
/// <remarks> /// This could be covered by calling ItemRemoved for each item currently
/// This could be covered by calling ItemRemoved for each item currently /// contained in the collection, but it is often simpler and more efficient
/// contained in the collection, but it is often simpler and more efficient /// to process the clearing of the entire collection as a special operation.
/// to process the clearing of the entire collection as a special operation. /// </remarks>
/// </remarks> event EventHandler Clearing;
event EventHandler Clearing;
/// <summary>Raised when the collection has been cleared of its items</summary>
/// <summary>Raised when the collection has been cleared of its items</summary> event EventHandler Cleared;
event EventHandler Cleared;
}
}
} // namespace Nuclex.Support.Collections
} // namespace Nuclex.Support.Collections

View File

@ -1,45 +1,44 @@
#region CPL License #region Apache License 2.0
/* /*
Nuclex Framework Nuclex .NET Framework
Copyright (C) 2002-2017 Nuclex Development Labs Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
This library is free software; you can redistribute it and/or Licensed under the Apache License, Version 2.0 (the "License");
modify it under the terms of the IBM Common Public License as you may not use this file except in compliance with the License.
published by the IBM Corporation; either version 1.0 of the You may obtain a copy of the License at
License, or (at your option) any later version.
http://www.apache.org/licenses/LICENSE-2.0
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of Unless required by applicable law or agreed to in writing, software
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the distributed under the License is distributed on an "AS IS" BASIS,
IBM Common Public License for more details. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
You should have received a copy of the IBM Common Public limitations under the License.
License along with this library */
*/ #endregion // Apache License 2.0
#endregion
using System;
using System;
namespace Nuclex.Support.Collections {
namespace Nuclex.Support.Collections {
/// <summary>Allows an object to be returned to its initial state</summary>
/// <summary>Allows an object to be returned to its initial state</summary> /// <remarks>
/// <remarks> /// <para>
/// <para> /// This interface is typically implemented by objects which can be recycled
/// This interface is typically implemented by objects which can be recycled /// in order to avoid the construction overhead of a heavyweight class and to
/// in order to avoid the construction overhead of a heavyweight class and to /// eliminate garbage by reusing instances.
/// eliminate garbage by reusing instances. /// </para>
/// </para> /// <para>
/// <para> /// Recyclable objects should have a parameterless constructor and calling
/// Recyclable objects should have a parameterless constructor and calling /// their Recycle() method should bring them back into the state they were
/// their Recycle() method should bring them back into the state they were /// in right after they had been constructed.
/// in right after they had been constructed. /// </para>
/// </para> /// </remarks>
/// </remarks> public interface IRecyclable {
public interface IRecyclable {
/// <summary>Returns the object to its initial state</summary>
/// <summary>Returns the object to its initial state</summary> void Recycle();
void Recycle();
}
}
} // namespace Nuclex.Support.Collections
} // namespace Nuclex.Support.Collections

View File

@ -1,56 +1,55 @@
#region CPL License #region Apache License 2.0
/* /*
Nuclex Framework Nuclex .NET Framework
Copyright (C) 2002-2017 Nuclex Development Labs Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
This library is free software; you can redistribute it and/or Licensed under the Apache License, Version 2.0 (the "License");
modify it under the terms of the IBM Common Public License as you may not use this file except in compliance with the License.
published by the IBM Corporation; either version 1.0 of the You may obtain a copy of the License at
License, or (at your option) any later version.
http://www.apache.org/licenses/LICENSE-2.0
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of Unless required by applicable law or agreed to in writing, software
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the distributed under the License is distributed on an "AS IS" BASIS,
IBM Common Public License for more details. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
You should have received a copy of the IBM Common Public limitations under the License.
License along with this library */
*/ #endregion // Apache License 2.0
#endregion
#if UNITTEST
#if UNITTEST
using System;
using System; using System.Collections.Generic;
using System.Collections.Generic;
using NUnit.Framework;
using NUnit.Framework;
namespace Nuclex.Support.Collections {
namespace Nuclex.Support.Collections {
/// <summary>Unit Test for the item event argument container</summary>
/// <summary>Unit Test for the item event argument container</summary> [TestFixture]
[TestFixture] internal class ItemEventArgsTest {
internal class ItemEventArgsTest {
/// <summary>
/// <summary> /// Tests whether an integer argument can be stored in the argument container
/// Tests whether an integer argument can be stored in the argument container /// </summary>
/// </summary> [Test]
[Test] public void IntegersCanBeCarried() {
public void IntegersCanBeCarried() { var test = new ItemEventArgs<int>(12345);
var test = new ItemEventArgs<int>(12345); Assert.AreEqual(12345, test.Item);
Assert.AreEqual(12345, test.Item); }
}
/// <summary>
/// <summary> /// Tests whether a string argument can be stored in the argument container
/// Tests whether a string argument can be stored in the argument container /// </summary>
/// </summary> [Test]
[Test] public void StringsCanBeCarried() {
public void StringsCanBeCarried() { var test = new ItemEventArgs<string>("hello world");
var test = new ItemEventArgs<string>("hello world"); Assert.AreEqual("hello world", test.Item);
Assert.AreEqual("hello world", test.Item); }
}
}
}
} // namespace Nuclex.Support.Collections
} // namespace Nuclex.Support.Collections
#endif // UNITTEST
#endif // UNITTEST

View File

@ -1,46 +1,45 @@
#region CPL License #region Apache License 2.0
/* /*
Nuclex Framework Nuclex .NET Framework
Copyright (C) 2002-2017 Nuclex Development Labs Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
This library is free software; you can redistribute it and/or Licensed under the Apache License, Version 2.0 (the "License");
modify it under the terms of the IBM Common Public License as you may not use this file except in compliance with the License.
published by the IBM Corporation; either version 1.0 of the You may obtain a copy of the License at
License, or (at your option) any later version.
http://www.apache.org/licenses/LICENSE-2.0
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of Unless required by applicable law or agreed to in writing, software
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the distributed under the License is distributed on an "AS IS" BASIS,
IBM Common Public License for more details. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
You should have received a copy of the IBM Common Public limitations under the License.
License along with this library */
*/ #endregion // Apache License 2.0
#endregion
using System;
using System;
namespace Nuclex.Support.Collections {
namespace Nuclex.Support.Collections {
/// <summary>
/// <summary> /// Argument container used by collections to notify about changed items
/// Argument container used by collections to notify about changed items /// </summary>
/// </summary> public class ItemEventArgs<TItem> : EventArgs {
public class ItemEventArgs<TItem> : EventArgs {
/// <summary>Initializes a new event arguments supplier</summary>
/// <summary>Initializes a new event arguments supplier</summary> /// <param name="item">Item to be supplied to the event handler</param>
/// <param name="item">Item to be supplied to the event handler</param> public ItemEventArgs(TItem item) {
public ItemEventArgs(TItem item) { this.item = item;
this.item = item; }
}
/// <summary>Obtains the collection item the event arguments are carrying</summary>
/// <summary>Obtains the collection item the event arguments are carrying</summary> public TItem Item {
public TItem Item { get { return this.item; }
get { return this.item; } }
}
/// <summary>Item to be passed to the event handler</summary>
/// <summary>Item to be passed to the event handler</summary> private TItem item;
private TItem item;
}
}
} // namespace Nuclex.Support.Collections
} // namespace Nuclex.Support.Collections

View File

@ -1,58 +1,57 @@
#region CPL License #region Apache License 2.0
/* /*
Nuclex Framework Nuclex .NET Framework
Copyright (C) 2002-2017 Nuclex Development Labs Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
This library is free software; you can redistribute it and/or Licensed under the Apache License, Version 2.0 (the "License");
modify it under the terms of the IBM Common Public License as you may not use this file except in compliance with the License.
published by the IBM Corporation; either version 1.0 of the You may obtain a copy of the License at
License, or (at your option) any later version.
http://www.apache.org/licenses/LICENSE-2.0
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of Unless required by applicable law or agreed to in writing, software
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the distributed under the License is distributed on an "AS IS" BASIS,
IBM Common Public License for more details. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
You should have received a copy of the IBM Common Public limitations under the License.
License along with this library */
*/ #endregion // Apache License 2.0
#endregion
#if UNITTEST
#if UNITTEST
using System;
using System; using System.Collections.Generic;
using System.Collections.Generic;
using NUnit.Framework;
using NUnit.Framework;
namespace Nuclex.Support.Collections {
namespace Nuclex.Support.Collections {
/// <summary>Unit Test for the item event argument container</summary>
/// <summary>Unit Test for the item event argument container</summary> [TestFixture]
[TestFixture] internal class ItemReplaceEventArgsTest {
internal class ItemReplaceEventArgsTest {
/// <summary>
/// <summary> /// Tests whether an integer argument can be stored in the argument container
/// Tests whether an integer argument can be stored in the argument container /// </summary>
/// </summary> [Test]
[Test] public void IntegersCanBeCarried() {
public void IntegersCanBeCarried() { var test = new ItemReplaceEventArgs<int>(12345, 54321);
var test = new ItemReplaceEventArgs<int>(12345, 54321); Assert.AreEqual(12345, test.OldItem);
Assert.AreEqual(12345, test.OldItem); Assert.AreEqual(54321, test.NewItem);
Assert.AreEqual(54321, test.NewItem); }
}
/// <summary>
/// <summary> /// Tests whether a string argument can be stored in the argument container
/// Tests whether a string argument can be stored in the argument container /// </summary>
/// </summary> [Test]
[Test] public void StringsCanBeCarried() {
public void StringsCanBeCarried() { var test = new ItemReplaceEventArgs<string>("hello", "world");
var test = new ItemReplaceEventArgs<string>("hello", "world"); Assert.AreEqual("hello", test.OldItem);
Assert.AreEqual("hello", test.OldItem); Assert.AreEqual("world", test.NewItem);
Assert.AreEqual("world", test.NewItem); }
}
}
}
} // namespace Nuclex.Support.Collections
} // namespace Nuclex.Support.Collections
#endif // UNITTEST
#endif // UNITTEST

View File

@ -1,55 +1,54 @@
#region CPL License #region Apache License 2.0
/* /*
Nuclex Framework Nuclex .NET Framework
Copyright (C) 2002-2017 Nuclex Development Labs Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
This library is free software; you can redistribute it and/or Licensed under the Apache License, Version 2.0 (the "License");
modify it under the terms of the IBM Common Public License as you may not use this file except in compliance with the License.
published by the IBM Corporation; either version 1.0 of the You may obtain a copy of the License at
License, or (at your option) any later version.
http://www.apache.org/licenses/LICENSE-2.0
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of Unless required by applicable law or agreed to in writing, software
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the distributed under the License is distributed on an "AS IS" BASIS,
IBM Common Public License for more details. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
You should have received a copy of the IBM Common Public limitations under the License.
License along with this library */
*/ #endregion // Apache License 2.0
#endregion
using System;
using System;
namespace Nuclex.Support.Collections {
namespace Nuclex.Support.Collections {
/// <summary>
/// <summary> /// Argument container used by collections to notify about replaced items
/// Argument container used by collections to notify about replaced items /// </summary>
/// </summary> public class ItemReplaceEventArgs<TItem> : EventArgs {
public class ItemReplaceEventArgs<TItem> : EventArgs {
/// <summary>Initializes a new event arguments supplier</summary>
/// <summary>Initializes a new event arguments supplier</summary> /// <param name="oldItem">Item that has been replaced by another item</param>
/// <param name="oldItem">Item that has been replaced by another item</param> /// <param name="newItem">Replacement item that is now part of the collection</param>
/// <param name="newItem">Replacement item that is now part of the collection</param> public ItemReplaceEventArgs(TItem oldItem, TItem newItem) {
public ItemReplaceEventArgs(TItem oldItem, TItem newItem) { this.oldItem = oldItem;
this.oldItem = oldItem; this.newItem = newItem;
this.newItem = newItem; }
}
/// <summary>Item that has been replaced by another item</summary>
/// <summary>Item that has been replaced by another item</summary> public TItem OldItem {
public TItem OldItem { get { return this.oldItem; }
get { return this.oldItem; } }
}
/// <summary>Replacement item that is now part of the collection</summary>
/// <summary>Replacement item that is now part of the collection</summary> public TItem NewItem {
public TItem NewItem { get { return this.newItem; }
get { return this.newItem; } }
}
/// <summary>Item that was removed from the collection</summary>
/// <summary>Item that was removed from the collection</summary> private TItem oldItem;
private TItem oldItem; /// <summary>Item that was added to the collection</summary>
/// <summary>Item that was added to the collection</summary> private TItem newItem;
private TItem newItem;
}
}
} // namespace Nuclex.Support.Collections
} // namespace Nuclex.Support.Collections

View File

@ -1,257 +1,256 @@
#region CPL License #region Apache License 2.0
/* /*
Nuclex Framework Nuclex .NET Framework
Copyright (C) 2002-2017 Nuclex Development Labs Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
This library is free software; you can redistribute it and/or Licensed under the Apache License, Version 2.0 (the "License");
modify it under the terms of the IBM Common Public License as you may not use this file except in compliance with the License.
published by the IBM Corporation; either version 1.0 of the You may obtain a copy of the License at
License, or (at your option) any later version.
http://www.apache.org/licenses/LICENSE-2.0
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of Unless required by applicable law or agreed to in writing, software
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the distributed under the License is distributed on an "AS IS" BASIS,
IBM Common Public License for more details. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
You should have received a copy of the IBM Common Public limitations under the License.
License along with this library */
*/ #endregion // Apache License 2.0
#endregion
using System;
using System; using System.Collections.Generic;
using System.Collections.Generic; using System.IO;
using System.IO;
#if UNITTEST
#if UNITTEST
using NUnit.Framework;
using NUnit.Framework;
namespace Nuclex.Support.Collections {
namespace Nuclex.Support.Collections {
/// <summary>Unit Test for the list segment class</summary>
/// <summary>Unit Test for the list segment class</summary> [TestFixture]
[TestFixture] internal class ListSegmentTest {
internal class ListSegmentTest {
/// <summary>
/// <summary> /// Tests whether the default constructor of the ListSegment class throws the
/// Tests whether the default constructor of the ListSegment class throws the /// right exception when being passed 'null' instead of a list
/// right exception when being passed 'null' instead of a list /// </summary>
/// </summary> [Test]
[Test] public void SimpleConstructorThrowsWhenListIsNull() {
public void SimpleConstructorThrowsWhenListIsNull() { Assert.Throws<ArgumentNullException>(
Assert.Throws<ArgumentNullException>( delegate() { new ListSegment<int>(null); }
delegate() { new ListSegment<int>(null); } );
); }
}
/// <summary>
/// <summary> /// Tests whether the simple constructor of the ListSegment class accepts
/// Tests whether the simple constructor of the ListSegment class accepts /// an empty list
/// an empty list /// </summary>
/// </summary> [Test]
[Test] public void SimpleConstructorAcceptsEmptyList() {
public void SimpleConstructorAcceptsEmptyList() { new ListSegment<int>(new List<int>());
new ListSegment<int>(new List<int>()); }
}
/// <summary>
/// <summary> /// Tests whether the full constructor of the ListSegment class throws the
/// Tests whether the full constructor of the ListSegment class throws the /// right exception when being passed 'null' instead of a string
/// right exception when being passed 'null' instead of a string /// </summary>
/// </summary> [Test]
[Test] public void ConstructorThrowsWhenListIsNull() {
public void ConstructorThrowsWhenListIsNull() { Assert.Throws<ArgumentNullException>(
Assert.Throws<ArgumentNullException>( delegate() { new ListSegment<int>(null, 0, 0); }
delegate() { new ListSegment<int>(null, 0, 0); } );
); }
}
/// <summary>
/// <summary> /// Tests whether the full constructor of the ListSegment class accepts
/// Tests whether the full constructor of the ListSegment class accepts /// an empty string
/// an empty string /// </summary>
/// </summary> [Test]
[Test] public void ConstructorAcceptsEmptyList() {
public void ConstructorAcceptsEmptyList() { new ListSegment<int>(new List<int>(), 0, 0);
new ListSegment<int>(new List<int>(), 0, 0); }
}
/// <summary>
/// <summary> /// Tests whether the full constructor of the ListSegment class throws the
/// Tests whether the full constructor of the ListSegment class throws the /// right exception when being passed an invalid start offset
/// right exception when being passed an invalid start offset /// </summary>
/// </summary> [Test]
[Test] public void ConstructorThrowsOnInvalidOffset() {
public void ConstructorThrowsOnInvalidOffset() { Assert.Throws<ArgumentOutOfRangeException>(
Assert.Throws<ArgumentOutOfRangeException>( delegate() { new ListSegment<int>(new List<int>(), -1, 0); }
delegate() { new ListSegment<int>(new List<int>(), -1, 0); } );
); }
}
/// <summary>
/// <summary> /// Tests whether the full constructor of the ListSegment class throws the
/// Tests whether the full constructor of the ListSegment class throws the /// right exception when being passed an invalid element count
/// right exception when being passed an invalid element count /// </summary>
/// </summary> [Test]
[Test] public void ConstructorThrowsOnInvalidCount() {
public void ConstructorThrowsOnInvalidCount() { Assert.Throws<ArgumentOutOfRangeException>(
Assert.Throws<ArgumentOutOfRangeException>( delegate() { new ListSegment<int>(new List<int>(), 0, -1); }
delegate() { new ListSegment<int>(new List<int>(), 0, -1); } );
); }
}
/// <summary>
/// <summary> /// Tests whether the full constructor of the ListSegment class throws the
/// Tests whether the full constructor of the ListSegment class throws the /// right exception when being passed a string length that's too large
/// right exception when being passed a string length that's too large /// </summary>
/// </summary> [Test]
[Test] public void ConstructorThrowsOnListOverrun() {
public void ConstructorThrowsOnListOverrun() { var testList = new List<int>(capacity: 5) { 1, 2, 3, 4, 5 };
var testList = new List<int>(capacity: 5) { 1, 2, 3, 4, 5 }; Assert.Throws<ArgumentException>(
Assert.Throws<ArgumentException>( delegate() { new ListSegment<int>(testList, 3, 3); }
delegate() { new ListSegment<int>(testList, 3, 3); } );
); }
}
/// <summary>Tests whether the 'Text' property works as expected</summary>
/// <summary>Tests whether the 'Text' property works as expected</summary> [Test]
[Test] public void ListPropertyStoresOriginalList() {
public void ListPropertyStoresOriginalList() { var testList = new List<int>(capacity: 5) { 1, 2, 3, 4, 5 };
var testList = new List<int>(capacity: 5) { 1, 2, 3, 4, 5 }; ListSegment<int> testSegment = new ListSegment<int>(testList, 1, 3);
ListSegment<int> testSegment = new ListSegment<int>(testList, 1, 3); Assert.AreSame(testList, testSegment.List);
Assert.AreSame(testList, testSegment.List); }
}
/// <summary>Tests whether the 'Offset' property works as expected</summary>
/// <summary>Tests whether the 'Offset' property works as expected</summary> [Test]
[Test] public void OffsetPropertyIsStored() {
public void OffsetPropertyIsStored() { var testList = new List<int>(capacity: 5) { 1, 2, 3, 4, 5 };
var testList = new List<int>(capacity: 5) { 1, 2, 3, 4, 5 }; ListSegment<int> testSegment = new ListSegment<int>(testList, 1, 3);
ListSegment<int> testSegment = new ListSegment<int>(testList, 1, 3); Assert.AreEqual(1, testSegment.Offset);
Assert.AreEqual(1, testSegment.Offset); }
}
/// <summary>Tests whether the 'Count' property works as expected</summary>
/// <summary>Tests whether the 'Count' property works as expected</summary> [Test]
[Test] public void CountPropertyIsStored() {
public void CountPropertyIsStored() { var testList = new List<int>(capacity: 5) { 1, 2, 3, 4, 5 };
var testList = new List<int>(capacity: 5) { 1, 2, 3, 4, 5 }; ListSegment<int> testSegment = new ListSegment<int>(testList, 1, 3);
ListSegment<int> testSegment = new ListSegment<int>(testList, 1, 3); Assert.AreEqual(3, testSegment.Count);
Assert.AreEqual(3, testSegment.Count); }
}
/// <summary>
/// <summary> /// Tests whether two differing instances produce different hash codes
/// Tests whether two differing instances produce different hash codes /// </summary>
/// </summary> [Test]
[Test] public void DifferentInstancesHaveDifferentHashCodes_Usually() {
public void DifferentInstancesHaveDifferentHashCodes_Usually() { var forwardCountSegment = new ListSegment<int>(
var forwardCountSegment = new ListSegment<int>( new List<int>(capacity: 9) { 1, 2, 3, 4, 5, 6, 7, 8, 9 }, 2, 7
new List<int>(capacity: 9) { 1, 2, 3, 4, 5, 6, 7, 8, 9 }, 2, 7 );
); var reverseCountSegment = new ListSegment<int>(
var reverseCountSegment = new ListSegment<int>( new List<int>(capacity: 9) { 9, 8, 7, 6, 5, 4, 3, 2, 1 }, 1, 8
new List<int>(capacity: 9) { 9, 8, 7, 6, 5, 4, 3, 2, 1 }, 1, 8 );
);
Assert.AreNotEqual(
Assert.AreNotEqual( forwardCountSegment.GetHashCode(), reverseCountSegment.GetHashCode()
forwardCountSegment.GetHashCode(), reverseCountSegment.GetHashCode() );
); }
}
/// <summary>
/// <summary> /// Tests whether two equivalent instances produce an identical hash code
/// Tests whether two equivalent instances produce an identical hash code /// </summary>
/// </summary> [Test]
[Test] public void EquivalentInstancesHaveSameHashcode() {
public void EquivalentInstancesHaveSameHashcode() { var testSegment = new ListSegment<int>(
var testSegment = new ListSegment<int>( new List<int>(capacity: 9) { 1, 2, 3, 4, 5, 6, 7, 8, 9 }, 2, 7
new List<int>(capacity: 9) { 1, 2, 3, 4, 5, 6, 7, 8, 9 }, 2, 7 );
); var identicalSegment = new ListSegment<int>(
var identicalSegment = new ListSegment<int>( new List<int>(capacity: 9) { 1, 2, 3, 4, 5, 6, 7, 8, 9 }, 2, 7
new List<int>(capacity: 9) { 1, 2, 3, 4, 5, 6, 7, 8, 9 }, 2, 7 );
);
Assert.AreEqual(
Assert.AreEqual( testSegment.GetHashCode(), identicalSegment.GetHashCode()
testSegment.GetHashCode(), identicalSegment.GetHashCode() );
); }
}
/// <summary>Tests the equals method performing a comparison against null</summary>
/// <summary>Tests the equals method performing a comparison against null</summary> [Test]
[Test] public void EqualsAgainstNullIsAlwaysFalse() {
public void EqualsAgainstNullIsAlwaysFalse() { var testList = new List<int>(capacity: 5) { 1, 2, 3, 4, 5 };
var testList = new List<int>(capacity: 5) { 1, 2, 3, 4, 5 }; ListSegment<int> testSegment = new ListSegment<int>(testList, 1, 3);
ListSegment<int> testSegment = new ListSegment<int>(testList, 1, 3);
Assert.IsFalse(
Assert.IsFalse( testSegment.Equals(null)
testSegment.Equals(null) );
); }
}
/// <summary>Tests the equality operator with differing instances</summary>
/// <summary>Tests the equality operator with differing instances</summary> [Test]
[Test] public void DifferingInstancesAreNotEqual() {
public void DifferingInstancesAreNotEqual() { var forwardCountSegment = new ListSegment<int>(
var forwardCountSegment = new ListSegment<int>( new List<int>(capacity: 9) { 1, 2, 3, 4, 5, 6, 7, 8, 9 }, 2, 7
new List<int>(capacity: 9) { 1, 2, 3, 4, 5, 6, 7, 8, 9 }, 2, 7 );
); var reverseCountSegment = new ListSegment<int>(
var reverseCountSegment = new ListSegment<int>( new List<int>(capacity: 9) { 9, 8, 7, 6, 5, 4, 3, 2, 1 }, 1, 8
new List<int>(capacity: 9) { 9, 8, 7, 6, 5, 4, 3, 2, 1 }, 1, 8 );
);
Assert.IsFalse(forwardCountSegment == reverseCountSegment);
Assert.IsFalse(forwardCountSegment == reverseCountSegment); }
}
/// <summary>Tests the equality operator with equivalent instances</summary>
/// <summary>Tests the equality operator with equivalent instances</summary> [Test]
[Test] public void EquivalentInstancesAreEqual() {
public void EquivalentInstancesAreEqual() { var testSegment = new ListSegment<int>(
var testSegment = new ListSegment<int>( new List<int>(capacity: 9) { 1, 2, 3, 4, 5, 6, 7, 8, 9 }, 2, 7
new List<int>(capacity: 9) { 1, 2, 3, 4, 5, 6, 7, 8, 9 }, 2, 7 );
); var identicalSegment = new ListSegment<int>(
var identicalSegment = new ListSegment<int>( new List<int>(capacity: 9) { 1, 2, 3, 4, 5, 6, 7, 8, 9 }, 2, 7
new List<int>(capacity: 9) { 1, 2, 3, 4, 5, 6, 7, 8, 9 }, 2, 7 );
);
Assert.IsTrue(testSegment == identicalSegment);
Assert.IsTrue(testSegment == identicalSegment); }
}
/// <summary>Tests the inequality operator with differing instances</summary>
/// <summary>Tests the inequality operator with differing instances</summary> [Test]
[Test] public void DifferingInstancesAreUnequal() {
public void DifferingInstancesAreUnequal() { var forwardCountSegment = new ListSegment<int>(
var forwardCountSegment = new ListSegment<int>( new List<int>(capacity: 9) { 1, 2, 3, 4, 5, 6, 7, 8, 9 }, 2, 7
new List<int>(capacity: 9) { 1, 2, 3, 4, 5, 6, 7, 8, 9 }, 2, 7 );
); var reverseCountSegment = new ListSegment<int>(
var reverseCountSegment = new ListSegment<int>( new List<int>(capacity: 9) { 9, 8, 7, 6, 5, 4, 3, 2, 1 }, 1, 8
new List<int>(capacity: 9) { 9, 8, 7, 6, 5, 4, 3, 2, 1 }, 1, 8 );
);
Assert.IsTrue(forwardCountSegment != reverseCountSegment);
Assert.IsTrue(forwardCountSegment != reverseCountSegment); }
}
/// <summary>Tests the inequality operator with equivalent instances</summary>
/// <summary>Tests the inequality operator with equivalent instances</summary> [Test]
[Test] public void EquivalentInstancesAreNotUnequal() {
public void EquivalentInstancesAreNotUnequal() { var testSegment = new ListSegment<int>(
var testSegment = new ListSegment<int>( new List<int>(capacity: 9) { 1, 2, 3, 4, 5, 6, 7, 8, 9 }, 2, 7
new List<int>(capacity: 9) { 1, 2, 3, 4, 5, 6, 7, 8, 9 }, 2, 7 );
); var identicalSegment = new ListSegment<int>(
var identicalSegment = new ListSegment<int>( new List<int>(capacity: 9) { 1, 2, 3, 4, 5, 6, 7, 8, 9 }, 2, 7
new List<int>(capacity: 9) { 1, 2, 3, 4, 5, 6, 7, 8, 9 }, 2, 7 );
);
Assert.IsFalse(testSegment != identicalSegment);
Assert.IsFalse(testSegment != identicalSegment); }
}
/// <summary>Tests the ToString() method of the string segment</summary>
/// <summary>Tests the ToString() method of the string segment</summary> [Test]
[Test] public void TestToString() {
public void TestToString() { var testList = new List<int>(capacity: 6) { 1, 2, 3, 4, 5, 6 };
var testList = new List<int>(capacity: 6) { 1, 2, 3, 4, 5, 6 }; ListSegment<int> testSegment = new ListSegment<int>(testList, 2, 2);
ListSegment<int> testSegment = new ListSegment<int>(testList, 2, 2);
string stringRepresentation = testSegment.ToString();
string stringRepresentation = testSegment.ToString(); StringAssert.Contains("3, 4", stringRepresentation);
StringAssert.Contains("3, 4", stringRepresentation); StringAssert.DoesNotContain("2", stringRepresentation);
StringAssert.DoesNotContain("2", stringRepresentation); StringAssert.DoesNotContain("5", stringRepresentation);
StringAssert.DoesNotContain("5", stringRepresentation); }
}
/// <summary>Tests whether the 'Text' property works as expected</summary>
/// <summary>Tests whether the 'Text' property works as expected</summary> [Test]
[Test] public void ToListReturnsSubset() {
public void ToListReturnsSubset() { var testList = new List<int>(capacity: 5) { 1, 2, 3, 4, 5 };
var testList = new List<int>(capacity: 5) { 1, 2, 3, 4, 5 }; ListSegment<int> testSegment = new ListSegment<int>(testList, 1, 3);
ListSegment<int> testSegment = new ListSegment<int>(testList, 1, 3); CollectionAssert.AreEqual(
CollectionAssert.AreEqual( new List<int>(capacity: 3) { 2, 3, 4 },
new List<int>(capacity: 3) { 2, 3, 4 }, testSegment.ToList()
testSegment.ToList() );
); }
}
}
}
} // namespace Nuclex.Support.Collections
} // namespace Nuclex.Support.Collections
#endif // UNITTEST
#endif // UNITTEST

View File

@ -1,281 +1,280 @@
#region CPL License #region Apache License 2.0
/* /*
Nuclex Framework Nuclex .NET Framework
Copyright (C) 2002-2017 Nuclex Development Labs Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
This library is free software; you can redistribute it and/or Licensed under the Apache License, Version 2.0 (the "License");
modify it under the terms of the IBM Common Public License as you may not use this file except in compliance with the License.
published by the IBM Corporation; either version 1.0 of the You may obtain a copy of the License at
License, or (at your option) any later version.
http://www.apache.org/licenses/LICENSE-2.0
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of Unless required by applicable law or agreed to in writing, software
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the distributed under the License is distributed on an "AS IS" BASIS,
IBM Common Public License for more details. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
You should have received a copy of the IBM Common Public limitations under the License.
License along with this library */
*/ #endregion // Apache License 2.0
#endregion
using System;
using System; using System.Collections.Generic;
using System.Collections.Generic; using System.Runtime.InteropServices;
using System.Runtime.InteropServices;
namespace Nuclex.Support.Collections {
namespace Nuclex.Support.Collections {
/// <summary>View into a section of an IList&lt;T&gt; without copying said string</summary>
/// <summary>View into a section of an IList&lt;T&gt; without copying said string</summary> /// <typeparam name="TElement">
/// <typeparam name="TElement"> /// Type of elements that are stored in the list the segment references
/// Type of elements that are stored in the list the segment references /// </typeparam>
/// </typeparam> /// <remarks>
/// <remarks> /// <para>
/// <para> /// The design of this class pretty much mirrors that of the
/// The design of this class pretty much mirrors that of the /// <see cref="T:System.ArraySegment" /> class found in the .NET framework, but is
/// <see cref="T:System.ArraySegment" /> class found in the .NET framework, but is /// specialized to be used for IList&lt;T&gt;, which can not be cast to arrays
/// specialized to be used for IList&lt;T&gt;, which can not be cast to arrays /// directly (and <see cref="M:System.ArrayList.Adapter" /> loses type safety).
/// directly (and <see cref="M:System.ArrayList.Adapter" /> loses type safety). /// </para>
/// </para> /// <para>
/// <para> /// In certain situations, passing a ListSegment instead of storing the selected
/// In certain situations, passing a ListSegment instead of storing the selected /// elements in a new list is useful. For example, the caller might want to know
/// elements in a new list is useful. For example, the caller might want to know /// from which index of the original list the section was taken. When the original
/// from which index of the original list the section was taken. When the original /// list needs to be modified, for example in a sorting algorithm, the list segment
/// list needs to be modified, for example in a sorting algorithm, the list segment /// can be used to specify a region for the algorithm to work on while still accessing
/// can be used to specify a region for the algorithm to work on while still accessing /// the original list.
/// the original list. /// </para>
/// </para> /// </remarks>
/// </remarks> #if !NO_SERIALIZATION
#if !NO_SERIALIZATION [Serializable, StructLayout(LayoutKind.Sequential)]
[Serializable, StructLayout(LayoutKind.Sequential)] #endif
#endif public struct ListSegment<TElement> {
public struct ListSegment<TElement> {
/// <summary>
/// <summary> /// Initializes a new instance of the <see cref="ListSegment&lt;TElement&gt;" /> class
/// Initializes a new instance of the <see cref="ListSegment&lt;TElement&gt;" /> class /// that delimits all the elements in the specified string
/// that delimits all the elements in the specified string /// </summary>
/// </summary> /// <param name="list">List that will be wrapped</param>
/// <param name="list">List that will be wrapped</param> /// <exception cref="System.ArgumentNullException">String is null</exception>
/// <exception cref="System.ArgumentNullException">String is null</exception> public ListSegment(IList<TElement> list) {
public ListSegment(IList<TElement> list) { if(list == null) { // questionable, but matches behavior of ArraySegment class
if(list == null) { // questionable, but matches behavior of ArraySegment class throw new ArgumentNullException("text", "Text must not be null");
throw new ArgumentNullException("text", "Text must not be null"); }
}
this.list = list;
this.list = list; this.offset = 0;
this.offset = 0; this.count = list.Count;
this.count = list.Count; }
}
/// <summary>
/// <summary> /// Initializes a new instance of the <see cref="ListSegment&lt;TElement&gt;" /> class
/// Initializes a new instance of the <see cref="ListSegment&lt;TElement&gt;" /> class /// that delimits the specified range of the elements in the specified string
/// that delimits the specified range of the elements in the specified string /// </summary>
/// </summary> /// <param name="list">The list containing the range of elements to delimit</param>
/// <param name="list">The list containing the range of elements to delimit</param> /// <param name="offset">The zero-based index of the first element in the range</param>
/// <param name="offset">The zero-based index of the first element in the range</param> /// <param name="count">The number of elements in the range</param>
/// <param name="count">The number of elements in the range</param> /// <exception cref="System.ArgumentOutOfRangeException">
/// <exception cref="System.ArgumentOutOfRangeException"> /// Offset or count is less than 0
/// Offset or count is less than 0 /// </exception>
/// </exception> /// <exception cref="System.ArgumentException">
/// <exception cref="System.ArgumentException"> /// Offset and count do not specify a valid range in array
/// Offset and count do not specify a valid range in array /// </exception>
/// </exception> /// <exception cref="System.ArgumentNullException">String is null</exception>
/// <exception cref="System.ArgumentNullException">String is null</exception> public ListSegment(IList<TElement> list, int offset, int count) {
public ListSegment(IList<TElement> list, int offset, int count) { if(list == null) { // questionable, but matches behavior of ArraySegment class
if(list == null) { // questionable, but matches behavior of ArraySegment class throw new ArgumentNullException("list");
throw new ArgumentNullException("list"); }
} if(offset < 0) {
if(offset < 0) { throw new ArgumentOutOfRangeException(
throw new ArgumentOutOfRangeException( "offset", "Argument out of range, non-negative number required"
"offset", "Argument out of range, non-negative number required" );
); }
} if(count < 0) {
if(count < 0) { throw new ArgumentOutOfRangeException(
throw new ArgumentOutOfRangeException( "count", "Argument out of range, non-negative number required"
"count", "Argument out of range, non-negative number required" );
); }
} if(count > (list.Count - offset)) {
if(count > (list.Count - offset)) { throw new ArgumentException(
throw new ArgumentException( "Invalid argument, specified offset and count exceed list size"
"Invalid argument, specified offset and count exceed list size" );
); }
}
this.list = list;
this.list = list; this.offset = offset;
this.offset = offset; this.count = count;
this.count = count; }
}
/// <summary>
/// <summary> /// Gets the original list containing the range of elements that the list
/// Gets the original list containing the range of elements that the list /// segment delimits
/// segment delimits /// </summary>
/// </summary> /// <returns>
/// <returns> /// The original list that was passed to the constructor, and that contains the range
/// The original list that was passed to the constructor, and that contains the range /// delimited by the <see cref="ListSegment&lt;TElement&gt;" />
/// delimited by the <see cref="ListSegment&lt;TElement&gt;" /> /// </returns>
/// </returns> public IList<TElement> List {
public IList<TElement> List { get { return this.list; }
get { return this.list; } }
}
/// <summary>
/// <summary> /// Gets the position of the first element in the range delimited by the list segment,
/// Gets the position of the first element in the range delimited by the list segment, /// relative to the start of the original list
/// relative to the start of the original list /// </summary>
/// </summary> /// <returns>
/// <returns> /// The position of the first element in the range delimited by the
/// The position of the first element in the range delimited by the /// <see cref="ListSegment&lt;TElement&gt;" />, relative to the start of the original list
/// <see cref="ListSegment&lt;TElement&gt;" />, relative to the start of the original list /// </returns>
/// </returns> public int Offset {
public int Offset { get { return this.offset; }
get { return this.offset; } }
}
/// <summary>
/// <summary> /// Gets the number of elements in the range delimited by the list segment
/// Gets the number of elements in the range delimited by the list segment /// </summary>
/// </summary> /// <returns>
/// <returns> /// The number of elements in the range delimited by
/// The number of elements in the range delimited by /// the <see cref="ListSegment&lt;TElement&gt;" />
/// the <see cref="ListSegment&lt;TElement&gt;" /> /// </returns>
/// </returns> public int Count {
public int Count { get { return this.count; }
get { return this.count; } }
}
/// <summary>Returns the hash code for the current instance</summary>
/// <summary>Returns the hash code for the current instance</summary> /// <returns>A 32-bit signed integer hash code</returns>
/// <returns>A 32-bit signed integer hash code</returns> public override int GetHashCode() {
public override int GetHashCode() { int hashCode = this.offset ^ this.count;
int hashCode = this.offset ^ this.count; for(int index = 0; index < this.count; ++index) {
for(int index = 0; index < this.count; ++index) { hashCode ^= this.list[index + this.offset].GetHashCode();
hashCode ^= this.list[index + this.offset].GetHashCode(); }
} return hashCode;
return hashCode; }
}
/// <summary>
/// <summary> /// Determines whether the specified object is equal to the current instance
/// Determines whether the specified object is equal to the current instance /// </summary>
/// </summary> /// <returns>
/// <returns> /// True if the specified object is a <see cref="ListSegment&lt;TElement&gt;" /> structure
/// True if the specified object is a <see cref="ListSegment&lt;TElement&gt;" /> structure /// and is equal to the current instance; otherwise, false
/// and is equal to the current instance; otherwise, false /// </returns>
/// </returns> /// <param name="other">The object to be compared with the current instance</param>
/// <param name="other">The object to be compared with the current instance</param> public override bool Equals(object other) {
public override bool Equals(object other) { return
return (other is ListSegment<TElement>) &&
(other is ListSegment<TElement>) && this.Equals((ListSegment<TElement>)other);
this.Equals((ListSegment<TElement>)other); }
}
/// <summary>
/// <summary> /// Determines whether the specified <see cref="ListSegment&lt;TElement&gt;" />
/// Determines whether the specified <see cref="ListSegment&lt;TElement&gt;" /> /// structure is equal to the current instance
/// structure is equal to the current instance /// </summary>
/// </summary> /// <returns>
/// <returns> /// True if the specified <see cref="ListSegment&lt;TElement&gt;" /> structure is equal
/// True if the specified <see cref="ListSegment&lt;TElement&gt;" /> structure is equal /// to the current instance; otherwise, false
/// to the current instance; otherwise, false /// </returns>
/// </returns> /// <param name="other">
/// <param name="other"> /// The <see cref="ListSegment&lt;TElement&gt;" /> structure to be compared with
/// The <see cref="ListSegment&lt;TElement&gt;" /> structure to be compared with /// the current instance
/// the current instance /// </param>
/// </param> public bool Equals(ListSegment<TElement> other) {
public bool Equals(ListSegment<TElement> other) { if(other.count != this.count) {
if(other.count != this.count) { return false;
return false; }
}
if(ReferenceEquals(other.list, this.list)) {
if(ReferenceEquals(other.list, this.list)) { return (other.offset == this.offset);
return (other.offset == this.offset); } else {
} else { var comparer = Comparer<TElement>.Default;
var comparer = Comparer<TElement>.Default; for(int index = 0; index < this.count; ++index) {
for(int index = 0; index < this.count; ++index) { int difference = comparer.Compare(
int difference = comparer.Compare( other.list[index + other.offset], this.list[index + this.offset]
other.list[index + other.offset], this.list[index + this.offset] );
); if(difference != 0) {
if(difference != 0) { return false;
return false; }
} }
} }
} return true;
return true; }
}
/// <summary>
/// <summary> /// Indicates whether two <see cref="ListSegment&lt;TElement&gt;" /> structures are equal
/// Indicates whether two <see cref="ListSegment&lt;TElement&gt;" /> structures are equal /// </summary>
/// </summary> /// <returns>True if a is equal to b; otherwise, false</returns>
/// <returns>True if a is equal to b; otherwise, false</returns> /// <param name="left">
/// <param name="left"> /// The <see cref="ListSegment&lt;TElement&gt;" /> structure on the left side of
/// The <see cref="ListSegment&lt;TElement&gt;" /> structure on the left side of /// the equality operator
/// the equality operator /// </param>
/// </param> /// <param name="right">
/// <param name="right"> /// The <see cref="ListSegment&lt;TElement&gt;" /> structure on the right side of
/// The <see cref="ListSegment&lt;TElement&gt;" /> structure on the right side of /// the equality operator
/// the equality operator /// </param>
/// </param> public static bool operator ==(ListSegment<TElement> left, ListSegment<TElement> right) {
public static bool operator ==(ListSegment<TElement> left, ListSegment<TElement> right) { return left.Equals(right);
return left.Equals(right); }
}
/// <summary>
/// <summary> /// Indicates whether two <see cref="ListSegment&lt;TElement&gt;" /> structures are unequal
/// Indicates whether two <see cref="ListSegment&lt;TElement&gt;" /> structures are unequal /// </summary>
/// </summary> /// <returns>True if a is not equal to b; otherwise, false</returns>
/// <returns>True if a is not equal to b; otherwise, false</returns> /// <param name="left">
/// <param name="left"> /// The <see cref="ListSegment&lt;TElement&gt;" /> structure on the left side of
/// The <see cref="ListSegment&lt;TElement&gt;" /> structure on the left side of /// the inequality operator
/// the inequality operator /// </param>
/// </param> /// <param name="right">
/// <param name="right"> /// The <see cref="ListSegment&lt;TElement&gt;" /> structure on the right side of
/// The <see cref="ListSegment&lt;TElement&gt;" /> structure on the right side of /// the inequality operator
/// the inequality operator /// </param>
/// </param> public static bool operator !=(ListSegment<TElement> left, ListSegment<TElement> right) {
public static bool operator !=(ListSegment<TElement> left, ListSegment<TElement> right) { return !(left == right);
return !(left == right); }
}
/// <summary>Returns a string representation of the list segment</summary>
/// <summary>Returns a string representation of the list segment</summary> /// <returns>The string representation of the list segment</returns>
/// <returns>The string representation of the list segment</returns> public override string ToString() {
public override string ToString() { var builder = new System.Text.StringBuilder();
var builder = new System.Text.StringBuilder(); builder.Append("ListSegment {");
builder.Append("ListSegment {"); for(int index = 0; index < Math.Min(this.count, 10); ++index) {
for(int index = 0; index < Math.Min(this.count, 10); ++index) { if(index == 0) {
if(index == 0) { builder.Append(" ");
builder.Append(" "); } else {
} else { builder.Append(", ");
builder.Append(", "); }
} builder.Append(this.list[index + this.offset].ToString());
builder.Append(this.list[index + this.offset].ToString()); }
}
if(this.count >= 11) {
if(this.count >= 11) { builder.Append(", ... }");
builder.Append(", ... }"); } else {
} else { builder.Append(" }");
builder.Append(" }"); }
}
return builder.ToString();
return builder.ToString(); }
}
/// <summary>Returns a new list containing only the elements in the list segment</summary>
/// <summary>Returns a new list containing only the elements in the list segment</summary> /// <returns>A new list containing only the elements in the list segment</returns>
/// <returns>A new list containing only the elements in the list segment</returns> public List<TElement> ToList() {
public List<TElement> ToList() { if(this.count == 0) {
if(this.count == 0) { return new List<TElement>(capacity: 0);
return new List<TElement>(capacity: 0); } else {
} else { var newList = new List<TElement>(capacity: this.count);
var newList = new List<TElement>(capacity: this.count); {
{ int endIndex = this.offset + this.count;
int endIndex = this.offset + this.count; for(int index = this.offset; index < endIndex; ++index) {
for(int index = this.offset; index < endIndex; ++index) { newList.Add(this.list[index]);
newList.Add(this.list[index]); }
} }
}
return newList;
return newList; }
} }
}
/// <summary>List wrapped by the list segment</summary>
/// <summary>List wrapped by the list segment</summary> private IList<TElement> list;
private IList<TElement> list; /// <summary>Offset in the original list the segment begins at</summary>
/// <summary>Offset in the original list the segment begins at</summary> private int offset;
private int offset; /// <summary>Number of elements in the segment</summary>
/// <summary>Number of elements in the segment</summary> private int count;
private int count;
}
}
} // namespace Nuclex.Support.Collections
} // namespace Nuclex.Support.Collections

View File

@ -1,273 +1,272 @@
#region CPL License #region Apache License 2.0
/* /*
Nuclex Framework Nuclex .NET Framework
Copyright (C) 2002-2017 Nuclex Development Labs Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
This library is free software; you can redistribute it and/or Licensed under the Apache License, Version 2.0 (the "License");
modify it under the terms of the IBM Common Public License as you may not use this file except in compliance with the License.
published by the IBM Corporation; either version 1.0 of the You may obtain a copy of the License at
License, or (at your option) any later version.
http://www.apache.org/licenses/LICENSE-2.0
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of Unless required by applicable law or agreed to in writing, software
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the distributed under the License is distributed on an "AS IS" BASIS,
IBM Common Public License for more details. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
You should have received a copy of the IBM Common Public limitations under the License.
License along with this library */
*/ #endregion // Apache License 2.0
#endregion
using System;
using System; using System.Collections;
using System.Collections; using System.Collections.Generic;
using System.Collections.Generic;
namespace Nuclex.Support.Collections {
namespace Nuclex.Support.Collections {
partial class MultiDictionary<TKey, TValue> {
partial class MultiDictionary<TKey, TValue> {
#region IEnumerable implementation
#region IEnumerable implementation
/// <summary>Returns a new object enumerator for the Dictionary</summary>
/// <summary>Returns a new object enumerator for the Dictionary</summary> /// <returns>The new object enumerator</returns>
/// <returns>The new object enumerator</returns> IEnumerator IEnumerable.GetEnumerator() {
IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator();
return GetEnumerator(); }
}
#endregion
#endregion
#region IDictionary implementation
#region IDictionary implementation
/// <summary>Adds an item into the dictionary</summary>
/// <summary>Adds an item into the dictionary</summary> /// <param name="key">Key under which the item will be added</param>
/// <param name="key">Key under which the item will be added</param> /// <param name="value">Item that will be added</param>
/// <param name="value">Item that will be added</param> void IDictionary.Add(object key, object value) {
void IDictionary.Add(object key, object value) { Add((TKey)key, (TValue)value);
Add((TKey)key, (TValue)value); }
}
/// <summary>Determines whether the specified key exists in the dictionary</summary>
/// <summary>Determines whether the specified key exists in the dictionary</summary> /// <param name="key">Key that will be checked for</param>
/// <param name="key">Key that will be checked for</param> /// <returns>True if an item with the specified key exists in the dictionary</returns>
/// <returns>True if an item with the specified key exists in the dictionary</returns> bool IDictionary.Contains(object key) {
bool IDictionary.Contains(object key) { return this.objectDictionary.Contains(key);
return this.objectDictionary.Contains(key); }
}
/// <summary>Whether the size of the dictionary is fixed</summary>
/// <summary>Whether the size of the dictionary is fixed</summary> bool IDictionary.IsFixedSize {
bool IDictionary.IsFixedSize { get { return this.objectDictionary.IsFixedSize; }
get { return this.objectDictionary.IsFixedSize; } }
}
/// <summary>Returns a collection of all keys in the dictionary</summary>
/// <summary>Returns a collection of all keys in the dictionary</summary> ICollection IDictionary.Keys {
ICollection IDictionary.Keys { get { return this.objectDictionary.Keys; }
get { return this.objectDictionary.Keys; } }
}
/// <summary>Returns a collection of all values stored in the dictionary</summary>
/// <summary>Returns a collection of all values stored in the dictionary</summary> ICollection IDictionary.Values {
ICollection IDictionary.Values { get {
get { if(this.valueCollection == null) {
if(this.valueCollection == null) { this.valueCollection = new ValueCollection(this);
this.valueCollection = new ValueCollection(this); }
}
return this.valueCollection;
return this.valueCollection; }
} }
}
/// <summary>Removes an item from the dictionary</summary>
/// <summary>Removes an item from the dictionary</summary> /// <param name="key">Key of the item that will be removed</param>
/// <param name="key">Key of the item that will be removed</param> void IDictionary.Remove(object key) {
void IDictionary.Remove(object key) { RemoveKey((TKey)key);
RemoveKey((TKey)key); }
}
/// <summary>Accesses an item in the dictionary by its key</summary>
/// <summary>Accesses an item in the dictionary by its key</summary> /// <param name="key">Key of the item that will be accessed</param>
/// <param name="key">Key of the item that will be accessed</param> /// <returns>The item with the specified key</returns>
/// <returns>The item with the specified key</returns> object IDictionary.this[object key] {
object IDictionary.this[object key] { get { return this.objectDictionary[key]; }
get { return this.objectDictionary[key]; } set { this[(TKey)key] = (ICollection<TValue>)value; }
set { this[(TKey)key] = (ICollection<TValue>)value; } }
}
#endregion
#endregion
#region IDictionaryEnumerator implementation
#region IDictionaryEnumerator implementation
/// <summary>Returns a new entry enumerator for the dictionary</summary>
/// <summary>Returns a new entry enumerator for the dictionary</summary> /// <returns>The new entry enumerator</returns>
/// <returns>The new entry enumerator</returns> IDictionaryEnumerator IDictionary.GetEnumerator() {
IDictionaryEnumerator IDictionary.GetEnumerator() { return new Enumerator(this);
return new Enumerator(this); }
}
#endregion // IDictionaryEnumerator implementation
#endregion // IDictionaryEnumerator implementation
#region ICollection<KeyValuePair<TKey, TValue>> implementation
#region ICollection<KeyValuePair<TKey, TValue>> implementation
/// <summary>Inserts an already prepared element into the dictionary</summary>
/// <summary>Inserts an already prepared element into the dictionary</summary> /// <param name="item">Prepared element that will be added to the dictionary</param>
/// <param name="item">Prepared element that will be added to the dictionary</param> void ICollection<KeyValuePair<TKey, TValue>>.Add(
void ICollection<KeyValuePair<TKey, TValue>>.Add( KeyValuePair<TKey, TValue> item
KeyValuePair<TKey, TValue> item ) {
) { Add(item.Key, item.Value);
Add(item.Key, item.Value); }
}
/// <summary>Removes all items from the dictionary</summary>
/// <summary>Removes all items from the dictionary</summary> /// <param name="itemToRemove">Item that will be removed from the dictionary</param>
/// <param name="itemToRemove">Item that will be removed from the dictionary</param> bool ICollection<KeyValuePair<TKey, TValue>>.Remove(
bool ICollection<KeyValuePair<TKey, TValue>>.Remove( KeyValuePair<TKey, TValue> itemToRemove
KeyValuePair<TKey, TValue> itemToRemove ) {
) { ICollection<TValue> values;
ICollection<TValue> values; if(!this.typedDictionary.TryGetValue(itemToRemove.Key, out values)) {
if(!this.typedDictionary.TryGetValue(itemToRemove.Key, out values)) { return false;
return false; }
}
if(values.Remove(itemToRemove.Value)) {
if(values.Remove(itemToRemove.Value)) { if(values.Count == 0) {
if(values.Count == 0) { this.typedDictionary.Remove(itemToRemove.Key);
this.typedDictionary.Remove(itemToRemove.Key); }
} return true;
return true; } else {
} else { return false;
return false; }
} }
}
#endregion
#endregion
#region ICollection implementation
#region ICollection implementation
/// <summary>Copies the contents of the Dictionary into an array</summary>
/// <summary>Copies the contents of the Dictionary into an array</summary> /// <param name="array">Array the Dictionary contents will be copied into</param>
/// <param name="array">Array the Dictionary contents will be copied into</param> /// <param name="arrayIndex">
/// <param name="arrayIndex"> /// Starting index at which to begin filling the destination array
/// Starting index at which to begin filling the destination array /// </param>
/// </param> void ICollection.CopyTo(Array array, int arrayIndex) {
void ICollection.CopyTo(Array array, int arrayIndex) { foreach(KeyValuePair<TKey, ICollection<TValue>> item in this.typedDictionary) {
foreach(KeyValuePair<TKey, ICollection<TValue>> item in this.typedDictionary) { foreach(TValue value in item.Value) {
foreach(TValue value in item.Value) { array.SetValue(new KeyValuePair<TKey, TValue>(item.Key, value), arrayIndex);
array.SetValue(new KeyValuePair<TKey, TValue>(item.Key, value), arrayIndex); ++arrayIndex;
++arrayIndex; }
} }
} }
}
/// <summary>Whether the Dictionary is synchronized for multi-threaded usage</summary>
/// <summary>Whether the Dictionary is synchronized for multi-threaded usage</summary> bool ICollection.IsSynchronized {
bool ICollection.IsSynchronized { get { return this.objectDictionary.IsSynchronized; }
get { return this.objectDictionary.IsSynchronized; } }
}
/// <summary>Synchronization root on which the Dictionary locks</summary>
/// <summary>Synchronization root on which the Dictionary locks</summary> object ICollection.SyncRoot {
object ICollection.SyncRoot { get { return this.objectDictionary.SyncRoot; }
get { return this.objectDictionary.SyncRoot; } }
}
#endregion
#endregion
#region IDictionary<TKey, ICollection<TValue>> implementation
#region IDictionary<TKey, ICollection<TValue>> implementation
/// <summary>Adds a series of values to a dictionary</summary>
/// <summary>Adds a series of values to a dictionary</summary> /// <param name="key">Key under which the values will be added</param>
/// <param name="key">Key under which the values will be added</param> /// <param name="values">Values that will be added to the dictionary</param>
/// <param name="values">Values that will be added to the dictionary</param> void IDictionary<TKey, ICollection<TValue>>.Add(TKey key, ICollection<TValue> values) {
void IDictionary<TKey, ICollection<TValue>>.Add(TKey key, ICollection<TValue> values) { ICollection<TValue> currentValues;
ICollection<TValue> currentValues; if(!this.typedDictionary.TryGetValue(key, out currentValues)) {
if(!this.typedDictionary.TryGetValue(key, out currentValues)) { currentValues = new ValueList(this);
currentValues = new ValueList(this); }
}
foreach(TValue value in values) {
foreach(TValue value in values) { currentValues.Add(value);
currentValues.Add(value); }
} }
}
/// <summary>Removes all values with the specified key</summary>
/// <summary>Removes all values with the specified key</summary> /// <param name="key">Key whose associated entries will be removed</param>
/// <param name="key">Key whose associated entries will be removed</param> /// <returns>True if at least one entry has been removed from the dictionary</returns>
/// <returns>True if at least one entry has been removed from the dictionary</returns> bool IDictionary<TKey, ICollection<TValue>>.Remove(TKey key) {
bool IDictionary<TKey, ICollection<TValue>>.Remove(TKey key) { return (RemoveKey(key) > 0);
return (RemoveKey(key) > 0); }
}
/// <summary>Returns a collection of value collections</summary>
/// <summary>Returns a collection of value collections</summary> ICollection<ICollection<TValue>> IDictionary<TKey, ICollection<TValue>>.Values {
ICollection<ICollection<TValue>> IDictionary<TKey, ICollection<TValue>>.Values { get { return this.typedDictionary.Values; }
get { return this.typedDictionary.Values; } }
}
#endregion // IDictionary<TKey, ICollection<TValue>> implementation
#endregion // IDictionary<TKey, ICollection<TValue>> implementation
#region ICollection<KeyValuePair<TKey, ICollection<TValue>>> implementation
#region ICollection<KeyValuePair<TKey, ICollection<TValue>>> implementation
/// <summary>Adds a series of values to a dictionary</summary>
/// <summary>Adds a series of values to a dictionary</summary> /// <param name="item">Entry containing the values that will be added</param>
/// <param name="item">Entry containing the values that will be added</param> void ICollection<KeyValuePair<TKey, ICollection<TValue>>>.Add(
void ICollection<KeyValuePair<TKey, ICollection<TValue>>>.Add( KeyValuePair<TKey, ICollection<TValue>> item
KeyValuePair<TKey, ICollection<TValue>> item ) {
) { ICollection<TValue> currentValues;
ICollection<TValue> currentValues; if(!this.typedDictionary.TryGetValue(item.Key, out currentValues)) {
if(!this.typedDictionary.TryGetValue(item.Key, out currentValues)) { currentValues = new ValueList(this);
currentValues = new ValueList(this); }
}
foreach(TValue value in item.Value) {
foreach(TValue value in item.Value) { currentValues.Add(value);
currentValues.Add(value); }
} }
}
/// <summary>
/// <summary> /// Checks whether the dictionary contains the specified key/value pair
/// Checks whether the dictionary contains the specified key/value pair /// </summary>
/// </summary> /// <param name="item">Key/value pair for which the dictionary will be checked</param>
/// <param name="item">Key/value pair for which the dictionary will be checked</param> /// <returns>True if the dictionary contains the specified key/value pair</returns>
/// <returns>True if the dictionary contains the specified key/value pair</returns> bool ICollection<KeyValuePair<TKey, ICollection<TValue>>>.Contains(
bool ICollection<KeyValuePair<TKey, ICollection<TValue>>>.Contains( KeyValuePair<TKey, ICollection<TValue>> item
KeyValuePair<TKey, ICollection<TValue>> item ) {
) { return this.typedDictionary.Contains(item);
return this.typedDictionary.Contains(item); }
}
/// <summary>Copies the contents of the dictionary into an array</summary>
/// <summary>Copies the contents of the dictionary into an array</summary> /// <param name="array"></param>
/// <param name="array"></param> /// <param name="arrayIndex"></param>
/// <param name="arrayIndex"></param> void ICollection<KeyValuePair<TKey, ICollection<TValue>>>.CopyTo(
void ICollection<KeyValuePair<TKey, ICollection<TValue>>>.CopyTo( KeyValuePair<TKey, ICollection<TValue>>[] array, int arrayIndex
KeyValuePair<TKey, ICollection<TValue>>[] array, int arrayIndex ) {
) { this.typedDictionary.CopyTo(array, arrayIndex);
this.typedDictionary.CopyTo(array, arrayIndex); }
}
/// <summary>Removes the specified key/value pair from the dictionary</summary>
/// <summary>Removes the specified key/value pair from the dictionary</summary> /// <param name="item">Key/value pair that will be removed</param>
/// <param name="item">Key/value pair that will be removed</param> /// <returns>True if the key/value pair was contained in the dictionary</returns>
/// <returns>True if the key/value pair was contained in the dictionary</returns> bool ICollection<KeyValuePair<TKey, ICollection<TValue>>>.Remove(
bool ICollection<KeyValuePair<TKey, ICollection<TValue>>>.Remove( KeyValuePair<TKey, ICollection<TValue>> item
KeyValuePair<TKey, ICollection<TValue>> item ) {
) { return this.typedDictionary.Remove(item);
return this.typedDictionary.Remove(item); }
}
/// <summary>Returns an enumerator for the dictionary</summary>
/// <summary>Returns an enumerator for the dictionary</summary> /// <returns>An enumerator for the key/value pairs in the dictionary</returns>
/// <returns>An enumerator for the key/value pairs in the dictionary</returns> IEnumerator<KeyValuePair<TKey, ICollection<TValue>>> IEnumerable<
IEnumerator<KeyValuePair<TKey, ICollection<TValue>>> IEnumerable< KeyValuePair<TKey, ICollection<TValue>>
KeyValuePair<TKey, ICollection<TValue>> >.GetEnumerator() {
>.GetEnumerator() { return this.typedDictionary.GetEnumerator();
return this.typedDictionary.GetEnumerator(); }
}
/// <summary>Number of unique keys in the dictionary</summary>
/// <summary>Number of unique keys in the dictionary</summary> /// <remarks>
/// <remarks> /// <para>
/// <para> /// This Count property returns a different value from the main interface of
/// This Count property returns a different value from the main interface of /// the multi dictionary to stay consistent with the implemented interfaces.
/// the multi dictionary to stay consistent with the implemented interfaces. /// </para>
/// </para> /// <para>
/// <para> /// If you cast a multi dictionary to a collection of collections, the count
/// If you cast a multi dictionary to a collection of collections, the count /// property of the outer collection should, of course, be the number of inner
/// property of the outer collection should, of course, be the number of inner /// collections it contains (and not the sum of the items contained in all of
/// collections it contains (and not the sum of the items contained in all of /// the inner collections).
/// the inner collections). /// </para>
/// </para> /// <para>
/// <para> /// If you use the count property in the main interface of the multi dictionary,
/// If you use the count property in the main interface of the multi dictionary, /// the value collections are hidden (it behaves as if the key was in the
/// the value collections are hidden (it behaves as if the key was in the /// dictionary multiple times), so now the sum of all key-value pairs should
/// dictionary multiple times), so now the sum of all key-value pairs should /// be returned.
/// be returned. /// </para>
/// </para> /// </remarks>
/// </remarks> int ICollection<KeyValuePair<TKey, ICollection<TValue>>>.Count {
int ICollection<KeyValuePair<TKey, ICollection<TValue>>>.Count { get { return this.typedDictionary.Count; }
get { return this.typedDictionary.Count; } }
}
#endregion // ICollection<KeyValuePair<TKey, ICollection<TValue>>> implementation
#endregion // ICollection<KeyValuePair<TKey, ICollection<TValue>>> implementation
}
}
} // namespace Nuclex.Support.Collections
} // namespace Nuclex.Support.Collections

View File

@ -1,391 +1,390 @@
#region CPL License #region Apache License 2.0
/* /*
Nuclex Framework Nuclex .NET Framework
Copyright (C) 2002-2017 Nuclex Development Labs Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
This library is free software; you can redistribute it and/or Licensed under the Apache License, Version 2.0 (the "License");
modify it under the terms of the IBM Common Public License as you may not use this file except in compliance with the License.
published by the IBM Corporation; either version 1.0 of the You may obtain a copy of the License at
License, or (at your option) any later version.
http://www.apache.org/licenses/LICENSE-2.0
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of Unless required by applicable law or agreed to in writing, software
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the distributed under the License is distributed on an "AS IS" BASIS,
IBM Common Public License for more details. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
You should have received a copy of the IBM Common Public limitations under the License.
License along with this library */
*/ #endregion // Apache License 2.0
#endregion
#if UNITTEST
#if UNITTEST
using System;
using System; using System.Collections.Generic;
using System.Collections.Generic;
using NUnit.Framework;
using NUnit.Framework; using NMock;
using NMock;
namespace Nuclex.Support.Collections {
namespace Nuclex.Support.Collections {
/// <summary>Unit tests for the multi dictionary</summary>
/// <summary>Unit tests for the multi dictionary</summary> [TestFixture]
[TestFixture] internal class MultiDictionaryTest {
internal class MultiDictionaryTest {
/// <summary>
/// <summary> /// Verifies that new instances of the multi dictionary can be created
/// Verifies that new instances of the multi dictionary can be created /// </summary>
/// </summary> [Test]
[Test] public void CanConstructNewDictionary() {
public void CanConstructNewDictionary() { var dictionary = new MultiDictionary<int, string>();
var dictionary = new MultiDictionary<int, string>(); Assert.IsNotNull(dictionary); // nonsense, prevents compiler warning
Assert.IsNotNull(dictionary); // nonsense, prevents compiler warning }
}
/// <summary>
/// <summary> /// Verifies that the count is initialized correctly when building
/// Verifies that the count is initialized correctly when building /// a multi dictionary from a dictionary of value collections.
/// a multi dictionary from a dictionary of value collections. /// </summary>
/// </summary> [Test]
[Test] public void CountIsCalculatedIfInitializedFromDictionary() {
public void CountIsCalculatedIfInitializedFromDictionary() { var contents = new Dictionary<int, ICollection<string>>();
var contents = new Dictionary<int, ICollection<string>>(); contents.Add(1, new List<string>(new string[] { "one", "eins" }));
contents.Add(1, new List<string>(new string[] { "one", "eins" })); contents.Add(2, new List<string>(new string[] { "two", "zwei" }));
contents.Add(2, new List<string>(new string[] { "two", "zwei" }));
var multiDictionary = new MultiDictionary<int, string>(contents);
var multiDictionary = new MultiDictionary<int, string>(contents); Assert.AreEqual(4, multiDictionary.Count);
Assert.AreEqual(4, multiDictionary.Count); }
}
/// <summary>
/// <summary> /// Verifies that a new multi dictionary based on a read-only dictionary is
/// Verifies that a new multi dictionary based on a read-only dictionary is /// also read-only
/// also read-only /// </summary>
/// </summary> [Test]
[Test] public void IsReadOnlyWhenBasedOnReadOnlyContainer() {
public void IsReadOnlyWhenBasedOnReadOnlyContainer() { var readOnly = new ReadOnlyDictionary<int, ICollection<string>>(
var readOnly = new ReadOnlyDictionary<int, ICollection<string>>( new Dictionary<int, ICollection<string>>()
new Dictionary<int, ICollection<string>>() );
); var dictionary = new MultiDictionary<int, string>(readOnly);
var dictionary = new MultiDictionary<int, string>(readOnly);
Assert.IsTrue(dictionary.IsReadOnly);
Assert.IsTrue(dictionary.IsReadOnly); }
}
/// <summary>
/// <summary> /// Ensures that the multi dictionary can contain the same key multiple times
/// Ensures that the multi dictionary can contain the same key multiple times /// (or in other words, multiple values on the same key)
/// (or in other words, multiple values on the same key) /// </summary>
/// </summary> [Test]
[Test] public void CanContainKeyMultipleTimes() {
public void CanContainKeyMultipleTimes() { var dictionary = new MultiDictionary<int, string>();
var dictionary = new MultiDictionary<int, string>(); dictionary.Add(123, "one two three");
dictionary.Add(123, "one two three"); dictionary.Add(123, "eins zwei drei");
dictionary.Add(123, "eins zwei drei");
Assert.AreEqual(2, dictionary.Count);
Assert.AreEqual(2, dictionary.Count);
CollectionAssert.AreEquivalent(
CollectionAssert.AreEquivalent( new KeyValuePair<int, string>[] {
new KeyValuePair<int, string>[] { new KeyValuePair<int, string>(123, "one two three"),
new KeyValuePair<int, string>(123, "one two three"), new KeyValuePair<int, string>(123, "eins zwei drei")
new KeyValuePair<int, string>(123, "eins zwei drei") },
}, dictionary
dictionary );
); }
}
/// <summary>
/// <summary> /// Verifies that adding values through the indexer still updates the item count
/// Verifies that adding values through the indexer still updates the item count /// </summary>
/// </summary> [Test]
[Test] public void AddingValuesFromIndexerUpdatesCount() {
public void AddingValuesFromIndexerUpdatesCount() { var dictionary = new MultiDictionary<int, string>();
var dictionary = new MultiDictionary<int, string>(); dictionary.Add(42, "the answer to everything");
dictionary.Add(42, "the answer to everything"); dictionary[42].Add("21x2");
dictionary[42].Add("21x2");
Assert.AreEqual(2, dictionary.Count);
Assert.AreEqual(2, dictionary.Count);
CollectionAssert.AreEquivalent(
CollectionAssert.AreEquivalent( new KeyValuePair<int, string>[] {
new KeyValuePair<int, string>[] { new KeyValuePair<int, string>(42, "the answer to everything"),
new KeyValuePair<int, string>(42, "the answer to everything"), new KeyValuePair<int, string>(42, "21x2")
new KeyValuePair<int, string>(42, "21x2") },
}, dictionary
dictionary );
); }
}
/// <summary>
/// <summary> /// Tests whether the collection can count the number of values stored
/// Tests whether the collection can count the number of values stored /// under a key
/// under a key /// </summary>
/// </summary> [Test]
[Test] public void ValuesWithSameKeyCanBeCounted() {
public void ValuesWithSameKeyCanBeCounted() { var dictionary = new MultiDictionary<int, string>();
var dictionary = new MultiDictionary<int, string>(); dictionary.Add(10, "ten");
dictionary.Add(10, "ten"); dictionary.Add(20, "twenty");
dictionary.Add(20, "twenty"); dictionary.Add(30, "thirty");
dictionary.Add(30, "thirty"); dictionary.Add(10, "zehn");
dictionary.Add(10, "zehn"); dictionary.Add(20, "zwanzig");
dictionary.Add(20, "zwanzig"); dictionary.Add(10, "dix");
dictionary.Add(10, "dix");
Assert.AreEqual(6, dictionary.Count);
Assert.AreEqual(6, dictionary.Count); Assert.AreEqual(3, dictionary.CountValues(10));
Assert.AreEqual(3, dictionary.CountValues(10)); Assert.AreEqual(2, dictionary.CountValues(20));
Assert.AreEqual(2, dictionary.CountValues(20)); Assert.AreEqual(1, dictionary.CountValues(30));
Assert.AreEqual(1, dictionary.CountValues(30)); }
}
/// <summary>
/// <summary> /// Verifies that counting the values of a non-existing key returns 0
/// Verifies that counting the values of a non-existing key returns 0 /// </summary>
/// </summary> [Test]
[Test] public void CountingValuesOfNonExistentKeyReturnsNull() {
public void CountingValuesOfNonExistentKeyReturnsNull() { var dictionary = new MultiDictionary<int, string>();
var dictionary = new MultiDictionary<int, string>(); Assert.AreEqual(0, dictionary.CountValues(1));
Assert.AreEqual(0, dictionary.CountValues(1)); }
}
/// <summary>
/// <summary> /// Ensures that its possible to remove values individually without affecting
/// Ensures that its possible to remove values individually without affecting /// other values stored under the same key
/// other values stored under the same key /// </summary>
/// </summary> [Test]
[Test] public void ValuesCanBeRemovedIndividually() {
public void ValuesCanBeRemovedIndividually() { var dictionary = new MultiDictionary<int, string>();
var dictionary = new MultiDictionary<int, string>(); dictionary.Add(10, "ten");
dictionary.Add(10, "ten"); dictionary.Add(10, "zehn");
dictionary.Add(10, "zehn"); dictionary.Add(10, "dix");
dictionary.Add(10, "dix");
dictionary.Remove(10, "zehn");
dictionary.Remove(10, "zehn");
Assert.AreEqual(2, dictionary.Count);
Assert.AreEqual(2, dictionary.Count); CollectionAssert.AreEquivalent(
CollectionAssert.AreEquivalent( new KeyValuePair<int, string>[] {
new KeyValuePair<int, string>[] { new KeyValuePair<int, string>(10, "ten"),
new KeyValuePair<int, string>(10, "ten"), new KeyValuePair<int, string>(10, "dix")
new KeyValuePair<int, string>(10, "dix") },
}, dictionary
dictionary );
); }
}
/// <summary>
/// <summary> /// Verifies that the Count property returns the number of unique keys if it is called
/// Verifies that the Count property returns the number of unique keys if it is called /// on the collection-of-collections interface implemented by the multi dictionary
/// on the collection-of-collections interface implemented by the multi dictionary /// </summary>
/// </summary> [Test]
[Test] public void CollectionOfCollectionCountIsUniqueKeyCount() {
public void CollectionOfCollectionCountIsUniqueKeyCount() { var dictionary = new MultiDictionary<int, string>();
var dictionary = new MultiDictionary<int, string>(); dictionary.Add(10, "ten");
dictionary.Add(10, "ten"); dictionary.Add(10, "zehn");
dictionary.Add(10, "zehn");
Assert.AreEqual(2, dictionary.Count);
Assert.AreEqual(2, dictionary.Count); var collectionOfCollections =
var collectionOfCollections = (ICollection<KeyValuePair<int, ICollection<string>>>)dictionary;
(ICollection<KeyValuePair<int, ICollection<string>>>)dictionary; Assert.AreEqual(1, collectionOfCollections.Count);
Assert.AreEqual(1, collectionOfCollections.Count); }
}
/// <summary>
/// <summary> /// Verifies that the multi dictionary can be tested for containment of a specific value
/// Verifies that the multi dictionary can be tested for containment of a specific value /// </summary>
/// </summary> [Test]
[Test] public void ContainmentCanBeTested() {
public void ContainmentCanBeTested() { var dictionary = new MultiDictionary<int, string>();
var dictionary = new MultiDictionary<int, string>(); dictionary.Add(10, "ten");
dictionary.Add(10, "ten"); dictionary.Add(10, "zehn");
dictionary.Add(10, "zehn");
Assert.IsTrue(dictionary.Contains(new KeyValuePair<int, string>(10, "ten")));
Assert.IsTrue(dictionary.Contains(new KeyValuePair<int, string>(10, "ten"))); Assert.IsTrue(dictionary.Contains(new KeyValuePair<int, string>(10, "zehn")));
Assert.IsTrue(dictionary.Contains(new KeyValuePair<int, string>(10, "zehn"))); Assert.IsFalse(dictionary.Contains(new KeyValuePair<int, string>(10, "dix")));
Assert.IsFalse(dictionary.Contains(new KeyValuePair<int, string>(10, "dix"))); Assert.IsFalse(dictionary.Contains(new KeyValuePair<int, string>(20, "ten")));
Assert.IsFalse(dictionary.Contains(new KeyValuePair<int, string>(20, "ten"))); }
}
/// <summary>
/// <summary> /// Verifies that the multi dictionary can be tested for containment of a specific key
/// Verifies that the multi dictionary can be tested for containment of a specific key /// </summary>
/// </summary> [Test]
[Test] public void KeyContainmentCanBeTested() {
public void KeyContainmentCanBeTested() { var dictionary = new MultiDictionary<int, string>();
var dictionary = new MultiDictionary<int, string>(); dictionary.Add(10, "ten");
dictionary.Add(10, "ten"); dictionary.Add(10, "zehn");
dictionary.Add(10, "zehn");
Assert.IsTrue(dictionary.ContainsKey(10));
Assert.IsTrue(dictionary.ContainsKey(10)); Assert.IsFalse(dictionary.ContainsKey(20));
Assert.IsFalse(dictionary.ContainsKey(20)); }
}
/// <summary>
/// <summary> /// Verifies that the key collection can be retrieved from the dictionary
/// Verifies that the key collection can be retrieved from the dictionary /// </summary>
/// </summary> [Test]
[Test] public void KeyCollectionCanBeRetrieved() {
public void KeyCollectionCanBeRetrieved() { var dictionary = new MultiDictionary<int, string>();
var dictionary = new MultiDictionary<int, string>(); dictionary.Add(10, "ten");
dictionary.Add(10, "ten"); dictionary.Add(10, "zehn");
dictionary.Add(10, "zehn");
ICollection<int> keys = dictionary.Keys;
ICollection<int> keys = dictionary.Keys; Assert.IsNotNull(keys);
Assert.IsNotNull(keys); Assert.AreEqual(1, keys.Count);
Assert.AreEqual(1, keys.Count); }
}
/// <summary>
/// <summary> /// Verifies that the key collection can be retrieved from the dictionary
/// Verifies that the key collection can be retrieved from the dictionary /// </summary>
/// </summary> [Test]
[Test] public void ValueCollectionCanBeRetrieved() {
public void ValueCollectionCanBeRetrieved() { var dictionary = new MultiDictionary<int, string>();
var dictionary = new MultiDictionary<int, string>(); dictionary.Add(10, "ten");
dictionary.Add(10, "ten"); dictionary.Add(10, "zehn");
dictionary.Add(10, "zehn"); dictionary.Add(20, "twenty");
dictionary.Add(20, "twenty");
ICollection<string> values = dictionary.Values;
ICollection<string> values = dictionary.Values; Assert.IsNotNull(values);
Assert.IsNotNull(values); Assert.AreEqual(3, values.Count);
Assert.AreEqual(3, values.Count); }
}
/// <summary>
/// <summary> /// Verifies that TryGetValue() returns false and doesn't throw if a key
/// Verifies that TryGetValue() returns false and doesn't throw if a key /// is not found in the collection
/// is not found in the collection /// </summary>
/// </summary> [Test]
[Test] public void TryGetValueReturnsFalseOnMissingKey() {
public void TryGetValueReturnsFalseOnMissingKey() { var dictionary = new MultiDictionary<int, string>();
var dictionary = new MultiDictionary<int, string>(); ICollection<string> values;
ICollection<string> values; Assert.IsFalse(dictionary.TryGetValue(123, out values));
Assert.IsFalse(dictionary.TryGetValue(123, out values)); }
}
/// <summary>Verifies that keys can be looked up via TryGetValue()</summary>
/// <summary>Verifies that keys can be looked up via TryGetValue()</summary> [Test]
[Test] public void TryGetValueCanLookUpValues() {
public void TryGetValueCanLookUpValues() { var dictionary = new MultiDictionary<int, string>();
var dictionary = new MultiDictionary<int, string>(); dictionary.Add(10, "ten");
dictionary.Add(10, "ten"); dictionary.Add(10, "zehn");
dictionary.Add(10, "zehn"); ICollection<string> values;
ICollection<string> values; Assert.IsTrue(dictionary.TryGetValue(10, out values));
Assert.IsTrue(dictionary.TryGetValue(10, out values)); Assert.AreEqual(2, values.Count);
Assert.AreEqual(2, values.Count); }
}
/// <summary>
/// <summary> /// Verifies that assigning null to a key deletes all the values stored
/// Verifies that assigning null to a key deletes all the values stored /// under it
/// under it /// </summary>
/// </summary> [Test]
[Test] public void AssigningNullToKeyRemovesAllValues() {
public void AssigningNullToKeyRemovesAllValues() { var dictionary = new MultiDictionary<int, string>();
var dictionary = new MultiDictionary<int, string>(); dictionary.Add(10, "ten");
dictionary.Add(10, "ten"); dictionary.Add(10, "zehn");
dictionary.Add(10, "zehn"); dictionary.Add(20, "twenty");
dictionary.Add(20, "twenty");
Assert.AreEqual(3, dictionary.Count);
Assert.AreEqual(3, dictionary.Count); dictionary[10] = null;
dictionary[10] = null; Assert.AreEqual(1, dictionary.Count);
Assert.AreEqual(1, dictionary.Count); Assert.IsFalse(dictionary.ContainsKey(10));
Assert.IsFalse(dictionary.ContainsKey(10)); }
}
/// <summary>
/// <summary> /// Verifies that assigning null to a key deletes all the values stored
/// Verifies that assigning null to a key deletes all the values stored /// under it
/// under it /// </summary>
/// </summary> [Test]
[Test] public void ValueListCanBeAssignedToNewKey() {
public void ValueListCanBeAssignedToNewKey() { var dictionary = new MultiDictionary<int, string>();
var dictionary = new MultiDictionary<int, string>(); dictionary[3] = new List<string>() { "three", "drei" };
dictionary[3] = new List<string>() { "three", "drei" };
Assert.AreEqual(2, dictionary.Count);
Assert.AreEqual(2, dictionary.Count); Assert.IsTrue(dictionary.Contains(new KeyValuePair<int, string>(3, "three")));
Assert.IsTrue(dictionary.Contains(new KeyValuePair<int, string>(3, "three"))); }
}
/// <summary>
/// <summary> /// Verifies that assigning null to a key deletes all the values stored
/// Verifies that assigning null to a key deletes all the values stored /// under it
/// under it /// </summary>
/// </summary> [Test]
[Test] public void ValueListCanOverwriteExistingKey() {
public void ValueListCanOverwriteExistingKey() { var dictionary = new MultiDictionary<int, string>();
var dictionary = new MultiDictionary<int, string>(); dictionary.Add(10, "dix");
dictionary.Add(10, "dix");
Assert.AreEqual(1, dictionary.Count);
Assert.AreEqual(1, dictionary.Count);
dictionary[10] = new List<string>() { "ten", "zehn" };
dictionary[10] = new List<string>() { "ten", "zehn" };
Assert.AreEqual(2, dictionary.Count);
Assert.AreEqual(2, dictionary.Count); Assert.IsFalse(dictionary.Contains(new KeyValuePair<int, string>(10, "dix")));
Assert.IsFalse(dictionary.Contains(new KeyValuePair<int, string>(10, "dix"))); Assert.IsTrue(dictionary.Contains(new KeyValuePair<int, string>(10, "ten")));
Assert.IsTrue(dictionary.Contains(new KeyValuePair<int, string>(10, "ten"))); }
}
/// <summary>
/// <summary> /// Verifies that nothing bad happens when a key is removed from the dictionary
/// Verifies that nothing bad happens when a key is removed from the dictionary /// that it doesn't contain
/// that it doesn't contain /// </summary>
/// </summary> [Test]
[Test] public void NonExistingKeyCanBeRemoved() {
public void NonExistingKeyCanBeRemoved() { var dictionary = new MultiDictionary<int, string>();
var dictionary = new MultiDictionary<int, string>(); Assert.AreEqual(0, dictionary.RemoveKey(123));
Assert.AreEqual(0, dictionary.RemoveKey(123)); }
}
/// <summary>
/// <summary> /// Verifies that the remove method returns the number of values that have
/// Verifies that the remove method returns the number of values that have /// been removed from the dictionary
/// been removed from the dictionary /// </summary>
/// </summary> [Test]
[Test] public void RemoveReturnsNumberOfValuesRemoved() {
public void RemoveReturnsNumberOfValuesRemoved() { var dictionary = new MultiDictionary<int, string>();
var dictionary = new MultiDictionary<int, string>(); dictionary.Add(10, "ten");
dictionary.Add(10, "ten"); dictionary.Add(10, "zehn");
dictionary.Add(10, "zehn"); Assert.AreEqual(2, dictionary.RemoveKey(10));
Assert.AreEqual(2, dictionary.RemoveKey(10)); }
}
/// <summary>
/// <summary> /// Verifies that the dictionary becomes empty after clearing it
/// Verifies that the dictionary becomes empty after clearing it /// </summary>
/// </summary> [Test]
[Test] public void DictionaryIsEmptyAfterClear() {
public void DictionaryIsEmptyAfterClear() { var dictionary = new MultiDictionary<int, string>();
var dictionary = new MultiDictionary<int, string>(); dictionary.Add(10, "ten");
dictionary.Add(10, "ten"); dictionary.Add(10, "zehn");
dictionary.Add(10, "zehn"); dictionary.Add(20, "twenty");
dictionary.Add(20, "twenty"); Assert.AreEqual(3, dictionary.Count);
Assert.AreEqual(3, dictionary.Count); dictionary.Clear();
dictionary.Clear(); Assert.AreEqual(0, dictionary.Count);
Assert.AreEqual(0, dictionary.Count); }
}
/// <summary>
/// <summary> /// Verifies that non-existing values can be removed from the dictionary
/// Verifies that non-existing values can be removed from the dictionary /// </summary>
/// </summary> [Test]
[Test] public void NonExistingValueCanBeRemoved() {
public void NonExistingValueCanBeRemoved() { var dictionary = new MultiDictionary<int, string>();
var dictionary = new MultiDictionary<int, string>(); Assert.IsFalse(dictionary.Remove(123, "test"));
Assert.IsFalse(dictionary.Remove(123, "test")); }
}
/// <summary>
/// <summary> /// Verifies that nothing bad happens when the last value under a key is removed
/// Verifies that nothing bad happens when the last value under a key is removed /// </summary>
/// </summary> [Test]
[Test] public void LastValueOfKeyCanBeRemoved() {
public void LastValueOfKeyCanBeRemoved() { var dictionary = new MultiDictionary<int, string>();
var dictionary = new MultiDictionary<int, string>(); dictionary.Add(123, "test");
dictionary.Add(123, "test"); dictionary.Remove(123, "test");
dictionary.Remove(123, "test"); Assert.AreEqual(0, dictionary.CountValues(123));
Assert.AreEqual(0, dictionary.CountValues(123)); }
}
/// <summary>
/// <summary> /// Verifies that the dictionary can be copied into an array
/// Verifies that the dictionary can be copied into an array /// </summary>
/// </summary> [Test]
[Test] public void DictionaryCanBeCopiedIntoArray() {
public void DictionaryCanBeCopiedIntoArray() { var expected = new List<KeyValuePair<int, string>>() {
var expected = new List<KeyValuePair<int, string>>() { new KeyValuePair<int, string>(1, "one"),
new KeyValuePair<int, string>(1, "one"), new KeyValuePair<int, string>(1, "eins"),
new KeyValuePair<int, string>(1, "eins"), new KeyValuePair<int, string>(2, "two"),
new KeyValuePair<int, string>(2, "two"), new KeyValuePair<int, string>(2, "zwei")
new KeyValuePair<int, string>(2, "zwei") };
};
var dictionary = new MultiDictionary<int, string>();
var dictionary = new MultiDictionary<int, string>(); foreach(KeyValuePair<int, string> entry in expected) {
foreach(KeyValuePair<int, string> entry in expected) { dictionary.Add(entry.Key, entry.Value);
dictionary.Add(entry.Key, entry.Value); }
}
var actual = new KeyValuePair<int, string>[4];
var actual = new KeyValuePair<int, string>[4]; dictionary.CopyTo(actual, 0);
dictionary.CopyTo(actual, 0);
CollectionAssert.AreEquivalent(expected, actual);
CollectionAssert.AreEquivalent(expected, actual); }
}
}
}
} // namespace Nuclex.Support.Collections
} // namespace Nuclex.Support.Collections
#endif // UNITTEST
#endif // UNITTEST

View File

@ -1,269 +1,268 @@
#region CPL License #region Apache License 2.0
/* /*
Nuclex Framework Nuclex .NET Framework
Copyright (C) 2002-2017 Nuclex Development Labs Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
This library is free software; you can redistribute it and/or Licensed under the Apache License, Version 2.0 (the "License");
modify it under the terms of the IBM Common Public License as you may not use this file except in compliance with the License.
published by the IBM Corporation; either version 1.0 of the You may obtain a copy of the License at
License, or (at your option) any later version.
http://www.apache.org/licenses/LICENSE-2.0
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of Unless required by applicable law or agreed to in writing, software
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the distributed under the License is distributed on an "AS IS" BASIS,
IBM Common Public License for more details. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
You should have received a copy of the IBM Common Public limitations under the License.
License along with this library */
*/ #endregion // Apache License 2.0
#endregion
using System;
using System; using System.Collections.Generic;
using System.Collections.Generic; using System.Collections;
using System.Collections;
namespace Nuclex.Support.Collections {
namespace Nuclex.Support.Collections {
partial class MultiDictionary<TKey, TValue> {
partial class MultiDictionary<TKey, TValue> {
/// <summary>
/// <summary> /// Provides access to the values stored in a multi dictionary as a collection
/// Provides access to the values stored in a multi dictionary as a collection /// </summary>
/// </summary> private class ValueCollection : ICollection<TValue>, ICollection {
private class ValueCollection : ICollection<TValue>, ICollection {
#region class Enumerator
#region class Enumerator
/// <summary>Enumerates the values stored in a multi dictionary</summary>
/// <summary>Enumerates the values stored in a multi dictionary</summary> private class Enumerator : IEnumerator<TValue> {
private class Enumerator : IEnumerator<TValue> {
/// <summary>Initializes a new enumerator</summary>
/// <summary>Initializes a new enumerator</summary> /// <param name="valueCollections">Value collections being enumerated</param>
/// <param name="valueCollections">Value collections being enumerated</param> public Enumerator(ICollection<ICollection<TValue>> valueCollections) {
public Enumerator(ICollection<ICollection<TValue>> valueCollections) { this.valueCollections = valueCollections;
this.valueCollections = valueCollections;
Reset();
Reset(); }
}
/// <summary>Immediately releases all resources owned by the instance</summary>
/// <summary>Immediately releases all resources owned by the instance</summary> public void Dispose() {
public void Dispose() { if(this.currentValue != null) {
if(this.currentValue != null) { this.currentValue.Dispose();
this.currentValue.Dispose(); this.currentValue = null;
this.currentValue = null; }
} if(this.currentCollection != null) {
if(this.currentCollection != null) { this.currentCollection.Dispose();
this.currentCollection.Dispose(); this.currentCollection = null;
this.currentCollection = null; }
} }
}
/// <summary>The current value the enumerator is pointing at</summary>
/// <summary>The current value the enumerator is pointing at</summary> public TValue Current {
public TValue Current { get {
get { if(this.currentValue == null) {
if(this.currentValue == null) { throw new InvalidOperationException("Enumerator is not on a valid position");
throw new InvalidOperationException("Enumerator is not on a valid position"); }
}
return this.currentValue.Current;
return this.currentValue.Current; }
} }
}
/// <summary>Advances the enumerator to the next item</summary>
/// <summary>Advances the enumerator to the next item</summary> /// <returns>
/// <returns> /// True if there was a next item, false if the enumerator reached the end
/// True if there was a next item, false if the enumerator reached the end /// </returns>
/// </returns> public bool MoveNext() {
public bool MoveNext() { if(this.currentCollection == null) {
if(this.currentCollection == null) { return false;
return false; }
}
for(; ; ) {
for(; ; ) {
// Try to move the enumerator in the current key's list to the next item
// Try to move the enumerator in the current key's list to the next item if(this.currentValue != null) {
if(this.currentValue != null) { if(this.currentValue.MoveNext()) {
if(this.currentValue.MoveNext()) { return true; // We found the next item
return true; // We found the next item } else {
} else { this.currentValue.Dispose();
this.currentValue.Dispose(); }
} }
}
// Enumerator for the current key's list reached the end, go to the next key
// Enumerator for the current key's list reached the end, go to the next key if(this.currentCollection.MoveNext()) {
if(this.currentCollection.MoveNext()) { this.currentValue = this.currentCollection.Current.GetEnumerator();
this.currentValue = this.currentCollection.Current.GetEnumerator(); } else {
} else { this.currentValue = null; // Guaranteed to be disposed already
this.currentValue = null; // Guaranteed to be disposed already this.currentCollection.Dispose();
this.currentCollection.Dispose(); this.currentCollection = null;
this.currentCollection = null; return false;
return false; }
}
}
} }
}
/// <summary>Resets the enumerator to its initial position</summary>
/// <summary>Resets the enumerator to its initial position</summary> public void Reset() {
public void Reset() { if(this.currentValue != null) {
if(this.currentValue != null) { this.currentValue.Dispose();
this.currentValue.Dispose(); this.currentValue = null;
this.currentValue = null; }
} if(this.currentCollection != null) {
if(this.currentCollection != null) { this.currentCollection.Dispose();
this.currentCollection.Dispose(); }
} this.currentCollection = valueCollections.GetEnumerator();
this.currentCollection = valueCollections.GetEnumerator(); }
}
#region IEnumerator implementation
#region IEnumerator implementation
/// <summary>The current entry the enumerator is pointing at</summary>
/// <summary>The current entry the enumerator is pointing at</summary> object IEnumerator.Current {
object IEnumerator.Current { get { return Current; }
get { return Current; } }
}
#endregion // IEnumerator implementation
#endregion // IEnumerator implementation
/// <summary>Value collections being enumerated</summary>
/// <summary>Value collections being enumerated</summary> private ICollection<ICollection<TValue>> valueCollections;
private ICollection<ICollection<TValue>> valueCollections; /// <summary>The current value collection the enumerator is in</summary>
/// <summary>The current value collection the enumerator is in</summary> private IEnumerator<ICollection<TValue>> currentCollection;
private IEnumerator<ICollection<TValue>> currentCollection; /// <summary>Current value in the collection the enumerator is in</summary>
/// <summary>Current value in the collection the enumerator is in</summary> private IEnumerator<TValue> currentValue;
private IEnumerator<TValue> currentValue;
}
}
#endregion // class Enumerator
#endregion // class Enumerator
/// <summary>Initializes a new multi dictionary value collection</summary>
/// <summary>Initializes a new multi dictionary value collection</summary> /// <param name="dictionary">Dictionary whose values the collection represents</param>
/// <param name="dictionary">Dictionary whose values the collection represents</param> public ValueCollection(MultiDictionary<TKey, TValue> dictionary) {
public ValueCollection(MultiDictionary<TKey, TValue> dictionary) { this.dictionary = dictionary;
this.dictionary = dictionary; this.dictionaryAsICollection = (ICollection)dictionary;
this.dictionaryAsICollection = (ICollection)dictionary; }
}
/// <summary>Determines whether the collection contains a specific value</summary>
/// <summary>Determines whether the collection contains a specific value</summary> /// <param name="item">Value for which the collection will be checked</param>
/// <param name="item">Value for which the collection will be checked</param> /// <returns>True if the collection contains the specified value</returns>
/// <returns>True if the collection contains the specified value</returns> public bool Contains(TValue item) {
public bool Contains(TValue item) { foreach(ICollection<TValue> values in this.dictionary.Values) {
foreach(ICollection<TValue> values in this.dictionary.Values) { if(values.Contains(item)) {
if(values.Contains(item)) { return true;
return true; }
} }
}
return false;
return false; }
}
/// <summary>Copies the contents of the collection into an array</summary>
/// <summary>Copies the contents of the collection into an array</summary> /// <param name="array">Array the collection contents will be copied into</param>
/// <param name="array">Array the collection contents will be copied into</param> /// <param name="arrayIndex">
/// <param name="arrayIndex"> /// Starting index in the array where writing will begin
/// Starting index in the array where writing will begin /// </param>
/// </param> public void CopyTo(TValue[] array, int arrayIndex) {
public void CopyTo(TValue[] array, int arrayIndex) { foreach(ICollection<TValue> values in this.dictionary.Values) {
foreach(ICollection<TValue> values in this.dictionary.Values) { foreach(TValue value in values) {
foreach(TValue value in values) { array[arrayIndex] = value;
array[arrayIndex] = value; ++arrayIndex;
++arrayIndex; }
} }
} }
}
/// <summary>The number of values in the collection</summary>
/// <summary>The number of values in the collection</summary> public int Count {
public int Count { get { return this.dictionary.count; }
get { return this.dictionary.count; } }
}
/// <summary>Always true since the value collection is read-only</summary>
/// <summary>Always true since the value collection is read-only</summary> public bool IsReadOnly {
public bool IsReadOnly { get { return true; }
get { return true; } }
}
/// <summary>Returns a new enumerator for the value collection</summary>
/// <summary>Returns a new enumerator for the value collection</summary> /// <returns>A new enumerator for the value collection</returns>
/// <returns>A new enumerator for the value collection</returns> public IEnumerator<TValue> GetEnumerator() {
public IEnumerator<TValue> GetEnumerator() { return new Enumerator(this.dictionary.typedDictionary.Values);
return new Enumerator(this.dictionary.typedDictionary.Values); }
}
#region IEnumerator implementation
#region IEnumerator implementation
/// <summary>Returns a non-typesafe enumerator for the collection</summary>
/// <summary>Returns a non-typesafe enumerator for the collection</summary> /// <returns>The non-typesafe collection enumerator</returns>
/// <returns>The non-typesafe collection enumerator</returns> IEnumerator IEnumerable.GetEnumerator() {
IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator();
return GetEnumerator(); }
}
#endregion // IEnumerator implementation
#endregion // IEnumerator implementation
#region ICollection<> implementation
#region ICollection<> implementation
/// <summary>Throws a NotSupportedException</summary>
/// <summary>Throws a NotSupportedException</summary> /// <param name="item">Not used</param>
/// <param name="item">Not used</param> void ICollection<TValue>.Add(TValue item) {
void ICollection<TValue>.Add(TValue item) { throw new NotSupportedException(
throw new NotSupportedException( "Items cannot be added to a dictionary through its values collection"
"Items cannot be added to a dictionary through its values collection" );
); }
}
/// <summary>Throws a NotSupportedException</summary>
/// <summary>Throws a NotSupportedException</summary> void ICollection<TValue>.Clear() {
void ICollection<TValue>.Clear() { throw new NotSupportedException(
throw new NotSupportedException( "The values collection of a dictionary cannot be cleared"
"The values collection of a dictionary cannot be cleared" );
); }
}
/// <summary>Throws a NotSupportedException</summary>
/// <summary>Throws a NotSupportedException</summary> /// <param name="item">Not used</param>
/// <param name="item">Not used</param> /// <returns>Nothing, since the method always throws an exception</returns>
/// <returns>Nothing, since the method always throws an exception</returns> bool ICollection<TValue>.Contains(TValue item) {
bool ICollection<TValue>.Contains(TValue item) { throw new NotImplementedException();
throw new NotImplementedException(); }
}
/// <summary>Not supported</summary>
/// <summary>Not supported</summary> /// <param name="item">Item that will not be removed</param>
/// <param name="item">Item that will not be removed</param> /// <returns>Nothing because the method throws an exception</returns>
/// <returns>Nothing because the method throws an exception</returns> bool ICollection<TValue>.Remove(TValue item) {
bool ICollection<TValue>.Remove(TValue item) { throw new NotSupportedException(
throw new NotSupportedException( "Items cannot be removed from a dictionary through its values collection"
"Items cannot be removed from a dictionary through its values collection" );
); }
}
#endregion ICollection<> implementation
#endregion ICollection<> implementation
#region ICollection implementation
#region ICollection implementation
/// <summary>Copies the contents of the collection into an array</summary>
/// <summary>Copies the contents of the collection into an array</summary> /// <param name="array">Array the collection's contents are copied into</param>
/// <param name="array">Array the collection's contents are copied into</param> /// <param name="arrayIndex">
/// <param name="arrayIndex"> /// Starting index in the array where writing will begin
/// Starting index in the array where writing will begin /// </param>
/// </param> void ICollection.CopyTo(Array array, int arrayIndex) {
void ICollection.CopyTo(Array array, int arrayIndex) { foreach(ICollection<TValue> values in this.dictionary.Values) {
foreach(ICollection<TValue> values in this.dictionary.Values) { foreach(TValue value in values) {
foreach(TValue value in values) { array.SetValue(value, arrayIndex);
array.SetValue(value, arrayIndex); ++arrayIndex;
++arrayIndex; }
} }
} }
}
/// <summary>Whether the dictionary is thread-safe</summary>
/// <summary>Whether the dictionary is thread-safe</summary> bool ICollection.IsSynchronized {
bool ICollection.IsSynchronized { get { return this.dictionaryAsICollection.IsSynchronized; }
get { return this.dictionaryAsICollection.IsSynchronized; } }
}
/// <summary>
/// <summary> /// The synchronization root used by the dictionary for thread synchronization
/// The synchronization root used by the dictionary for thread synchronization /// </summary>
/// </summary> object ICollection.SyncRoot {
object ICollection.SyncRoot { get { return this.dictionaryAsICollection.SyncRoot; }
get { return this.dictionaryAsICollection.SyncRoot; } }
}
#endregion // ICollection implementation
#endregion // ICollection implementation
/// <summary>Dictionary whose values the collection represents</summary>
/// <summary>Dictionary whose values the collection represents</summary> private MultiDictionary<TKey, TValue> dictionary;
private MultiDictionary<TKey, TValue> dictionary; /// <summary>The dictionary under its ICollection interface</summary>
/// <summary>The dictionary under its ICollection interface</summary> private ICollection dictionaryAsICollection;
private ICollection dictionaryAsICollection;
}
}
}
}
} // namespace Nuclex.Support.Collections
} // namespace Nuclex.Support.Collections

View File

@ -1,415 +1,414 @@
#region CPL License #region Apache License 2.0
/* /*
Nuclex Framework Nuclex .NET Framework
Copyright (C) 2002-2017 Nuclex Development Labs Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
This library is free software; you can redistribute it and/or Licensed under the Apache License, Version 2.0 (the "License");
modify it under the terms of the IBM Common Public License as you may not use this file except in compliance with the License.
published by the IBM Corporation; either version 1.0 of the You may obtain a copy of the License at
License, or (at your option) any later version.
http://www.apache.org/licenses/LICENSE-2.0
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of Unless required by applicable law or agreed to in writing, software
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the distributed under the License is distributed on an "AS IS" BASIS,
IBM Common Public License for more details. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
You should have received a copy of the IBM Common Public limitations under the License.
License along with this library */
*/ #endregion // Apache License 2.0
#endregion
using System;
using System; using System.Collections;
using System.Collections; using System.Collections.Generic;
using System.Collections.Generic; using System.Collections.ObjectModel;
using System.Collections.ObjectModel;
namespace Nuclex.Support.Collections {
namespace Nuclex.Support.Collections {
/// <summary>Dictionary that can contain multiple values under the same key</summary>
/// <summary>Dictionary that can contain multiple values under the same key</summary> /// <typeparam name="TKey">Type of keys used within the dictionary</typeparam>
/// <typeparam name="TKey">Type of keys used within the dictionary</typeparam> /// <typeparam name="TValue">Type of values used within the dictionary</typeparam>
/// <typeparam name="TValue">Type of values used within the dictionary</typeparam> public partial class MultiDictionary<TKey, TValue> : IMultiDictionary<TKey, TValue> {
public partial class MultiDictionary<TKey, TValue> : IMultiDictionary<TKey, TValue> {
#region class Enumerator
#region class Enumerator
/// <summary>Enumerates the values stored in a multi dictionary</summary>
/// <summary>Enumerates the values stored in a multi dictionary</summary> private class Enumerator :
private class Enumerator : IDictionaryEnumerator,
IDictionaryEnumerator, IEnumerator<KeyValuePair<TKey, TValue>> {
IEnumerator<KeyValuePair<TKey, TValue>> {
/// <summary>Initializes a new multi dictionary enumerator</summary>
/// <summary>Initializes a new multi dictionary enumerator</summary> /// <param name="dictionary">Dictionary that will be enumerated</param>
/// <param name="dictionary">Dictionary that will be enumerated</param> public Enumerator(MultiDictionary<TKey, TValue> dictionary) {
public Enumerator(MultiDictionary<TKey, TValue> dictionary) { this.dictionary = dictionary;
this.dictionary = dictionary;
Reset();
Reset(); }
}
/// <summary>The current entry the enumerator is pointing at</summary>
/// <summary>The current entry the enumerator is pointing at</summary> public KeyValuePair<TKey, TValue> Current {
public KeyValuePair<TKey, TValue> Current { get {
get { if(this.currentValue == null) {
if(this.currentValue == null) { throw new InvalidOperationException("Enumerator is not on a valid position");
throw new InvalidOperationException("Enumerator is not on a valid position"); }
}
return new KeyValuePair<TKey, TValue>(
return new KeyValuePair<TKey, TValue>( this.currentCollection.Current.Key, this.currentValue.Current
this.currentCollection.Current.Key, this.currentValue.Current );
); }
} }
}
/// <summary>Immediately releases all resources owned by the instance</summary>
/// <summary>Immediately releases all resources owned by the instance</summary> public void Dispose() {
public void Dispose() { if(this.currentValue != null) {
if(this.currentValue != null) { this.currentValue.Dispose();
this.currentValue.Dispose(); this.currentValue = null;
this.currentValue = null; }
} if(this.currentCollection != null) {
if(this.currentCollection != null) { this.currentCollection.Dispose();
this.currentCollection.Dispose(); this.currentCollection = null;
this.currentCollection = null; }
} }
}
/// <summary>Advances the enumerator to the entry</summary>
/// <summary>Advances the enumerator to the entry</summary> /// <returns>
/// <returns> /// True if there was a next entry, false if the end of the set has been reached
/// True if there was a next entry, false if the end of the set has been reached /// </returns>
/// </returns> public bool MoveNext() {
public bool MoveNext() { if(this.currentCollection == null) {
if(this.currentCollection == null) { return false;
return false; }
}
for(; ; ) {
for(; ; ) {
// Try to move the enumerator in the current key's list to the next item
// Try to move the enumerator in the current key's list to the next item if(this.currentValue != null) {
if(this.currentValue != null) { if(this.currentValue.MoveNext()) {
if(this.currentValue.MoveNext()) { return true; // We found the next item
return true; // We found the next item } else {
} else { this.currentValue.Dispose();
this.currentValue.Dispose(); }
} }
}
// Enumerator for the current key's list reached the end, go to the next key
// Enumerator for the current key's list reached the end, go to the next key if(this.currentCollection.MoveNext()) {
if(this.currentCollection.MoveNext()) { this.currentValue = this.currentCollection.Current.Value.GetEnumerator();
this.currentValue = this.currentCollection.Current.Value.GetEnumerator(); } else {
} else { this.currentValue = null; // Guaranteed to be disposed already
this.currentValue = null; // Guaranteed to be disposed already this.currentCollection.Dispose();
this.currentCollection.Dispose(); this.currentCollection = null;
this.currentCollection = null; return false;
return false; }
}
}
} }
}
/// <summary>Resets the enumerator to its initial position</summary>
/// <summary>Resets the enumerator to its initial position</summary> public void Reset() {
public void Reset() { if(this.currentValue != null) {
if(this.currentValue != null) { this.currentValue.Dispose();
this.currentValue.Dispose(); this.currentValue = null;
this.currentValue = null; }
} if(this.currentCollection != null) {
if(this.currentCollection != null) { this.currentCollection.Dispose();
this.currentCollection.Dispose(); }
} this.currentCollection = this.dictionary.GetEnumerator();
this.currentCollection = this.dictionary.GetEnumerator(); }
}
#region IEnumerator implementation
#region IEnumerator implementation
/// <summary>The item the enumerator is currently pointing at</summary>
/// <summary>The item the enumerator is currently pointing at</summary> object IEnumerator.Current {
object IEnumerator.Current { get { return Current; }
get { return Current; } }
}
#endregion // IEnumerator implementation
#endregion // IEnumerator implementation
#region IDictionaryEnumerator implementation
#region IDictionaryEnumerator implementation
/// <summary>The current entry the enumerator is pointing to</summary>
/// <summary>The current entry the enumerator is pointing to</summary> DictionaryEntry IDictionaryEnumerator.Entry {
DictionaryEntry IDictionaryEnumerator.Entry { get {
get { enforceEnumeratorOnValidPosition();
enforceEnumeratorOnValidPosition();
return new DictionaryEntry(
return new DictionaryEntry( this.currentCollection.Current.Key, this.currentValue.Current
this.currentCollection.Current.Key, this.currentValue.Current );
); }
} }
}
/// <summary>The current dictionary key</summary>
/// <summary>The current dictionary key</summary> object IDictionaryEnumerator.Key {
object IDictionaryEnumerator.Key { get {
get { enforceEnumeratorOnValidPosition();
enforceEnumeratorOnValidPosition(); return this.currentCollection.Current.Key;
return this.currentCollection.Current.Key; }
} }
}
/// <summary>The current dictionary value</summary>
/// <summary>The current dictionary value</summary> object IDictionaryEnumerator.Value {
object IDictionaryEnumerator.Value { get {
get { enforceEnumeratorOnValidPosition();
enforceEnumeratorOnValidPosition(); return this.currentValue.Current;
return this.currentValue.Current; }
} }
}
#endregion // IDictionaryEnumerator implementation
#endregion // IDictionaryEnumerator implementation
/// <summary>
/// <summary> /// Throws an exception if the enumerator is not on a valid position
/// Throws an exception if the enumerator is not on a valid position /// </summary>
/// </summary> private void enforceEnumeratorOnValidPosition() {
private void enforceEnumeratorOnValidPosition() { if(this.currentValue == null) {
if(this.currentValue == null) { throw new InvalidOperationException("Enumerator is not on a valid position");
throw new InvalidOperationException("Enumerator is not on a valid position"); }
} }
}
/// <summary>Dictionary over whose entries the enumerator is enumerating</summary>
/// <summary>Dictionary over whose entries the enumerator is enumerating</summary> private IDictionary<TKey, ICollection<TValue>> dictionary;
private IDictionary<TKey, ICollection<TValue>> dictionary; /// <summary>Current key the enumerator is at</summary>
/// <summary>Current key the enumerator is at</summary> private IEnumerator<KeyValuePair<TKey, ICollection<TValue>>> currentCollection;
private IEnumerator<KeyValuePair<TKey, ICollection<TValue>>> currentCollection; /// <summary>Current value in the current key the enumerator is at</summary>
/// <summary>Current value in the current key the enumerator is at</summary> private IEnumerator<TValue> currentValue;
private IEnumerator<TValue> currentValue;
}
}
#endregion // class Enumerator
#endregion // class Enumerator
#region class ValueList
#region class ValueList
/// <summary>Stores the list of values for a dictionary key</summary>
/// <summary>Stores the list of values for a dictionary key</summary> private class ValueList : Collection<TValue> {
private class ValueList : Collection<TValue> {
/// <summary>Initializes a new value list</summary>
/// <summary>Initializes a new value list</summary> /// <param name="dictionary">Dictionary the value list belongs to</param>
/// <param name="dictionary">Dictionary the value list belongs to</param> public ValueList(MultiDictionary<TKey, TValue> dictionary) {
public ValueList(MultiDictionary<TKey, TValue> dictionary) { this.dictionary = dictionary;
this.dictionary = dictionary; }
}
/// <summary>Called when the value list is being cleared</summary>
/// <summary>Called when the value list is being cleared</summary> protected override void ClearItems() {
protected override void ClearItems() { this.dictionary.count -= Count;
this.dictionary.count -= Count; base.ClearItems();
base.ClearItems(); }
}
/// <summary>Called when an item is inserted into the value list</summary>
/// <summary>Called when an item is inserted into the value list</summary> /// <param name="index">Index at which the item is being inserted</param>
/// <param name="index">Index at which the item is being inserted</param> /// <param name="item">Item that is being inserted</param>
/// <param name="item">Item that is being inserted</param> protected override void InsertItem(int index, TValue item) {
protected override void InsertItem(int index, TValue item) { base.InsertItem(index, item);
base.InsertItem(index, item); ++this.dictionary.count;
++this.dictionary.count; }
}
/// <summary>Called when an item is removed from the value list</summary>
/// <summary>Called when an item is removed from the value list</summary> /// <param name="index">Index at which the item is being removed</param>
/// <param name="index">Index at which the item is being removed</param> protected override void RemoveItem(int index) {
protected override void RemoveItem(int index) { base.RemoveItem(index);
base.RemoveItem(index); --this.dictionary.count;
--this.dictionary.count; }
}
/// <summary>The dictionary the value list belongs to</summary>
/// <summary>The dictionary the value list belongs to</summary> private MultiDictionary<TKey, TValue> dictionary;
private MultiDictionary<TKey, TValue> dictionary;
}
}
#endregion // class ValueList
#endregion // class ValueList
/// <summary>Initializes a new multi dictionary</summary>
/// <summary>Initializes a new multi dictionary</summary> public MultiDictionary() : this(new Dictionary<TKey, ICollection<TValue>>()) { }
public MultiDictionary() : this(new Dictionary<TKey, ICollection<TValue>>()) { }
/// <summary>Initializes a new multi dictionary</summary>
/// <summary>Initializes a new multi dictionary</summary> /// <param name="dictionary">Dictionary the multi dictionary will be based on</param>
/// <param name="dictionary">Dictionary the multi dictionary will be based on</param> internal MultiDictionary(IDictionary<TKey, ICollection<TValue>> dictionary) {
internal MultiDictionary(IDictionary<TKey, ICollection<TValue>> dictionary) { this.typedDictionary = dictionary;
this.typedDictionary = dictionary; this.objectDictionary = (this.typedDictionary as IDictionary);
this.objectDictionary = (this.typedDictionary as IDictionary);
foreach(ICollection<TValue> values in dictionary.Values) {
foreach(ICollection<TValue> values in dictionary.Values) { this.count += values.Count;
this.count += values.Count; }
} }
}
/// <summary>Whether the dictionary is write-protected</summary>
/// <summary>Whether the dictionary is write-protected</summary> public bool IsReadOnly {
public bool IsReadOnly { get { return this.typedDictionary.IsReadOnly; }
get { return this.typedDictionary.IsReadOnly; } }
}
/// <summary>Determines the number of values stored under the specified key</summary>
/// <summary>Determines the number of values stored under the specified key</summary> /// <param name="key">Key whose values will be counted</param>
/// <param name="key">Key whose values will be counted</param> /// <returns>The number of values stored under the specified key</returns>
/// <returns>The number of values stored under the specified key</returns> public int CountValues(TKey key) {
public int CountValues(TKey key) { ICollection<TValue> values;
ICollection<TValue> values; if(this.typedDictionary.TryGetValue(key, out values)) {
if(this.typedDictionary.TryGetValue(key, out values)) { return values.Count;
return values.Count; } else {
} else { return 0;
return 0; }
} }
}
/// <summary>
/// <summary> /// Determines whether the specified KeyValuePair is contained in the dictionary
/// Determines whether the specified KeyValuePair is contained in the dictionary /// </summary>
/// </summary> /// <param name="item">KeyValuePair that will be checked for</param>
/// <param name="item">KeyValuePair that will be checked for</param> /// <returns>True if the provided KeyValuePair was contained in the dictionary</returns>
/// <returns>True if the provided KeyValuePair was contained in the dictionary</returns> public bool Contains(KeyValuePair<TKey, TValue> item) {
public bool Contains(KeyValuePair<TKey, TValue> item) { ICollection<TValue> values;
ICollection<TValue> values; if(this.typedDictionary.TryGetValue(item.Key, out values)) {
if(this.typedDictionary.TryGetValue(item.Key, out values)) { return values.Contains(item.Value);
return values.Contains(item.Value); } else {
} else { return false;
return false; }
} }
}
/// <summary>Determines whether the Dictionary contains the specified key</summary>
/// <summary>Determines whether the Dictionary contains the specified key</summary> /// <param name="key">Key that will be checked for</param>
/// <param name="key">Key that will be checked for</param> /// <returns>
/// <returns> /// True if an entry with the specified key was contained in the Dictionary
/// True if an entry with the specified key was contained in the Dictionary /// </returns>
/// </returns> public bool ContainsKey(TKey key) {
public bool ContainsKey(TKey key) { return this.typedDictionary.ContainsKey(key);
return this.typedDictionary.ContainsKey(key); }
}
/// <summary>Copies the contents of the Dictionary into an array</summary>
/// <summary>Copies the contents of the Dictionary into an array</summary> /// <param name="array">Array the Dictionary will be copied into</param>
/// <param name="array">Array the Dictionary will be copied into</param> /// <param name="arrayIndex">
/// <param name="arrayIndex"> /// Starting index at which to begin filling the destination array
/// Starting index at which to begin filling the destination array /// </param>
/// </param> public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex) {
public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex) { foreach(KeyValuePair<TKey, ICollection<TValue>> item in this.typedDictionary) {
foreach(KeyValuePair<TKey, ICollection<TValue>> item in this.typedDictionary) { foreach(TValue value in item.Value) {
foreach(TValue value in item.Value) { array[arrayIndex] = new KeyValuePair<TKey, TValue>(item.Key, value);
array[arrayIndex] = new KeyValuePair<TKey, TValue>(item.Key, value); ++arrayIndex;
++arrayIndex; }
} }
} }
}
/// <summary>Number of elements contained in the multi dictionary</summary>
/// <summary>Number of elements contained in the multi dictionary</summary> public int Count {
public int Count { get { return this.count; }
get { return this.count; } }
}
/// <summary>Creates a new enumerator for the dictionary</summary>
/// <summary>Creates a new enumerator for the dictionary</summary> /// <returns>The new dictionary enumerator</returns>
/// <returns>The new dictionary enumerator</returns> public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator() {
public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator() { return new Enumerator(this);
return new Enumerator(this); }
}
/// <summary>Collection of all keys contained in the dictionary</summary>
/// <summary>Collection of all keys contained in the dictionary</summary> public ICollection<TKey> Keys {
public ICollection<TKey> Keys { get { return this.typedDictionary.Keys; }
get { return this.typedDictionary.Keys; } }
}
/// <summary>Collection of all values contained in the dictionary</summary>
/// <summary>Collection of all values contained in the dictionary</summary> public ICollection<TValue> Values {
public ICollection<TValue> Values { get {
get { if(this.valueCollection == null) {
if(this.valueCollection == null) { this.valueCollection = new ValueCollection(this);
this.valueCollection = new ValueCollection(this); }
}
return this.valueCollection;
return this.valueCollection; }
} }
}
/// <summary>
/// <summary> /// Attempts to retrieve the item with the specified key from the dictionary
/// Attempts to retrieve the item with the specified key from the dictionary /// </summary>
/// </summary> /// <param name="key">Key of the item to attempt to retrieve</param>
/// <param name="key">Key of the item to attempt to retrieve</param> /// <param name="values">
/// <param name="values"> /// Output parameter that will receive the values upon successful completion
/// Output parameter that will receive the values upon successful completion /// </param>
/// </param> /// <returns>
/// <returns> /// True if the item was found and has been placed in the output parameter
/// True if the item was found and has been placed in the output parameter /// </returns>
/// </returns> public bool TryGetValue(TKey key, out ICollection<TValue> values) {
public bool TryGetValue(TKey key, out ICollection<TValue> values) { return this.typedDictionary.TryGetValue(key, out values);
return this.typedDictionary.TryGetValue(key, out values); }
}
/// <summary>Accesses an item in the dictionary by its key</summary>
/// <summary>Accesses an item in the dictionary by its key</summary> /// <param name="key">Key of the item that will be accessed</param>
/// <param name="key">Key of the item that will be accessed</param> public ICollection<TValue> this[TKey key] {
public ICollection<TValue> this[TKey key] { get { return this.typedDictionary[key]; }
get { return this.typedDictionary[key]; } set {
set { if(value == null) {
if(value == null) { RemoveKey(key);
RemoveKey(key); } else {
} else { ICollection<TValue> currentValues;
ICollection<TValue> currentValues; if(this.typedDictionary.TryGetValue(key, out currentValues)) {
if(this.typedDictionary.TryGetValue(key, out currentValues)) { currentValues.Clear();
currentValues.Clear(); } else {
} else { currentValues = new ValueList(this);
currentValues = new ValueList(this); this.typedDictionary.Add(key, currentValues);
this.typedDictionary.Add(key, currentValues); }
} foreach(TValue addedValue in value) {
foreach(TValue addedValue in value) { currentValues.Add(addedValue);
currentValues.Add(addedValue); }
} }
} }
} }
}
/// <summary>Inserts an item into the dictionary</summary>
/// <summary>Inserts an item into the dictionary</summary> /// <param name="key">Key under which to add the new item</param>
/// <param name="key">Key under which to add the new item</param> /// <param name="value">Item that will be added to the dictionary</param>
/// <param name="value">Item that will be added to the dictionary</param> public void Add(TKey key, TValue value) {
public void Add(TKey key, TValue value) { ICollection<TValue> values;
ICollection<TValue> values; if(!this.typedDictionary.TryGetValue(key, out values)) {
if(!this.typedDictionary.TryGetValue(key, out values)) { values = new ValueList(this);
values = new ValueList(this); this.typedDictionary.Add(key, values);
this.typedDictionary.Add(key, values); }
}
values.Add(value);
values.Add(value); }
}
/// <summary>
/// <summary> /// Removes the item with the specified key and value from the dictionary
/// Removes the item with the specified key and value from the dictionary /// </summary>
/// </summary> /// <param name="key">Key of the item that will be removed</param>
/// <param name="key">Key of the item that will be removed</param> /// <param name="value">Value of the item that will be removed</param>
/// <param name="value">Value of the item that will be removed</param> /// <returns>
/// <returns> /// True if the specified item was contained in the dictionary and was removed
/// True if the specified item was contained in the dictionary and was removed /// </returns>
/// </returns> /// <exception cref="NotSupportedException">If the dictionary is read-only</exception>
/// <exception cref="NotSupportedException">If the dictionary is read-only</exception> public bool Remove(TKey key, TValue value) {
public bool Remove(TKey key, TValue value) { ICollection<TValue> values;
ICollection<TValue> values; if(this.typedDictionary.TryGetValue(key, out values)) {
if(this.typedDictionary.TryGetValue(key, out values)) { values.Remove(value);
values.Remove(value); if(values.Count == 0) {
if(values.Count == 0) { this.typedDictionary.Remove(key);
this.typedDictionary.Remove(key); }
} return true;
return true; } else {
} else { return false;
return false; }
} }
}
/// <summary>Removes all items with the specified key from the dictionary</summary>
/// <summary>Removes all items with the specified key from the dictionary</summary> /// <param name="key">Key of the item that will be removed</param>
/// <param name="key">Key of the item that will be removed</param> /// <returns>The number of items that have been removed from the dictionary</returns>
/// <returns>The number of items that have been removed from the dictionary</returns> /// <exception cref="NotSupportedException">If the dictionary is read-only</exception>
/// <exception cref="NotSupportedException">If the dictionary is read-only</exception> public int RemoveKey(TKey key) {
public int RemoveKey(TKey key) { ICollection<TValue> values;
ICollection<TValue> values; if(this.typedDictionary.TryGetValue(key, out values)) {
if(this.typedDictionary.TryGetValue(key, out values)) { this.count -= values.Count;
this.count -= values.Count; this.typedDictionary.Remove(key);
this.typedDictionary.Remove(key); return values.Count;
return values.Count; } else {
} else { return 0;
return 0; }
} }
}
/// <summary>Removes all items from the Dictionary</summary>
/// <summary>Removes all items from the Dictionary</summary> public void Clear() {
public void Clear() { this.typedDictionary.Clear();
this.typedDictionary.Clear(); this.count = 0;
this.count = 0; }
}
/// <summary>The wrapped Dictionary under its type-safe interface</summary>
/// <summary>The wrapped Dictionary under its type-safe interface</summary> private IDictionary<TKey, ICollection<TValue>> typedDictionary;
private IDictionary<TKey, ICollection<TValue>> typedDictionary; /// <summary>The wrapped Dictionary under its object interface</summary>
/// <summary>The wrapped Dictionary under its object interface</summary> private IDictionary objectDictionary;
private IDictionary objectDictionary; /// <summary>The number of items currently in the multi dictionary</summary>
/// <summary>The number of items currently in the multi dictionary</summary> private int count;
private int count; /// <summary>Provides the values stores in the dictionary in sequence</summary>
/// <summary>Provides the values stores in the dictionary in sequence</summary> private ValueCollection valueCollection;
private ValueCollection valueCollection;
}
}
} // namespace Nuclex.Support.Collections
} // namespace Nuclex.Support.Collections

View File

@ -1,145 +1,144 @@
#region CPL License #region Apache License 2.0
/* /*
Nuclex Framework Nuclex .NET Framework
Copyright (C) 2002-2017 Nuclex Development Labs Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
This library is free software; you can redistribute it and/or Licensed under the Apache License, Version 2.0 (the "License");
modify it under the terms of the IBM Common Public License as you may not use this file except in compliance with the License.
published by the IBM Corporation; either version 1.0 of the You may obtain a copy of the License at
License, or (at your option) any later version.
http://www.apache.org/licenses/LICENSE-2.0
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of Unless required by applicable law or agreed to in writing, software
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the distributed under the License is distributed on an "AS IS" BASIS,
IBM Common Public License for more details. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
You should have received a copy of the IBM Common Public limitations under the License.
License along with this library */
*/ #endregion // Apache License 2.0
#endregion
#if !NO_NMOCK
#if !NO_NMOCK
using System;
using System; using System.Collections.Generic;
using System.Collections.Generic;
#if UNITTEST
#if UNITTEST
using NUnit.Framework;
using NUnit.Framework; using NMock;
using NMock;
namespace Nuclex.Support.Collections {
namespace Nuclex.Support.Collections {
/// <summary>Unit Test for the observable collection class</summary>
/// <summary>Unit Test for the observable collection class</summary> [TestFixture]
[TestFixture] internal class ObservableCollectionTest {
internal class ObservableCollectionTest {
#region interface IObservableCollectionSubscriber
#region interface IObservableCollectionSubscriber
/// <summary>Interface used to test the observable collection</summary>
/// <summary>Interface used to test the observable collection</summary> public interface IObservableCollectionSubscriber {
public interface IObservableCollectionSubscriber {
/// <summary>Called when the collection is about to clear its contents</summary>
/// <summary>Called when the collection is about to clear its contents</summary> /// <param name="sender">Collection that is clearing its contents</param>
/// <param name="sender">Collection that is clearing its contents</param> /// <param name="arguments">Not used</param>
/// <param name="arguments">Not used</param> void Clearing(object sender, EventArgs arguments);
void Clearing(object sender, EventArgs arguments);
/// <summary>Called when the collection has been cleared of its contents</summary>
/// <summary>Called when the collection has been cleared of its contents</summary> /// <param name="sender">Collection that was cleared of its contents</param>
/// <param name="sender">Collection that was cleared of its contents</param> /// <param name="arguments">Not used</param>
/// <param name="arguments">Not used</param> void Cleared(object sender, EventArgs arguments);
void Cleared(object sender, EventArgs arguments);
/// <summary>Called when an item is added to the collection</summary>
/// <summary>Called when an item is added to the collection</summary> /// <param name="sender">Collection to which an item is being added</param>
/// <param name="sender">Collection to which an item is being added</param> /// <param name="arguments">Contains the item that is being added</param>
/// <param name="arguments">Contains the item that is being added</param> void ItemAdded(object sender, ItemEventArgs<int> arguments);
void ItemAdded(object sender, ItemEventArgs<int> arguments);
/// <summary>Called when an item is removed from the collection</summary>
/// <summary>Called when an item is removed from the collection</summary> /// <param name="sender">Collection from which an item is being removed</param>
/// <param name="sender">Collection from which an item is being removed</param> /// <param name="arguments">Contains the item that is being removed</param>
/// <param name="arguments">Contains the item that is being removed</param> void ItemRemoved(object sender, ItemEventArgs<int> arguments);
void ItemRemoved(object sender, ItemEventArgs<int> arguments);
}
}
#endregion // interface IObservableCollectionSubscriber
#endregion // interface IObservableCollectionSubscriber
/// <summary>Initialization routine executed before each test is run</summary>
/// <summary>Initialization routine executed before each test is run</summary> [SetUp]
[SetUp] public void Setup() {
public void Setup() { this.mockery = new MockFactory();
this.mockery = new MockFactory();
this.mockedSubscriber = this.mockery.CreateMock<IObservableCollectionSubscriber>();
this.mockedSubscriber = this.mockery.CreateMock<IObservableCollectionSubscriber>();
this.observedCollection = new ObservableCollection<int>();
this.observedCollection = new ObservableCollection<int>(); this.observedCollection.Clearing += new EventHandler(
this.observedCollection.Clearing += new EventHandler( this.mockedSubscriber.MockObject.Clearing
this.mockedSubscriber.MockObject.Clearing );
); this.observedCollection.Cleared += new EventHandler(
this.observedCollection.Cleared += new EventHandler( this.mockedSubscriber.MockObject.Cleared
this.mockedSubscriber.MockObject.Cleared );
); this.observedCollection.ItemAdded += new EventHandler<ItemEventArgs<int>>(
this.observedCollection.ItemAdded += new EventHandler<ItemEventArgs<int>>( this.mockedSubscriber.MockObject.ItemAdded
this.mockedSubscriber.MockObject.ItemAdded );
); this.observedCollection.ItemRemoved += new EventHandler<ItemEventArgs<int>>(
this.observedCollection.ItemRemoved += new EventHandler<ItemEventArgs<int>>( this.mockedSubscriber.MockObject.ItemRemoved
this.mockedSubscriber.MockObject.ItemRemoved );
); }
}
/// <summary>Tests whether the Clearing event is fired</summary>
/// <summary>Tests whether the Clearing event is fired</summary> [Test]
[Test] public void TestClearingEvent() {
public void TestClearingEvent() { this.mockedSubscriber.Expects.One.Method(m => m.Clearing(null, null)).WithAnyArguments();
this.mockedSubscriber.Expects.One.Method(m => m.Clearing(null, null)).WithAnyArguments(); this.mockedSubscriber.Expects.One.Method(m => m.Cleared(null, null)).WithAnyArguments();
this.mockedSubscriber.Expects.One.Method(m => m.Cleared(null, null)).WithAnyArguments(); this.observedCollection.Clear();
this.observedCollection.Clear();
this.mockery.VerifyAllExpectationsHaveBeenMet();
this.mockery.VerifyAllExpectationsHaveBeenMet(); }
}
/// <summary>Tests whether the ItemAdded event is fired</summary>
/// <summary>Tests whether the ItemAdded event is fired</summary> [Test]
[Test] public void TestItemAddedEvent() {
public void TestItemAddedEvent() { this.mockedSubscriber.Expects.One.Method(m => m.ItemAdded(null, null)).WithAnyArguments();
this.mockedSubscriber.Expects.One.Method(m => m.ItemAdded(null, null)).WithAnyArguments();
this.observedCollection.Add(123);
this.observedCollection.Add(123);
this.mockery.VerifyAllExpectationsHaveBeenMet();
this.mockery.VerifyAllExpectationsHaveBeenMet(); }
}
/// <summary>Tests whether the ItemRemoved event is fired</summary>
/// <summary>Tests whether the ItemRemoved event is fired</summary> [Test]
[Test] public void TestItemRemovedEvent() {
public void TestItemRemovedEvent() { this.mockedSubscriber.Expects.One.Method(m => m.ItemAdded(null, null)).WithAnyArguments();
this.mockedSubscriber.Expects.One.Method(m => m.ItemAdded(null, null)).WithAnyArguments();
this.observedCollection.Add(123);
this.observedCollection.Add(123);
this.mockedSubscriber.Expects.One.Method(m => m.ItemRemoved(null, null)).WithAnyArguments();
this.mockedSubscriber.Expects.One.Method(m => m.ItemRemoved(null, null)).WithAnyArguments();
this.observedCollection.Remove(123);
this.observedCollection.Remove(123);
this.mockery.VerifyAllExpectationsHaveBeenMet();
this.mockery.VerifyAllExpectationsHaveBeenMet(); }
}
/// <summary>Tests whether a the list constructor is working</summary>
/// <summary>Tests whether a the list constructor is working</summary> [Test]
[Test] public void TestListConstructor() {
public void TestListConstructor() { int[] integers = new int[] { 12, 34, 56, 78 };
int[] integers = new int[] { 12, 34, 56, 78 };
var testCollection = new ObservableCollection<int>(integers);
var testCollection = new ObservableCollection<int>(integers);
CollectionAssert.AreEqual(integers, testCollection);
CollectionAssert.AreEqual(integers, testCollection); }
}
/// <summary>Mock object factory</summary>
/// <summary>Mock object factory</summary> private MockFactory mockery;
private MockFactory mockery; /// <summary>The mocked observable collection subscriber</summary>
/// <summary>The mocked observable collection subscriber</summary> private Mock<IObservableCollectionSubscriber> mockedSubscriber;
private Mock<IObservableCollectionSubscriber> mockedSubscriber; /// <summary>An observable collection to which a mock will be subscribed</summary>
/// <summary>An observable collection to which a mock will be subscribed</summary> private ObservableCollection<int> observedCollection;
private ObservableCollection<int> observedCollection;
}
}
} // namespace Nuclex.Support.Collections
} // namespace Nuclex.Support.Collections
#endif // UNITTEST
#endif // UNITTEST
#endif // !NO_NMOCK
#endif // !NO_NMOCK

View File

@ -1,231 +1,230 @@
#region CPL License #region Apache License 2.0
/* /*
Nuclex Framework Nuclex .NET Framework
Copyright (C) 2002-2017 Nuclex Development Labs Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
This library is free software; you can redistribute it and/or Licensed under the Apache License, Version 2.0 (the "License");
modify it under the terms of the IBM Common Public License as you may not use this file except in compliance with the License.
published by the IBM Corporation; either version 1.0 of the You may obtain a copy of the License at
License, or (at your option) any later version.
http://www.apache.org/licenses/LICENSE-2.0
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of Unless required by applicable law or agreed to in writing, software
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the distributed under the License is distributed on an "AS IS" BASIS,
IBM Common Public License for more details. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
You should have received a copy of the IBM Common Public limitations under the License.
License along with this library */
*/ #endregion // Apache License 2.0
#endregion
using System;
using System; using System.Collections;
using System.Collections; using System.Collections.Generic;
using System.Collections.Generic; using System.Collections.ObjectModel;
using System.Collections.ObjectModel; #if !NO_SPECIALIZED_COLLECTIONS
#if !NO_SPECIALIZED_COLLECTIONS using System.Collections.Specialized;
using System.Collections.Specialized; #endif
#endif
namespace Nuclex.Support.Collections {
namespace Nuclex.Support.Collections {
/// <summary>Collection which fires events when items are added or removed</summary>
/// <summary>Collection which fires events when items are added or removed</summary> /// <typeparam name="TItem">Type of items the collection manages</typeparam>
/// <typeparam name="TItem">Type of items the collection manages</typeparam> public class ObservableCollection<TItem> :
public class ObservableCollection<TItem> : ICollection<TItem>,
ICollection<TItem>, ICollection,
ICollection, #if !NO_SPECIALIZED_COLLECTIONS
#if !NO_SPECIALIZED_COLLECTIONS INotifyCollectionChanged,
INotifyCollectionChanged, #endif
#endif IObservableCollection<TItem> {
IObservableCollection<TItem> {
/// <summary>Raised when an item has been added to the collection</summary>
/// <summary>Raised when an item has been added to the collection</summary> public event EventHandler<ItemEventArgs<TItem>> ItemAdded;
public event EventHandler<ItemEventArgs<TItem>> ItemAdded; /// <summary>Raised when an item is removed from the collection</summary>
/// <summary>Raised when an item is removed from the collection</summary> public event EventHandler<ItemEventArgs<TItem>> ItemRemoved;
public event EventHandler<ItemEventArgs<TItem>> ItemRemoved; /// <summary>Raised when an item is replaced in the collection</summary>
/// <summary>Raised when an item is replaced in the collection</summary> public event EventHandler<ItemReplaceEventArgs<TItem>> ItemReplaced {
public event EventHandler<ItemReplaceEventArgs<TItem>> ItemReplaced { add { }
add { } remove { }
remove { } }
} /// <summary>Raised when the collection is about to be cleared</summary>
/// <summary>Raised when the collection is about to be cleared</summary> /// <remarks>
/// <remarks> /// This could be covered by calling ItemRemoved for each item currently
/// This could be covered by calling ItemRemoved for each item currently /// contained in the collection, but it is often simpler and more efficient
/// contained in the collection, but it is often simpler and more efficient /// to process the clearing of the entire collection as a special operation.
/// to process the clearing of the entire collection as a special operation. /// </remarks>
/// </remarks> public event EventHandler Clearing;
public event EventHandler Clearing; /// <summary>Raised when the collection has been cleared</summary>
/// <summary>Raised when the collection has been cleared</summary> public event EventHandler Cleared;
public event EventHandler Cleared;
#if !NO_SPECIALIZED_COLLECTIONS
#if !NO_SPECIALIZED_COLLECTIONS /// <summary>Called when the collection has changed</summary>
/// <summary>Called when the collection has changed</summary> public event NotifyCollectionChangedEventHandler CollectionChanged;
public event NotifyCollectionChangedEventHandler CollectionChanged; #endif
#endif
/// <summary>Initializes a new ObservableCollection with no items</summary>
/// <summary>Initializes a new ObservableCollection with no items</summary> public ObservableCollection() : this(new Collection<TItem>()) { }
public ObservableCollection() : this(new Collection<TItem>()) { }
/// <summary>
/// <summary> /// Initializes a new ObservableCollection as a wrapper for an existing collection
/// Initializes a new ObservableCollection as a wrapper for an existing collection /// </summary>
/// </summary> /// <param name="collection">Collection that will be wrapped</param>
/// <param name="collection">Collection that will be wrapped</param> /// <exception cref="System.ArgumentNullException">List is null</exception>
/// <exception cref="System.ArgumentNullException">List is null</exception> public ObservableCollection(ICollection<TItem> collection) {
public ObservableCollection(ICollection<TItem> collection) { this.typedCollection = collection;
this.typedCollection = collection; this.objectCollection = (collection as ICollection);
this.objectCollection = (collection as ICollection); }
}
/// <summary>Removes all elements from the Collection</summary>
/// <summary>Removes all elements from the Collection</summary> public void Clear() {
public void Clear() { OnClearing();
OnClearing(); this.typedCollection.Clear();
this.typedCollection.Clear(); OnCleared();
OnCleared(); }
}
/// <summary>Adds an item to the collection</summary>
/// <summary>Adds an item to the collection</summary> /// <param name="item">Collection an item will be added to</param>
/// <param name="item">Collection an item will be added to</param> public void Add(TItem item) {
public void Add(TItem item) { this.typedCollection.Add(item);
this.typedCollection.Add(item); OnAdded(item);
OnAdded(item); }
}
/// <summary>Determines whether the collection contains the specified item</summary>
/// <summary>Determines whether the collection contains the specified item</summary> /// <param name="item">Item the collection will be searched for</param>
/// <param name="item">Item the collection will be searched for</param> /// <returns>
/// <returns> /// True if the collection contains the specified item, false otherwise
/// True if the collection contains the specified item, false otherwise /// </returns>
/// </returns> public bool Contains(TItem item) {
public bool Contains(TItem item) { return this.typedCollection.Contains(item);
return this.typedCollection.Contains(item); }
}
/// <summary>Copies the contents of the collection into an array</summary>
/// <summary>Copies the contents of the collection into an array</summary> /// <param name="array">Array the collection's contents will be copied into</param>
/// <param name="array">Array the collection's contents will be copied into</param> /// <param name="arrayIndex">
/// <param name="arrayIndex"> /// Index in the array where the collection's first item will be placed
/// Index in the array where the collection's first item will be placed /// </param>
/// </param> public void CopyTo(TItem[] array, int arrayIndex) {
public void CopyTo(TItem[] array, int arrayIndex) { this.typedCollection.CopyTo(array, arrayIndex);
this.typedCollection.CopyTo(array, arrayIndex); }
}
/// <summary>The total number of items currently in the collection</summary>
/// <summary>The total number of items currently in the collection</summary> public int Count {
public int Count { get { return this.typedCollection.Count; }
get { return this.typedCollection.Count; } }
}
/// <summary>Whether the collection is read-only</summary>
/// <summary>Whether the collection is read-only</summary> public bool IsReadOnly {
public bool IsReadOnly { get { return this.typedCollection.IsReadOnly; }
get { return this.typedCollection.IsReadOnly; } }
}
/// <summary>Removes an item from the collection</summary>
/// <summary>Removes an item from the collection</summary> /// <param name="item">Item that will be removed from the collection</param>
/// <param name="item">Item that will be removed from the collection</param> /// <returns>True if the item was found and removed, false otherwise</returns>
/// <returns>True if the item was found and removed, false otherwise</returns> public bool Remove(TItem item) {
public bool Remove(TItem item) { bool wasRemoved = this.typedCollection.Remove(item);
bool wasRemoved = this.typedCollection.Remove(item); if(wasRemoved) {
if(wasRemoved) { OnRemoved(item);
OnRemoved(item); }
}
return wasRemoved;
return wasRemoved; }
}
/// <summary>Returns an enumerator for the items in the collection</summary>
/// <summary>Returns an enumerator for the items in the collection</summary> /// <returns>An enumeration for the items in the collection</returns>
/// <returns>An enumeration for the items in the collection</returns> public IEnumerator<TItem> GetEnumerator() {
public IEnumerator<TItem> GetEnumerator() { return this.typedCollection.GetEnumerator();
return this.typedCollection.GetEnumerator(); }
}
/// <summary>Fires the 'ItemAdded' event</summary>
/// <summary>Fires the 'ItemAdded' event</summary> /// <param name="item">Item that has been added to the collection</param>
/// <param name="item">Item that has been added to the collection</param> protected virtual void OnAdded(TItem item) {
protected virtual void OnAdded(TItem item) { if(ItemAdded != null) {
if(ItemAdded != null) { ItemAdded(this, new ItemEventArgs<TItem>(item));
ItemAdded(this, new ItemEventArgs<TItem>(item)); }
} #if !NO_SPECIALIZED_COLLECTIONS
#if !NO_SPECIALIZED_COLLECTIONS if(CollectionChanged != null) {
if(CollectionChanged != null) { CollectionChanged(
CollectionChanged( this,
this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item)
new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item) );
); }
} #endif
#endif }
}
/// <summary>Fires the 'ItemRemoved' event</summary>
/// <summary>Fires the 'ItemRemoved' event</summary> /// <param name="item">Item that has been removed from the collection</param>
/// <param name="item">Item that has been removed from the collection</param> protected virtual void OnRemoved(TItem item) {
protected virtual void OnRemoved(TItem item) { if(ItemRemoved != null) {
if(ItemRemoved != null) { ItemRemoved(this, new ItemEventArgs<TItem>(item));
ItemRemoved(this, new ItemEventArgs<TItem>(item)); }
} #if !NO_SPECIALIZED_COLLECTIONS
#if !NO_SPECIALIZED_COLLECTIONS if(CollectionChanged != null) {
if(CollectionChanged != null) { CollectionChanged(
CollectionChanged( this,
this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, item)
new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, item) );
); }
} #endif
#endif }
}
/// <summary>Fires the 'Clearing' event</summary>
/// <summary>Fires the 'Clearing' event</summary> protected virtual void OnClearing() {
protected virtual void OnClearing() { if(Clearing != null) {
if(Clearing != null) { Clearing(this, EventArgs.Empty);
Clearing(this, EventArgs.Empty); }
} }
}
/// <summary>Fires the 'Cleared' event</summary>
/// <summary>Fires the 'Cleared' event</summary> protected virtual void OnCleared() {
protected virtual void OnCleared() { if(Cleared != null) {
if(Cleared != null) { Cleared(this, EventArgs.Empty);
Cleared(this, EventArgs.Empty); }
} #if !NO_SPECIALIZED_COLLECTIONS
#if !NO_SPECIALIZED_COLLECTIONS if(CollectionChanged != null) {
if(CollectionChanged != null) { CollectionChanged(this, Constants.NotifyCollectionResetEventArgs);
CollectionChanged(this, Constants.NotifyCollectionResetEventArgs); }
} #endif
#endif }
}
#region IEnumerable implementation
#region IEnumerable implementation
/// <summary>Returns an enumerator for the items in the collection</summary>
/// <summary>Returns an enumerator for the items in the collection</summary> /// <returns>An enumeration for the items in the collection</returns>
/// <returns>An enumeration for the items in the collection</returns> IEnumerator IEnumerable.GetEnumerator() {
IEnumerator IEnumerable.GetEnumerator() { return this.objectCollection.GetEnumerator();
return this.objectCollection.GetEnumerator(); }
}
#endregion // IEnumerable implementation
#endregion // IEnumerable implementation
#region ICollection implementation
#region ICollection implementation
/// <summary>Copies the contents of the collection into an array</summary>
/// <summary>Copies the contents of the collection into an array</summary> /// <param name="array">Array the collection's contents will be copied into</param>
/// <param name="array">Array the collection's contents will be copied into</param> /// <param name="arrayIndex">
/// <param name="arrayIndex"> /// Index in the array where the collection's first item will be placed
/// Index in the array where the collection's first item will be placed /// </param>
/// </param> void ICollection.CopyTo(Array array, int arrayIndex) {
void ICollection.CopyTo(Array array, int arrayIndex) { this.objectCollection.CopyTo(array, arrayIndex);
this.objectCollection.CopyTo(array, arrayIndex); }
}
/// <summary>Whether the collection synchronizes accesses from multiple threads</summary>
/// <summary>Whether the collection synchronizes accesses from multiple threads</summary> bool ICollection.IsSynchronized {
bool ICollection.IsSynchronized { get { return this.objectCollection.IsSynchronized; }
get { return this.objectCollection.IsSynchronized; } }
}
/// <summary>
/// <summary> /// Synchronization root used to synchronize threads accessing the collection
/// Synchronization root used to synchronize threads accessing the collection /// </summary>
/// </summary> object ICollection.SyncRoot {
object ICollection.SyncRoot { get { return this.objectCollection.SyncRoot; }
get { return this.objectCollection.SyncRoot; } }
}
#endregion // IEnumerable implementation
#endregion // IEnumerable implementation
/// <summary>The wrapped collection under its type-safe interface</summary>
/// <summary>The wrapped collection under its type-safe interface</summary> private ICollection<TItem> typedCollection;
private ICollection<TItem> typedCollection; /// <summary>The wrapped collection under its object interface</summary>
/// <summary>The wrapped collection under its object interface</summary> private ICollection objectCollection;
private ICollection objectCollection;
}
}
} // namespace Nuclex.Support.Collections
} // namespace Nuclex.Support.Collections

File diff suppressed because it is too large Load Diff

View File

@ -1,489 +1,488 @@
#region CPL License #region Apache License 2.0
/* /*
Nuclex Framework Nuclex .NET Framework
Copyright (C) 2002-2017 Nuclex Development Labs Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
This library is free software; you can redistribute it and/or Licensed under the Apache License, Version 2.0 (the "License");
modify it under the terms of the IBM Common Public License as you may not use this file except in compliance with the License.
published by the IBM Corporation; either version 1.0 of the You may obtain a copy of the License at
License, or (at your option) any later version.
http://www.apache.org/licenses/LICENSE-2.0
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of Unless required by applicable law or agreed to in writing, software
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the distributed under the License is distributed on an "AS IS" BASIS,
IBM Common Public License for more details. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
You should have received a copy of the IBM Common Public limitations under the License.
License along with this library */
*/ #endregion // Apache License 2.0
#endregion
using System;
using System; using System.Collections;
using System.Collections; using System.Collections.Generic;
using System.Collections.Generic; #if !NO_SPECIALIZED_COLLECTIONS
#if !NO_SPECIALIZED_COLLECTIONS using System.Collections.Specialized;
using System.Collections.Specialized; #endif
#endif using System.Runtime.Serialization;
using System.Runtime.Serialization;
namespace Nuclex.Support.Collections {
namespace Nuclex.Support.Collections {
/// <summary>A dictionary that sneds out change notifications</summary>
/// <summary>A dictionary that sneds out change notifications</summary> /// <typeparam name="TKey">Type of the keys used in the dictionary</typeparam>
/// <typeparam name="TKey">Type of the keys used in the dictionary</typeparam> /// <typeparam name="TValue">Type of the values used in the dictionary</typeparam>
/// <typeparam name="TValue">Type of the values used in the dictionary</typeparam> #if !NO_SERIALIZATION
#if !NO_SERIALIZATION [Serializable]
[Serializable] #endif
#endif public class ObservableDictionary<TKey, TValue> :
public class ObservableDictionary<TKey, TValue> : #if !NO_SERIALIZATION
#if !NO_SERIALIZATION ISerializable,
ISerializable, IDeserializationCallback,
IDeserializationCallback, #endif
#endif IDictionary<TKey, TValue>,
IDictionary<TKey, TValue>, IDictionary,
IDictionary, #if !NO_SPECIALIZED_COLLECTIONS
#if !NO_SPECIALIZED_COLLECTIONS INotifyCollectionChanged,
INotifyCollectionChanged, #endif
#endif IObservableCollection<KeyValuePair<TKey, TValue>> {
IObservableCollection<KeyValuePair<TKey, TValue>> {
#if !NO_SERIALIZATION
#if !NO_SERIALIZATION #region class SerializedDictionary
#region class SerializedDictionary
/// <summary>
/// <summary> /// Dictionary wrapped used to reconstruct a serialized read only dictionary
/// Dictionary wrapped used to reconstruct a serialized read only dictionary /// </summary>
/// </summary> private class SerializedDictionary : Dictionary<TKey, TValue> {
private class SerializedDictionary : Dictionary<TKey, TValue> {
/// <summary>
/// <summary> /// Initializes a new instance of the System.WeakReference class, using deserialized
/// Initializes a new instance of the System.WeakReference class, using deserialized /// data from the specified serialization and stream objects.
/// data from the specified serialization and stream objects. /// </summary>
/// </summary> /// <param name="info">
/// <param name="info"> /// An object that holds all the data needed to serialize or deserialize the
/// An object that holds all the data needed to serialize or deserialize the /// current System.WeakReference object.
/// current System.WeakReference object. /// </param>
/// </param> /// <param name="context">
/// <param name="context"> /// (Reserved) Describes the source and destination of the serialized stream
/// (Reserved) Describes the source and destination of the serialized stream /// specified by info.
/// specified by info. /// </param>
/// </param> /// <exception cref="System.ArgumentNullException">
/// <exception cref="System.ArgumentNullException"> /// The info parameter is null.
/// The info parameter is null. /// </exception>
/// </exception> public SerializedDictionary(SerializationInfo info, StreamingContext context) :
public SerializedDictionary(SerializationInfo info, StreamingContext context) : base(info, context) { }
base(info, context) { }
}
}
#endregion // class SerializedDictionary
#endregion // class SerializedDictionary #endif // !NO_SERIALIZATION
#endif // !NO_SERIALIZATION
/// <summary>Raised when an item has been added to the dictionary</summary>
/// <summary>Raised when an item has been added to the dictionary</summary> public event EventHandler<ItemEventArgs<KeyValuePair<TKey, TValue>>> ItemAdded;
public event EventHandler<ItemEventArgs<KeyValuePair<TKey, TValue>>> ItemAdded; /// <summary>Raised when an item is removed from the dictionary</summary>
/// <summary>Raised when an item is removed from the dictionary</summary> public event EventHandler<ItemEventArgs<KeyValuePair<TKey, TValue>>> ItemRemoved;
public event EventHandler<ItemEventArgs<KeyValuePair<TKey, TValue>>> ItemRemoved; /// <summary>Raised when an item is replaced in the collection</summary>
/// <summary>Raised when an item is replaced in the collection</summary> public event EventHandler<ItemReplaceEventArgs<KeyValuePair<TKey, TValue>>> ItemReplaced;
public event EventHandler<ItemReplaceEventArgs<KeyValuePair<TKey, TValue>>> ItemReplaced; /// <summary>Raised when the dictionary is about to be cleared</summary>
/// <summary>Raised when the dictionary is about to be cleared</summary> public event EventHandler Clearing;
public event EventHandler Clearing; /// <summary>Raised when the dictionary has been cleared</summary>
/// <summary>Raised when the dictionary has been cleared</summary> public event EventHandler Cleared;
public event EventHandler Cleared;
#if !NO_SPECIALIZED_COLLECTIONS
#if !NO_SPECIALIZED_COLLECTIONS /// <summary>Called when the collection has changed</summary>
/// <summary>Called when the collection has changed</summary> public event NotifyCollectionChangedEventHandler CollectionChanged;
public event NotifyCollectionChangedEventHandler CollectionChanged; #endif
#endif
/// <summary>Initializes a new observable dictionary</summary>
/// <summary>Initializes a new observable dictionary</summary> public ObservableDictionary() : this(new Dictionary<TKey, TValue>()) { }
public ObservableDictionary() : this(new Dictionary<TKey, TValue>()) { }
/// <summary>Initializes a new observable Dictionary wrapper</summary>
/// <summary>Initializes a new observable Dictionary wrapper</summary> /// <param name="dictionary">Dictionary that will be wrapped</param>
/// <param name="dictionary">Dictionary that will be wrapped</param> public ObservableDictionary(IDictionary<TKey, TValue> dictionary) {
public ObservableDictionary(IDictionary<TKey, TValue> dictionary) { this.typedDictionary = dictionary;
this.typedDictionary = dictionary; this.objectDictionary = (this.typedDictionary as IDictionary);
this.objectDictionary = (this.typedDictionary as IDictionary); }
}
#if !NO_SERIALIZATION
#if !NO_SERIALIZATION
/// <summary>
/// <summary> /// Initializes a new instance of the System.WeakReference class, using deserialized
/// Initializes a new instance of the System.WeakReference class, using deserialized /// data from the specified serialization and stream objects.
/// data from the specified serialization and stream objects. /// </summary>
/// </summary> /// <param name="info">
/// <param name="info"> /// An object that holds all the data needed to serialize or deserialize the
/// An object that holds all the data needed to serialize or deserialize the /// current System.WeakReference object.
/// current System.WeakReference object. /// </param>
/// </param> /// <param name="context">
/// <param name="context"> /// (Reserved) Describes the source and destination of the serialized stream
/// (Reserved) Describes the source and destination of the serialized stream /// specified by info.
/// specified by info. /// </param>
/// </param> /// <exception cref="System.ArgumentNullException">
/// <exception cref="System.ArgumentNullException"> /// The info parameter is null.
/// The info parameter is null. /// </exception>
/// </exception> protected ObservableDictionary(SerializationInfo info, StreamingContext context) :
protected ObservableDictionary(SerializationInfo info, StreamingContext context) : this(new SerializedDictionary(info, context)) { }
this(new SerializedDictionary(info, context)) { }
#endif // !NO_SERIALIZATION
#endif // !NO_SERIALIZATION
/// <summary>Whether the directory is write-protected</summary>
/// <summary>Whether the directory is write-protected</summary> public bool IsReadOnly {
public bool IsReadOnly { get { return this.typedDictionary.IsReadOnly; }
get { return this.typedDictionary.IsReadOnly; } }
}
/// <summary>
/// <summary> /// Determines whether the specified KeyValuePair is contained in the Dictionary
/// Determines whether the specified KeyValuePair is contained in the Dictionary /// </summary>
/// </summary> /// <param name="item">KeyValuePair that will be checked for</param>
/// <param name="item">KeyValuePair that will be checked for</param> /// <returns>True if the provided KeyValuePair was contained in the Dictionary</returns>
/// <returns>True if the provided KeyValuePair was contained in the Dictionary</returns> public bool Contains(KeyValuePair<TKey, TValue> item) {
public bool Contains(KeyValuePair<TKey, TValue> item) { return this.typedDictionary.Contains(item);
return this.typedDictionary.Contains(item); }
}
/// <summary>Determines whether the Dictionary contains the specified key</summary>
/// <summary>Determines whether the Dictionary contains the specified key</summary> /// <param name="key">Key that will be checked for</param>
/// <param name="key">Key that will be checked for</param> /// <returns>
/// <returns> /// True if an entry with the specified key was contained in the Dictionary
/// True if an entry with the specified key was contained in the Dictionary /// </returns>
/// </returns> public bool ContainsKey(TKey key) {
public bool ContainsKey(TKey key) { return this.typedDictionary.ContainsKey(key);
return this.typedDictionary.ContainsKey(key); }
}
/// <summary>Copies the contents of the Dictionary into an array</summary>
/// <summary>Copies the contents of the Dictionary into an array</summary> /// <param name="array">Array the Dictionary will be copied into</param>
/// <param name="array">Array the Dictionary will be copied into</param> /// <param name="arrayIndex">
/// <param name="arrayIndex"> /// Starting index at which to begin filling the destination array
/// Starting index at which to begin filling the destination array /// </param>
/// </param> public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex) {
public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex) { this.typedDictionary.CopyTo(array, arrayIndex);
this.typedDictionary.CopyTo(array, arrayIndex); }
}
/// <summary>Number of elements contained in the Dictionary</summary>
/// <summary>Number of elements contained in the Dictionary</summary> public int Count {
public int Count { get { return this.typedDictionary.Count; }
get { return this.typedDictionary.Count; } }
}
/// <summary>Creates a new enumerator for the Dictionary</summary>
/// <summary>Creates a new enumerator for the Dictionary</summary> /// <returns>The new Dictionary enumerator</returns>
/// <returns>The new Dictionary enumerator</returns> public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator() {
public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator() { return this.typedDictionary.GetEnumerator();
return this.typedDictionary.GetEnumerator(); }
}
/// <summary>Collection of all keys contained in the Dictionary</summary>
/// <summary>Collection of all keys contained in the Dictionary</summary> public ICollection<TKey> Keys {
public ICollection<TKey> Keys { get { return this.typedDictionary.Keys; }
get { return this.typedDictionary.Keys; } }
}
/// <summary>Collection of all values contained in the Dictionary</summary>
/// <summary>Collection of all values contained in the Dictionary</summary> public ICollection<TValue> Values {
public ICollection<TValue> Values { get { return this.typedDictionary.Values; }
get { return this.typedDictionary.Values; } }
}
/// <summary>
/// <summary> /// Attempts to retrieve the item with the specified key from the Dictionary
/// Attempts to retrieve the item with the specified key from the Dictionary /// </summary>
/// </summary> /// <param name="key">Key of the item to attempt to retrieve</param>
/// <param name="key">Key of the item to attempt to retrieve</param> /// <param name="value">
/// <param name="value"> /// Output parameter that will receive the key upon successful completion
/// Output parameter that will receive the key upon successful completion /// </param>
/// </param> /// <returns>
/// <returns> /// True if the item was found and has been placed in the output parameter
/// True if the item was found and has been placed in the output parameter /// </returns>
/// </returns> public bool TryGetValue(TKey key, out TValue value) {
public bool TryGetValue(TKey key, out TValue value) { return this.typedDictionary.TryGetValue(key, out value);
return this.typedDictionary.TryGetValue(key, out value); }
}
/// <summary>Accesses an item in the Dictionary by its key</summary>
/// <summary>Accesses an item in the Dictionary by its key</summary> /// <param name="key">Key of the item that will be accessed</param>
/// <param name="key">Key of the item that will be accessed</param> public TValue this[TKey key] {
public TValue this[TKey key] { get { return this.typedDictionary[key]; }
get { return this.typedDictionary[key]; } set {
set { bool removed;
bool removed; TValue oldValue;
TValue oldValue; removed = this.typedDictionary.TryGetValue(key, out oldValue);
removed = this.typedDictionary.TryGetValue(key, out oldValue);
this.typedDictionary[key] = value;
this.typedDictionary[key] = value;
if(removed) {
if(removed) { OnReplaced(
OnReplaced( new KeyValuePair<TKey, TValue>(key, oldValue),
new KeyValuePair<TKey, TValue>(key, oldValue), new KeyValuePair<TKey, TValue>(key, value)
new KeyValuePair<TKey, TValue>(key, value) );
); } else {
} else { OnAdded(new KeyValuePair<TKey, TValue>(key, value));
OnAdded(new KeyValuePair<TKey, TValue>(key, value)); }
} }
} }
}
/// <summary>Inserts an item into the Dictionary</summary>
/// <summary>Inserts an item into the Dictionary</summary> /// <param name="key">Key under which to add the new item</param>
/// <param name="key">Key under which to add the new item</param> /// <param name="value">Item that will be added to the Dictionary</param>
/// <param name="value">Item that will be added to the Dictionary</param> public void Add(TKey key, TValue value) {
public void Add(TKey key, TValue value) { this.typedDictionary.Add(key, value);
this.typedDictionary.Add(key, value); OnAdded(new KeyValuePair<TKey, TValue>(key, value));
OnAdded(new KeyValuePair<TKey, TValue>(key, value)); }
}
/// <summary>Removes the item with the specified key from the Dictionary</summary>
/// <summary>Removes the item with the specified key from the Dictionary</summary> /// <param name="key">Key of the elementes that will be removed</param>
/// <param name="key">Key of the elementes that will be removed</param> /// <returns>True if an item with the specified key was found and removed</returns>
/// <returns>True if an item with the specified key was found and removed</returns> public bool Remove(TKey key) {
public bool Remove(TKey key) { TValue oldValue;
TValue oldValue; this.typedDictionary.TryGetValue(key, out oldValue);
this.typedDictionary.TryGetValue(key, out oldValue);
bool removed = this.typedDictionary.Remove(key);
bool removed = this.typedDictionary.Remove(key); if(removed) {
if(removed) { OnRemoved(new KeyValuePair<TKey, TValue>(key, oldValue));
OnRemoved(new KeyValuePair<TKey, TValue>(key, oldValue)); }
} return removed;
return removed; }
}
/// <summary>Removes all items from the Dictionary</summary>
/// <summary>Removes all items from the Dictionary</summary> public void Clear() {
public void Clear() { OnClearing();
OnClearing(); this.typedDictionary.Clear();
this.typedDictionary.Clear(); OnCleared();
OnCleared(); }
}
/// <summary>Fires the 'ItemAdded' event</summary>
/// <summary>Fires the 'ItemAdded' event</summary> /// <param name="item">Item that has been added to the collection</param>
/// <param name="item">Item that has been added to the collection</param> protected virtual void OnAdded(KeyValuePair<TKey, TValue> item) {
protected virtual void OnAdded(KeyValuePair<TKey, TValue> item) { if(ItemAdded != null)
if(ItemAdded != null) ItemAdded(this, new ItemEventArgs<KeyValuePair<TKey, TValue>>(item));
ItemAdded(this, new ItemEventArgs<KeyValuePair<TKey, TValue>>(item));
#if !NO_SPECIALIZED_COLLECTIONS
#if !NO_SPECIALIZED_COLLECTIONS if(CollectionChanged != null) {
if(CollectionChanged != null) { CollectionChanged(
CollectionChanged( this,
this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item)
new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item) );
); }
} #endif
#endif }
}
/// <summary>Fires the 'ItemRemoved' event</summary>
/// <summary>Fires the 'ItemRemoved' event</summary> /// <param name="item">Item that has been removed from the collection</param>
/// <param name="item">Item that has been removed from the collection</param> protected virtual void OnRemoved(KeyValuePair<TKey, TValue> item) {
protected virtual void OnRemoved(KeyValuePair<TKey, TValue> item) { if(ItemRemoved != null) {
if(ItemRemoved != null) { ItemRemoved(this, new ItemEventArgs<KeyValuePair<TKey, TValue>>(item));
ItemRemoved(this, new ItemEventArgs<KeyValuePair<TKey, TValue>>(item)); }
} #if !NO_SPECIALIZED_COLLECTIONS
#if !NO_SPECIALIZED_COLLECTIONS if(CollectionChanged != null) {
if(CollectionChanged != null) { CollectionChanged(
CollectionChanged( this,
this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, item)
new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, item) );
); }
} #endif
#endif }
}
/// <summary>Fires the 'ItemReplaced' event</summary>
/// <summary>Fires the 'ItemReplaced' event</summary> /// <param name="oldItem">Item that has been replaced in the collection</param>
/// <param name="oldItem">Item that has been replaced in the collection</param> /// <param name="newItem">Item with which the original item was replaced</param>
/// <param name="newItem">Item with which the original item was replaced</param> protected virtual void OnReplaced(
protected virtual void OnReplaced( KeyValuePair<TKey, TValue> oldItem, KeyValuePair<TKey, TValue> newItem
KeyValuePair<TKey, TValue> oldItem, KeyValuePair<TKey, TValue> newItem ) {
) { if(ItemReplaced != null) {
if(ItemReplaced != null) { ItemReplaced(
ItemReplaced( this,
this, new ItemReplaceEventArgs<KeyValuePair<TKey, TValue>>(oldItem, newItem)
new ItemReplaceEventArgs<KeyValuePair<TKey, TValue>>(oldItem, newItem) );
); }
} #if !NO_SPECIALIZED_COLLECTIONS
#if !NO_SPECIALIZED_COLLECTIONS if(CollectionChanged != null) {
if(CollectionChanged != null) { CollectionChanged(
CollectionChanged( this, new NotifyCollectionChangedEventArgs(
this, new NotifyCollectionChangedEventArgs( NotifyCollectionChangedAction.Replace, newItem, oldItem
NotifyCollectionChangedAction.Replace, newItem, oldItem )
) );
); }
} #endif
#endif }
}
/// <summary>Fires the 'Clearing' event</summary>
/// <summary>Fires the 'Clearing' event</summary> protected virtual void OnClearing() {
protected virtual void OnClearing() { if(Clearing != null) {
if(Clearing != null) { Clearing(this, EventArgs.Empty);
Clearing(this, EventArgs.Empty); }
} }
}
/// <summary>Fires the 'Cleared' event</summary>
/// <summary>Fires the 'Cleared' event</summary> protected virtual void OnCleared() {
protected virtual void OnCleared() { if(Cleared != null) {
if(Cleared != null) { Cleared(this, EventArgs.Empty);
Cleared(this, EventArgs.Empty); }
} #if !NO_SPECIALIZED_COLLECTIONS
#if !NO_SPECIALIZED_COLLECTIONS if(CollectionChanged != null) {
if(CollectionChanged != null) { CollectionChanged(this, Constants.NotifyCollectionResetEventArgs);
CollectionChanged(this, Constants.NotifyCollectionResetEventArgs); }
} #endif
#endif }
}
#region IEnumerable implementation
#region IEnumerable implementation
/// <summary>Returns a new object enumerator for the Dictionary</summary>
/// <summary>Returns a new object enumerator for the Dictionary</summary> /// <returns>The new object enumerator</returns>
/// <returns>The new object enumerator</returns> System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() {
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return (this.typedDictionary as IEnumerable).GetEnumerator();
return (this.typedDictionary as IEnumerable).GetEnumerator(); }
}
#endregion
#endregion
#region IDictionary implementation
#region IDictionary implementation
/// <summary>Adds an item into the Dictionary</summary>
/// <summary>Adds an item into the Dictionary</summary> /// <param name="key">Key under which the item will be added</param>
/// <param name="key">Key under which the item will be added</param> /// <param name="value">Item that will be added</param>
/// <param name="value">Item that will be added</param> void IDictionary.Add(object key, object value) {
void IDictionary.Add(object key, object value) { this.objectDictionary.Add(key, value);
this.objectDictionary.Add(key, value); OnAdded(new KeyValuePair<TKey, TValue>((TKey)key, (TValue)value));
OnAdded(new KeyValuePair<TKey, TValue>((TKey)key, (TValue)value)); }
}
/// <summary>Determines whether the specified key exists in the Dictionary</summary>
/// <summary>Determines whether the specified key exists in the Dictionary</summary> /// <param name="key">Key that will be checked for</param>
/// <param name="key">Key that will be checked for</param> /// <returns>True if an item with the specified key exists in the Dictionary</returns>
/// <returns>True if an item with the specified key exists in the Dictionary</returns> bool IDictionary.Contains(object key) {
bool IDictionary.Contains(object key) { return this.objectDictionary.Contains(key);
return this.objectDictionary.Contains(key); }
}
/// <summary>Whether the size of the Dictionary is fixed</summary>
/// <summary>Whether the size of the Dictionary is fixed</summary> bool IDictionary.IsFixedSize {
bool IDictionary.IsFixedSize { get { return this.objectDictionary.IsFixedSize; }
get { return this.objectDictionary.IsFixedSize; } }
}
/// <summary>Returns a collection of all keys in the Dictionary</summary>
/// <summary>Returns a collection of all keys in the Dictionary</summary> ICollection IDictionary.Keys {
ICollection IDictionary.Keys { get { return this.objectDictionary.Keys; }
get { return this.objectDictionary.Keys; } }
}
/// <summary>Returns a collection of all values stored in the Dictionary</summary>
/// <summary>Returns a collection of all values stored in the Dictionary</summary> ICollection IDictionary.Values {
ICollection IDictionary.Values { get { return this.objectDictionary.Values; }
get { return this.objectDictionary.Values; } }
}
/// <summary>Removes an item from the Dictionary</summary>
/// <summary>Removes an item from the Dictionary</summary> /// <param name="key">Key of the item that will be removed</param>
/// <param name="key">Key of the item that will be removed</param> void IDictionary.Remove(object key) {
void IDictionary.Remove(object key) { TValue value;
TValue value; bool removed = this.typedDictionary.TryGetValue((TKey)key, out value);
bool removed = this.typedDictionary.TryGetValue((TKey)key, out value); this.objectDictionary.Remove(key);
this.objectDictionary.Remove(key); if(removed) {
if(removed) { OnRemoved(new KeyValuePair<TKey, TValue>((TKey)key, (TValue)value));
OnRemoved(new KeyValuePair<TKey, TValue>((TKey)key, (TValue)value)); }
} }
}
/// <summary>Accesses an item in the Dictionary by its key</summary>
/// <summary>Accesses an item in the Dictionary by its key</summary> /// <param name="key">Key of the item that will be accessed</param>
/// <param name="key">Key of the item that will be accessed</param> /// <returns>The item with the specified key</returns>
/// <returns>The item with the specified key</returns> object IDictionary.this[object key] {
object IDictionary.this[object key] { get { return this.objectDictionary[key]; }
get { return this.objectDictionary[key]; } set {
set { bool removed;
bool removed; TValue oldValue;
TValue oldValue; removed = this.typedDictionary.TryGetValue((TKey)key, out oldValue);
removed = this.typedDictionary.TryGetValue((TKey)key, out oldValue);
this.objectDictionary[key] = value;
this.objectDictionary[key] = value;
if(removed) {
if(removed) { OnRemoved(new KeyValuePair<TKey, TValue>((TKey)key, oldValue));
OnRemoved(new KeyValuePair<TKey, TValue>((TKey)key, oldValue)); }
} OnAdded(new KeyValuePair<TKey, TValue>((TKey)key, (TValue)value));
OnAdded(new KeyValuePair<TKey, TValue>((TKey)key, (TValue)value)); }
} }
}
#endregion // IDictionary implementation
#endregion // IDictionary implementation
#region IDictionaryEnumerator implementation
#region IDictionaryEnumerator implementation
/// <summary>Returns a new entry enumerator for the dictionary</summary>
/// <summary>Returns a new entry enumerator for the dictionary</summary> /// <returns>The new entry enumerator</returns>
/// <returns>The new entry enumerator</returns> IDictionaryEnumerator IDictionary.GetEnumerator() {
IDictionaryEnumerator IDictionary.GetEnumerator() { return this.objectDictionary.GetEnumerator();
return this.objectDictionary.GetEnumerator(); }
}
#endregion // IDictionaryEnumerator implementation
#endregion // IDictionaryEnumerator implementation
#region ICollection<> implementation
#region ICollection<> implementation
/// <summary>Inserts an already prepared element into the Dictionary</summary>
/// <summary>Inserts an already prepared element into the Dictionary</summary> /// <param name="item">Prepared element that will be added to the Dictionary</param>
/// <param name="item">Prepared element that will be added to the Dictionary</param> void ICollection<KeyValuePair<TKey, TValue>>.Add(
void ICollection<KeyValuePair<TKey, TValue>>.Add( KeyValuePair<TKey, TValue> item
KeyValuePair<TKey, TValue> item ) {
) { this.typedDictionary.Add(item);
this.typedDictionary.Add(item); OnAdded(item);
OnAdded(item); }
}
/// <summary>Removes all items from the Dictionary</summary>
/// <summary>Removes all items from the Dictionary</summary> void ICollection<KeyValuePair<TKey, TValue>>.Clear() {
void ICollection<KeyValuePair<TKey, TValue>>.Clear() { OnClearing();
OnClearing(); this.typedDictionary.Clear();
this.typedDictionary.Clear(); OnCleared();
OnCleared(); }
}
/// <summary>Removes all items from the Dictionary</summary>
/// <summary>Removes all items from the Dictionary</summary> /// <param name="itemToRemove">Item that will be removed from the Dictionary</param>
/// <param name="itemToRemove">Item that will be removed from the Dictionary</param> bool ICollection<KeyValuePair<TKey, TValue>>.Remove(
bool ICollection<KeyValuePair<TKey, TValue>>.Remove( KeyValuePair<TKey, TValue> itemToRemove
KeyValuePair<TKey, TValue> itemToRemove ) {
) { bool removed = this.typedDictionary.Remove(itemToRemove);
bool removed = this.typedDictionary.Remove(itemToRemove); if(removed) {
if(removed) { OnRemoved(itemToRemove);
OnRemoved(itemToRemove); }
} return removed;
return removed; }
}
#endregion
#endregion
#region ICollection implementation
#region ICollection implementation
/// <summary>Copies the contents of the Dictionary into an array</summary>
/// <summary>Copies the contents of the Dictionary into an array</summary> /// <param name="array">Array the Dictionary contents will be copied into</param>
/// <param name="array">Array the Dictionary contents will be copied into</param> /// <param name="index">
/// <param name="index"> /// Starting index at which to begin filling the destination array
/// Starting index at which to begin filling the destination array /// </param>
/// </param> void ICollection.CopyTo(Array array, int index) {
void ICollection.CopyTo(Array array, int index) { this.objectDictionary.CopyTo(array, index);
this.objectDictionary.CopyTo(array, index); }
}
/// <summary>Whether the Dictionary is synchronized for multi-threaded usage</summary>
/// <summary>Whether the Dictionary is synchronized for multi-threaded usage</summary> bool ICollection.IsSynchronized {
bool ICollection.IsSynchronized { get { return this.objectDictionary.IsSynchronized; }
get { return this.objectDictionary.IsSynchronized; } }
}
/// <summary>Synchronization root on which the Dictionary locks</summary>
/// <summary>Synchronization root on which the Dictionary locks</summary> object ICollection.SyncRoot {
object ICollection.SyncRoot { get { return this.objectDictionary.SyncRoot; }
get { return this.objectDictionary.SyncRoot; } }
}
#endregion
#endregion
#if !NO_SERIALIZATION
#if !NO_SERIALIZATION #region ISerializable implementation
#region ISerializable implementation
/// <summary>Serializes the Dictionary</summary>
/// <summary>Serializes the Dictionary</summary> /// <param name="info">
/// <param name="info"> /// Provides the container into which the Dictionary will serialize itself
/// Provides the container into which the Dictionary will serialize itself /// </param>
/// </param> /// <param name="context">
/// <param name="context"> /// Contextual informations about the serialization environment
/// Contextual informations about the serialization environment /// </param>
/// </param> void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context) {
void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context) { (this.typedDictionary as ISerializable).GetObjectData(info, context);
(this.typedDictionary as ISerializable).GetObjectData(info, context); }
}
/// <summary>Called after all objects have been successfully deserialized</summary>
/// <summary>Called after all objects have been successfully deserialized</summary> /// <param name="sender">Nicht unterstützt</param>
/// <param name="sender">Nicht unterstützt</param> void IDeserializationCallback.OnDeserialization(object sender) {
void IDeserializationCallback.OnDeserialization(object sender) { (this.typedDictionary as IDeserializationCallback).OnDeserialization(sender);
(this.typedDictionary as IDeserializationCallback).OnDeserialization(sender); }
}
#endregion
#endregion #endif //!NO_SERIALIZATION
#endif //!NO_SERIALIZATION
/// <summary>The wrapped Dictionary under its type-safe interface</summary>
/// <summary>The wrapped Dictionary under its type-safe interface</summary> private IDictionary<TKey, TValue> typedDictionary;
private IDictionary<TKey, TValue> typedDictionary; /// <summary>The wrapped Dictionary under its object interface</summary>
/// <summary>The wrapped Dictionary under its object interface</summary> private IDictionary objectDictionary;
private IDictionary objectDictionary;
}
}
} // namespace Nuclex.Support.Collections
} // namespace Nuclex.Support.Collections

View File

@ -1,176 +1,175 @@
#region CPL License #region Apache License 2.0
/* /*
Nuclex Framework Nuclex .NET Framework
Copyright (C) 2002-2017 Nuclex Development Labs Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
This library is free software; you can redistribute it and/or Licensed under the Apache License, Version 2.0 (the "License");
modify it under the terms of the IBM Common Public License as you may not use this file except in compliance with the License.
published by the IBM Corporation; either version 1.0 of the You may obtain a copy of the License at
License, or (at your option) any later version.
http://www.apache.org/licenses/LICENSE-2.0
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of Unless required by applicable law or agreed to in writing, software
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the distributed under the License is distributed on an "AS IS" BASIS,
IBM Common Public License for more details. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
You should have received a copy of the IBM Common Public limitations under the License.
License along with this library */
*/ #endregion // Apache License 2.0
#endregion
#if !NO_NMOCK
#if !NO_NMOCK
using System;
using System; using System.Collections.Generic;
using System.Collections.Generic;
#if UNITTEST
#if UNITTEST
using NUnit.Framework;
using NUnit.Framework; using NMock;
using NMock;
namespace Nuclex.Support.Collections {
namespace Nuclex.Support.Collections {
/// <summary>Unit Test for the observable list class</summary>
/// <summary>Unit Test for the observable list class</summary> [TestFixture]
[TestFixture] internal class ObservableListTest {
internal class ObservableListTest {
#region interface IObservableCollectionSubscriber
#region interface IObservableCollectionSubscriber
/// <summary>Interface used to test the observable collection</summary>
/// <summary>Interface used to test the observable collection</summary> public interface IObservableCollectionSubscriber {
public interface IObservableCollectionSubscriber {
/// <summary>Called when the collection is about to clear its contents</summary>
/// <summary>Called when the collection is about to clear its contents</summary> /// <param name="sender">Collection that is clearing its contents</param>
/// <param name="sender">Collection that is clearing its contents</param> /// <param name="arguments">Not used</param>
/// <param name="arguments">Not used</param> void Clearing(object sender, EventArgs arguments);
void Clearing(object sender, EventArgs arguments);
/// <summary>Called when the collection has been cleared of its contents</summary>
/// <summary>Called when the collection has been cleared of its contents</summary> /// <param name="sender">Collection that was cleared of its contents</param>
/// <param name="sender">Collection that was cleared of its contents</param> /// <param name="arguments">Not used</param>
/// <param name="arguments">Not used</param> void Cleared(object sender, EventArgs arguments);
void Cleared(object sender, EventArgs arguments);
/// <summary>Called when an item is added to the collection</summary>
/// <summary>Called when an item is added to the collection</summary> /// <param name="sender">Collection to which an item is being added</param>
/// <param name="sender">Collection to which an item is being added</param> /// <param name="arguments">Contains the item that is being added</param>
/// <param name="arguments">Contains the item that is being added</param> void ItemAdded(object sender, ItemEventArgs<int> arguments);
void ItemAdded(object sender, ItemEventArgs<int> arguments);
/// <summary>Called when an item is removed from the collection</summary>
/// <summary>Called when an item is removed from the collection</summary> /// <param name="sender">Collection from which an item is being removed</param>
/// <param name="sender">Collection from which an item is being removed</param> /// <param name="arguments">Contains the item that is being removed</param>
/// <param name="arguments">Contains the item that is being removed</param> void ItemRemoved(object sender, ItemEventArgs<int> arguments);
void ItemRemoved(object sender, ItemEventArgs<int> arguments);
/// <summary>Called when an item is replaced in the dictionary</summary>
/// <summary>Called when an item is replaced in the dictionary</summary> /// <param name="sender">Dictionary in which an item is being replaced</param>
/// <param name="sender">Dictionary in which an item is being replaced</param> /// <param name="arguments">Contains the replaced item and its replacement</param>
/// <param name="arguments">Contains the replaced item and its replacement</param> void ItemReplaced(object sender, ItemReplaceEventArgs<int> arguments);
void ItemReplaced(object sender, ItemReplaceEventArgs<int> arguments);
}
}
#endregion // interface IObservableCollectionSubscriber
#endregion // interface IObservableCollectionSubscriber
/// <summary>Initialization routine executed before each test is run</summary>
/// <summary>Initialization routine executed before each test is run</summary> [SetUp]
[SetUp] public void Setup() {
public void Setup() { this.mockery = new MockFactory();
this.mockery = new MockFactory();
this.mockedSubscriber = this.mockery.CreateMock<IObservableCollectionSubscriber>();
this.mockedSubscriber = this.mockery.CreateMock<IObservableCollectionSubscriber>();
this.observedList = new ObservableList<int>();
this.observedList = new ObservableList<int>(); this.observedList.Clearing += new EventHandler(
this.observedList.Clearing += new EventHandler( this.mockedSubscriber.MockObject.Clearing
this.mockedSubscriber.MockObject.Clearing );
); this.observedList.Cleared += new EventHandler(
this.observedList.Cleared += new EventHandler( this.mockedSubscriber.MockObject.Cleared
this.mockedSubscriber.MockObject.Cleared );
); this.observedList.ItemAdded += new EventHandler<ItemEventArgs<int>>(
this.observedList.ItemAdded += new EventHandler<ItemEventArgs<int>>( this.mockedSubscriber.MockObject.ItemAdded
this.mockedSubscriber.MockObject.ItemAdded );
); this.observedList.ItemRemoved += new EventHandler<ItemEventArgs<int>>(
this.observedList.ItemRemoved += new EventHandler<ItemEventArgs<int>>( this.mockedSubscriber.MockObject.ItemRemoved
this.mockedSubscriber.MockObject.ItemRemoved );
); this.observedList.ItemReplaced += new EventHandler<ItemReplaceEventArgs<int>>(
this.observedList.ItemReplaced += new EventHandler<ItemReplaceEventArgs<int>>( this.mockedSubscriber.MockObject.ItemReplaced
this.mockedSubscriber.MockObject.ItemReplaced );
); }
}
/// <summary>Tests whether the Clearing event is fired</summary>
/// <summary>Tests whether the Clearing event is fired</summary> [Test]
[Test] public void TestClearingEvent() {
public void TestClearingEvent() { this.mockedSubscriber.Expects.One.Method(m => m.Clearing(null, null)).WithAnyArguments();
this.mockedSubscriber.Expects.One.Method(m => m.Clearing(null, null)).WithAnyArguments(); this.mockedSubscriber.Expects.One.Method(m => m.Cleared(null, null)).WithAnyArguments();
this.mockedSubscriber.Expects.One.Method(m => m.Cleared(null, null)).WithAnyArguments(); this.observedList.Clear();
this.observedList.Clear();
this.mockery.VerifyAllExpectationsHaveBeenMet();
this.mockery.VerifyAllExpectationsHaveBeenMet(); }
}
/// <summary>Tests whether the ItemAdded event is fired</summary>
/// <summary>Tests whether the ItemAdded event is fired</summary> [Test]
[Test] public void TestItemAddedEvent() {
public void TestItemAddedEvent() { this.mockedSubscriber.Expects.One.Method(m => m.ItemAdded(null, null)).WithAnyArguments();
this.mockedSubscriber.Expects.One.Method(m => m.ItemAdded(null, null)).WithAnyArguments();
this.observedList.Add(123);
this.observedList.Add(123);
this.mockery.VerifyAllExpectationsHaveBeenMet();
this.mockery.VerifyAllExpectationsHaveBeenMet(); }
}
/// <summary>Tests whether the ItemRemoved event is fired</summary>
/// <summary>Tests whether the ItemRemoved event is fired</summary> [Test]
[Test] public void TestItemRemovedEvent() {
public void TestItemRemovedEvent() { this.mockedSubscriber.Expects.One.Method(m => m.ItemAdded(null, null)).WithAnyArguments();
this.mockedSubscriber.Expects.One.Method(m => m.ItemAdded(null, null)).WithAnyArguments();
this.observedList.Add(123);
this.observedList.Add(123);
this.mockedSubscriber.Expects.One.Method(m => m.ItemRemoved(null, null)).WithAnyArguments();
this.mockedSubscriber.Expects.One.Method(m => m.ItemRemoved(null, null)).WithAnyArguments();
this.observedList.Remove(123);
this.observedList.Remove(123);
this.mockery.VerifyAllExpectationsHaveBeenMet();
this.mockery.VerifyAllExpectationsHaveBeenMet(); }
}
/// <summary>Tests whether items in the collection can be replaced</summary>
/// <summary>Tests whether items in the collection can be replaced</summary> [Test]
[Test] public void TestItemReplacement() {
public void TestItemReplacement() { this.mockedSubscriber.Expects.Exactly(3).Method(
this.mockedSubscriber.Expects.Exactly(3).Method( m => m.ItemAdded(null, null)
m => m.ItemAdded(null, null) ).WithAnyArguments();
).WithAnyArguments();
this.observedList.Add(1);
this.observedList.Add(1); this.observedList.Add(2);
this.observedList.Add(2); this.observedList.Add(3);
this.observedList.Add(3);
this.mockedSubscriber.Expects.One.Method(m => m.ItemReplaced(null, null)).WithAnyArguments();
this.mockedSubscriber.Expects.One.Method(m => m.ItemReplaced(null, null)).WithAnyArguments();
// Replace the middle item with something else
// Replace the middle item with something else this.observedList[1] = 4;
this.observedList[1] = 4;
Assert.AreEqual(
Assert.AreEqual( 1, this.observedList.IndexOf(4)
1, this.observedList.IndexOf(4) );
);
this.mockery.VerifyAllExpectationsHaveBeenMet();
this.mockery.VerifyAllExpectationsHaveBeenMet(); }
}
/// <summary>Tests whether a the list constructor is working</summary>
/// <summary>Tests whether a the list constructor is working</summary> [Test]
[Test] public void TestListConstructor() {
public void TestListConstructor() { int[] integers = new int[] { 12, 34, 56, 78 };
int[] integers = new int[] { 12, 34, 56, 78 };
var testList = new ObservableList<int>(integers);
var testList = new ObservableList<int>(integers);
CollectionAssert.AreEqual(integers, testList);
CollectionAssert.AreEqual(integers, testList); }
}
/// <summary>Mock object factory</summary>
/// <summary>Mock object factory</summary> private MockFactory mockery;
private MockFactory mockery; /// <summary>The mocked observable collection subscriber</summary>
/// <summary>The mocked observable collection subscriber</summary> private Mock<IObservableCollectionSubscriber> mockedSubscriber;
private Mock<IObservableCollectionSubscriber> mockedSubscriber; /// <summary>An observable collection to which a mock will be subscribed</summary>
/// <summary>An observable collection to which a mock will be subscribed</summary> private ObservableList<int> observedList;
private ObservableList<int> observedList;
}
}
} // namespace Nuclex.Support.Collections
} // namespace Nuclex.Support.Collections
#endif // UNITTEST
#endif // UNITTEST
#endif // !NO_NMOCK
#endif // !NO_NMOCK

View File

@ -1,361 +1,360 @@
#region CPL License #region Apache License 2.0
/* /*
Nuclex Framework Nuclex .NET Framework
Copyright (C) 2002-2017 Nuclex Development Labs Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
This library is free software; you can redistribute it and/or Licensed under the Apache License, Version 2.0 (the "License");
modify it under the terms of the IBM Common Public License as you may not use this file except in compliance with the License.
published by the IBM Corporation; either version 1.0 of the You may obtain a copy of the License at
License, or (at your option) any later version.
http://www.apache.org/licenses/LICENSE-2.0
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of Unless required by applicable law or agreed to in writing, software
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the distributed under the License is distributed on an "AS IS" BASIS,
IBM Common Public License for more details. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
You should have received a copy of the IBM Common Public limitations under the License.
License along with this library */
*/ #endregion // Apache License 2.0
#endregion
using System;
using System; using System.Collections;
using System.Collections; using System.Collections.Generic;
using System.Collections.Generic;
#if !NO_SPECIALIZED_COLLECTIONS
#if !NO_SPECIALIZED_COLLECTIONS using System.Collections.Specialized;
using System.Collections.Specialized; #endif
#endif
namespace Nuclex.Support.Collections {
namespace Nuclex.Support.Collections {
/// <summary>List which fires events when items are added or removed</summary>
/// <summary>List which fires events when items are added or removed</summary> /// <typeparam name="TItem">Type of items the collection manages</typeparam>
/// <typeparam name="TItem">Type of items the collection manages</typeparam> public class ObservableList<TItem> :
public class ObservableList<TItem> : IList<TItem>,
IList<TItem>, IList,
IList, ICollection,
ICollection, #if !NO_SPECIALIZED_COLLECTIONS
#if !NO_SPECIALIZED_COLLECTIONS INotifyCollectionChanged,
INotifyCollectionChanged, #endif
#endif IObservableCollection<TItem> {
IObservableCollection<TItem> {
/// <summary>Raised when an item has been added to the collection</summary>
/// <summary>Raised when an item has been added to the collection</summary> public event EventHandler<ItemEventArgs<TItem>> ItemAdded;
public event EventHandler<ItemEventArgs<TItem>> ItemAdded; /// <summary>Raised when an item is removed from the collection</summary>
/// <summary>Raised when an item is removed from the collection</summary> public event EventHandler<ItemEventArgs<TItem>> ItemRemoved;
public event EventHandler<ItemEventArgs<TItem>> ItemRemoved; /// <summary>Raised when an item is replaced in the collection</summary>
/// <summary>Raised when an item is replaced in the collection</summary> public event EventHandler<ItemReplaceEventArgs<TItem>> ItemReplaced;
public event EventHandler<ItemReplaceEventArgs<TItem>> ItemReplaced; /// <summary>Raised when the collection is about to be cleared</summary>
/// <summary>Raised when the collection is about to be cleared</summary> /// <remarks>
/// <remarks> /// This could be covered by calling ItemRemoved for each item currently
/// This could be covered by calling ItemRemoved for each item currently /// contained in the collection, but it is often simpler and more efficient
/// contained in the collection, but it is often simpler and more efficient /// to process the clearing of the entire collection as a special operation.
/// to process the clearing of the entire collection as a special operation. /// </remarks>
/// </remarks> public event EventHandler Clearing;
public event EventHandler Clearing; /// <summary>Raised when the collection has been cleared</summary>
/// <summary>Raised when the collection has been cleared</summary> public event EventHandler Cleared;
public event EventHandler Cleared;
#if !NO_SPECIALIZED_COLLECTIONS
#if !NO_SPECIALIZED_COLLECTIONS /// <summary>Called when the collection has changed</summary>
/// <summary>Called when the collection has changed</summary> public event NotifyCollectionChangedEventHandler CollectionChanged;
public event NotifyCollectionChangedEventHandler CollectionChanged; #endif
#endif
/// <summary>
/// <summary> /// Initializes a new instance of the ObservableList class that is empty.
/// Initializes a new instance of the ObservableList class that is empty. /// </summary>
/// </summary> public ObservableList() : this(new List<TItem>()) { }
public ObservableList() : this(new List<TItem>()) { }
/// <summary>
/// <summary> /// Initializes a new instance of the ObservableList class as a wrapper
/// Initializes a new instance of the ObservableList class as a wrapper /// for the specified list.
/// for the specified list. /// </summary>
/// </summary> /// <param name="list">The list that is wrapped by the new collection.</param>
/// <param name="list">The list that is wrapped by the new collection.</param> /// <exception cref="System.ArgumentNullException">List is null</exception>
/// <exception cref="System.ArgumentNullException">List is null</exception> public ObservableList(IList<TItem> list) {
public ObservableList(IList<TItem> list) { this.typedList = list;
this.typedList = list; this.objectList = list as IList; // Gah!
this.objectList = list as IList; // Gah! }
}
/// <summary>Determines the index of the specified item in the list</summary>
/// <summary>Determines the index of the specified item in the list</summary> /// <param name="item">Item whose index will be determined</param>
/// <param name="item">Item whose index will be determined</param> /// <returns>The index of the item in the list or -1 if not found</returns>
/// <returns>The index of the item in the list or -1 if not found</returns> public int IndexOf(TItem item) {
public int IndexOf(TItem item) { return this.typedList.IndexOf(item);
return this.typedList.IndexOf(item); }
}
/// <summary>Inserts an item into the list at the specified index</summary>
/// <summary>Inserts an item into the list at the specified index</summary> /// <param name="index">Index the item will be insertted at</param>
/// <param name="index">Index the item will be insertted at</param> /// <param name="item">Item that will be inserted into the list</param>
/// <param name="item">Item that will be inserted into the list</param> public void Insert(int index, TItem item) {
public void Insert(int index, TItem item) { this.typedList.Insert(index, item);
this.typedList.Insert(index, item); OnAdded(item, index);
OnAdded(item, index); }
}
/// <summary>Removes the item at the specified index from the list</summary>
/// <summary>Removes the item at the specified index from the list</summary> /// <param name="index">Index at which the item will be removed</param>
/// <param name="index">Index at which the item will be removed</param> public void RemoveAt(int index) {
public void RemoveAt(int index) { TItem item = this.typedList[index];
TItem item = this.typedList[index]; this.typedList.RemoveAt(index);
this.typedList.RemoveAt(index); OnRemoved(item, index);
OnRemoved(item, index); }
}
/// <summary>Accesses the item at the specified index in the list</summary>
/// <summary>Accesses the item at the specified index in the list</summary> /// <param name="index">Index of the item that will be accessed</param>
/// <param name="index">Index of the item that will be accessed</param> /// <returns>The item at the specified index</returns>
/// <returns>The item at the specified index</returns> public TItem this[int index] {
public TItem this[int index] { get { return this.typedList[index]; }
get { return this.typedList[index]; } set {
set { TItem oldItem = this.typedList[index];
TItem oldItem = this.typedList[index]; this.typedList[index] = value;
this.typedList[index] = value; OnReplaced(oldItem, value, index);
OnReplaced(oldItem, value, index); }
} }
}
/// <summary>Adds an item to the end of the list</summary>
/// <summary>Adds an item to the end of the list</summary> /// <param name="item">Item that will be added to the list</param>
/// <param name="item">Item that will be added to the list</param> public void Add(TItem item) {
public void Add(TItem item) { this.typedList.Add(item);
this.typedList.Add(item); OnAdded(item, this.typedList.Count - 1);
OnAdded(item, this.typedList.Count - 1); }
}
/// <summary>Removes all items from the list</summary>
/// <summary>Removes all items from the list</summary> public void Clear() {
public void Clear() { OnClearing();
OnClearing(); this.typedList.Clear();
this.typedList.Clear(); OnCleared();
OnCleared(); }
}
/// <summary>Checks whether the list contains the specified item</summary>
/// <summary>Checks whether the list contains the specified item</summary> /// <param name="item">Item the list will be checked for</param>
/// <param name="item">Item the list will be checked for</param> /// <returns>True if the list contains the specified items</returns>
/// <returns>True if the list contains the specified items</returns> public bool Contains(TItem item) {
public bool Contains(TItem item) { return this.typedList.Contains(item);
return this.typedList.Contains(item); }
}
/// <summary>Copies the contents of the list into an array</summary>
/// <summary>Copies the contents of the list into an array</summary> /// <param name="array">Array the list will be copied into</param>
/// <param name="array">Array the list will be copied into</param> /// <param name="arrayIndex">
/// <param name="arrayIndex"> /// Index in the target array where the first item will be copied to
/// Index in the target array where the first item will be copied to /// </param>
/// </param> public void CopyTo(TItem[] array, int arrayIndex) {
public void CopyTo(TItem[] array, int arrayIndex) { this.typedList.CopyTo(array, arrayIndex);
this.typedList.CopyTo(array, arrayIndex); }
}
/// <summary>Total number of items in the list</summary>
/// <summary>Total number of items in the list</summary> public int Count {
public int Count { get { return this.typedList.Count; }
get { return this.typedList.Count; } }
}
/// <summary>Whether the list is a read-only list</summary>
/// <summary>Whether the list is a read-only list</summary> public bool IsReadOnly {
public bool IsReadOnly { get { return this.typedList.IsReadOnly; }
get { return this.typedList.IsReadOnly; } }
}
/// <summary>Removes the specified item from the list</summary>
/// <summary>Removes the specified item from the list</summary> /// <param name="item">Item that will be removed from the list</param>
/// <param name="item">Item that will be removed from the list</param> /// <returns>
/// <returns> /// True if the item was found and removed from the list, false otherwise
/// True if the item was found and removed from the list, false otherwise /// </returns>
/// </returns> public bool Remove(TItem item) {
public bool Remove(TItem item) { int index = this.typedList.IndexOf(item);
int index = this.typedList.IndexOf(item); if(index == -1) {
if(index == -1) { return false;
return false; }
}
TItem removedItem = this.typedList[index];
TItem removedItem = this.typedList[index]; this.typedList.RemoveAt(index);
this.typedList.RemoveAt(index); OnRemoved(removedItem, index);
OnRemoved(removedItem, index);
return true;
return true; }
}
/// <summary>Returns an enumerator for the items in the list</summary>
/// <summary>Returns an enumerator for the items in the list</summary> /// <returns>An enumerator for the list's items</returns>
/// <returns>An enumerator for the list's items</returns> public IEnumerator<TItem> GetEnumerator() {
public IEnumerator<TItem> GetEnumerator() { return this.typedList.GetEnumerator();
return this.typedList.GetEnumerator(); }
}
#region IEnumerable implementation
#region IEnumerable implementation
/// <summary>Returns an enumerator for the items in the list</summary>
/// <summary>Returns an enumerator for the items in the list</summary> /// <returns>An enumerator for the list's items</returns>
/// <returns>An enumerator for the list's items</returns> IEnumerator IEnumerable.GetEnumerator() {
IEnumerator IEnumerable.GetEnumerator() { return this.objectList.GetEnumerator();
return this.objectList.GetEnumerator(); }
}
#endregion // IEnumerable implementation
#endregion // IEnumerable implementation
#region ICollection implementation
#region ICollection implementation
/// <summary>Copies the contents of the list into an array</summary>
/// <summary>Copies the contents of the list into an array</summary> /// <param name="array">Array the list will be copied into</param>
/// <param name="array">Array the list will be copied into</param> /// <param name="arrayIndex">
/// <param name="arrayIndex"> /// Index in the target array where the first item will be copied to
/// Index in the target array where the first item will be copied to /// </param>
/// </param> void ICollection.CopyTo(Array array, int arrayIndex) {
void ICollection.CopyTo(Array array, int arrayIndex) { this.objectList.CopyTo(array, arrayIndex);
this.objectList.CopyTo(array, arrayIndex); }
}
/// <summary>Whether this list performs thread synchronization</summary>
/// <summary>Whether this list performs thread synchronization</summary> bool ICollection.IsSynchronized {
bool ICollection.IsSynchronized { get { return this.objectList.IsSynchronized; }
get { return this.objectList.IsSynchronized; } }
}
/// <summary>Synchronization root used by the list to synchronize threads</summary>
/// <summary>Synchronization root used by the list to synchronize threads</summary> object ICollection.SyncRoot {
object ICollection.SyncRoot { get { return this.objectList.SyncRoot; }
get { return this.objectList.SyncRoot; } }
}
#endregion // ICollection implementation
#endregion // ICollection implementation
#region IList implementation
#region IList implementation
/// <summary>Adds an item to the list</summary>
/// <summary>Adds an item to the list</summary> /// <param name="value">Item that will be added to the list</param>
/// <param name="value">Item that will be added to the list</param> /// <returns>
/// <returns> /// The position at which the item has been inserted or -1 if the item was not inserted
/// The position at which the item has been inserted or -1 if the item was not inserted /// </returns>
/// </returns> int IList.Add(object value) {
int IList.Add(object value) { int index = this.objectList.Add(value);
int index = this.objectList.Add(value); TItem addedItem = this.typedList[index];
TItem addedItem = this.typedList[index]; OnAdded(addedItem, index);
OnAdded(addedItem, index); return index;
return index; }
}
/// <summary>Checks whether the list contains the specified item</summary>
/// <summary>Checks whether the list contains the specified item</summary> /// <param name="item">Item the list will be checked for</param>
/// <param name="item">Item the list will be checked for</param> /// <returns>True if the list contains the specified items</returns>
/// <returns>True if the list contains the specified items</returns> bool IList.Contains(object item) {
bool IList.Contains(object item) { return this.objectList.Contains(item);
return this.objectList.Contains(item); }
}
/// <summary>Determines the index of the specified item in the list</summary>
/// <summary>Determines the index of the specified item in the list</summary> /// <param name="item">Item whose index will be determined</param>
/// <param name="item">Item whose index will be determined</param> /// <returns>The index of the item in the list or -1 if not found</returns>
/// <returns>The index of the item in the list or -1 if not found</returns> int IList.IndexOf(object item) {
int IList.IndexOf(object item) { return this.objectList.IndexOf(item);
return this.objectList.IndexOf(item); }
}
/// <summary>Inserts an item into the list at the specified index</summary>
/// <summary>Inserts an item into the list at the specified index</summary> /// <param name="index">Index the item will be insertted at</param>
/// <param name="index">Index the item will be insertted at</param> /// <param name="item">Item that will be inserted into the list</param>
/// <param name="item">Item that will be inserted into the list</param> void IList.Insert(int index, object item) {
void IList.Insert(int index, object item) { this.objectList.Insert(index, item);
this.objectList.Insert(index, item); TItem addedItem = this.typedList[index];
TItem addedItem = this.typedList[index]; OnAdded(addedItem, index);
OnAdded(addedItem, index); }
}
/// <summary>Whether the list is of a fixed size</summary>
/// <summary>Whether the list is of a fixed size</summary> bool IList.IsFixedSize {
bool IList.IsFixedSize { get { return this.objectList.IsFixedSize; }
get { return this.objectList.IsFixedSize; } }
}
/// <summary>Removes the specified item from the list</summary>
/// <summary>Removes the specified item from the list</summary> /// <param name="item">Item that will be removed from the list</param>
/// <param name="item">Item that will be removed from the list</param> void IList.Remove(object item) {
void IList.Remove(object item) { int index = this.objectList.IndexOf(item);
int index = this.objectList.IndexOf(item); if(index == -1) {
if(index == -1) { return;
return; }
}
TItem removedItem = this.typedList[index];
TItem removedItem = this.typedList[index]; this.objectList.RemoveAt(index);
this.objectList.RemoveAt(index); OnRemoved(removedItem, index);
OnRemoved(removedItem, index); }
}
/// <summary>Accesses the item at the specified index in the list</summary>
/// <summary>Accesses the item at the specified index in the list</summary> /// <param name="index">Index of the item that will be accessed</param>
/// <param name="index">Index of the item that will be accessed</param> /// <returns>The item at the specified index</returns>
/// <returns>The item at the specified index</returns> object IList.this[int index] {
object IList.this[int index] { get { return this.objectList[index]; }
get { return this.objectList[index]; } set {
set { TItem oldItem = this.typedList[index];
TItem oldItem = this.typedList[index]; this.objectList[index] = value;
this.objectList[index] = value; TItem newItem = this.typedList[index];
TItem newItem = this.typedList[index]; OnReplaced(oldItem, newItem, index);
OnReplaced(oldItem, newItem, index); }
} }
}
#endregion // IList implementation
#endregion // IList implementation
/// <summary>Fires the 'ItemAdded' event</summary>
/// <summary>Fires the 'ItemAdded' event</summary> /// <param name="item">Item that has been added to the collection</param>
/// <param name="item">Item that has been added to the collection</param> /// <param name="index">Index of the added item</param>
/// <param name="index">Index of the added item</param> protected virtual void OnAdded(TItem item, int index) {
protected virtual void OnAdded(TItem item, int index) { if(ItemAdded != null) {
if(ItemAdded != null) { ItemAdded(this, new ItemEventArgs<TItem>(item));
ItemAdded(this, new ItemEventArgs<TItem>(item)); }
} #if !NO_SPECIALIZED_COLLECTIONS
#if !NO_SPECIALIZED_COLLECTIONS if(CollectionChanged != null) {
if(CollectionChanged != null) { CollectionChanged(
CollectionChanged( this,
this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item, index)
new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item, index) );
); }
} #endif
#endif }
}
/// <summary>Fires the 'ItemRemoved' event</summary>
/// <summary>Fires the 'ItemRemoved' event</summary> /// <param name="item">Item that has been removed from the collection</param>
/// <param name="item">Item that has been removed from the collection</param> /// <param name="index">Index the item has been removed from</param>
/// <param name="index">Index the item has been removed from</param> protected virtual void OnRemoved(TItem item, int index) {
protected virtual void OnRemoved(TItem item, int index) { if(ItemRemoved != null) {
if(ItemRemoved != null) { ItemRemoved(this, new ItemEventArgs<TItem>(item));
ItemRemoved(this, new ItemEventArgs<TItem>(item)); }
} #if !NO_SPECIALIZED_COLLECTIONS
#if !NO_SPECIALIZED_COLLECTIONS if(CollectionChanged != null) {
if(CollectionChanged != null) { CollectionChanged(
CollectionChanged( this,
this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, item, index)
new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, item, index) );
); }
} #endif
#endif }
}
/// <summary>Fires the 'ItemReplaced' event</summary>
/// <summary>Fires the 'ItemReplaced' event</summary> /// <param name="oldItem">Item that has been replaced</param>
/// <param name="oldItem">Item that has been replaced</param> /// <param name="newItem">New item the original item was replaced with</param>
/// <param name="newItem">New item the original item was replaced with</param> /// <param name="index">Index of the replaced item</param>
/// <param name="index">Index of the replaced item</param> protected virtual void OnReplaced(TItem oldItem, TItem newItem, int index) {
protected virtual void OnReplaced(TItem oldItem, TItem newItem, int index) { if(ItemReplaced != null) {
if(ItemReplaced != null) { ItemReplaced(this, new ItemReplaceEventArgs<TItem>(oldItem, newItem));
ItemReplaced(this, new ItemReplaceEventArgs<TItem>(oldItem, newItem)); }
} #if !NO_SPECIALIZED_COLLECTIONS
#if !NO_SPECIALIZED_COLLECTIONS if(CollectionChanged != null) {
if(CollectionChanged != null) { CollectionChanged(
CollectionChanged( this,
this, new NotifyCollectionChangedEventArgs(
new NotifyCollectionChangedEventArgs( NotifyCollectionChangedAction.Replace, newItem, oldItem, index
NotifyCollectionChangedAction.Replace, newItem, oldItem, index )
) );
); }
} #endif
#endif }
}
/// <summary>Fires the 'Clearing' event</summary>
/// <summary>Fires the 'Clearing' event</summary> protected virtual void OnClearing() {
protected virtual void OnClearing() { if(Clearing != null) {
if(Clearing != null) { Clearing(this, EventArgs.Empty);
Clearing(this, EventArgs.Empty); }
} }
}
/// <summary>Fires the 'Cleared' event</summary>
/// <summary>Fires the 'Cleared' event</summary> protected virtual void OnCleared() {
protected virtual void OnCleared() { if(Cleared != null) {
if(Cleared != null) { Cleared(this, EventArgs.Empty);
Cleared(this, EventArgs.Empty); }
} #if !NO_SPECIALIZED_COLLECTIONS
#if !NO_SPECIALIZED_COLLECTIONS if(CollectionChanged != null) {
if(CollectionChanged != null) { CollectionChanged(this, Constants.NotifyCollectionResetEventArgs);
CollectionChanged(this, Constants.NotifyCollectionResetEventArgs); }
} #endif
#endif }
}
/// <summary>The wrapped list under its type-safe interface</summary>
/// <summary>The wrapped list under its type-safe interface</summary> private IList<TItem> typedList;
private IList<TItem> typedList; /// <summary>The wrapped list under its object interface</summary>
/// <summary>The wrapped list under its object interface</summary> private IList objectList;
private IList objectList;
}
}
} // namespace Nuclex.Support.Collections
} // namespace Nuclex.Support.Collections

View File

@ -1,301 +1,300 @@
#region CPL License #region Apache License 2.0
/* /*
Nuclex Framework Nuclex .NET Framework
Copyright (C) 2002-2017 Nuclex Development Labs Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
This library is free software; you can redistribute it and/or Licensed under the Apache License, Version 2.0 (the "License");
modify it under the terms of the IBM Common Public License as you may not use this file except in compliance with the License.
published by the IBM Corporation; either version 1.0 of the You may obtain a copy of the License at
License, or (at your option) any later version.
http://www.apache.org/licenses/LICENSE-2.0
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of Unless required by applicable law or agreed to in writing, software
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the distributed under the License is distributed on an "AS IS" BASIS,
IBM Common Public License for more details. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
You should have received a copy of the IBM Common Public limitations under the License.
License along with this library */
*/ #endregion // Apache License 2.0
#endregion
#if !NO_SETS
#if !NO_SETS
#if UNITTEST
#if UNITTEST
using System;
using System; using System.Collections;
using System.Collections; using System.Collections.Generic;
using System.Collections.Generic; using System.IO;
using System.IO; using System.Runtime.Serialization.Formatters.Binary;
using System.Runtime.Serialization.Formatters.Binary;
using NUnit.Framework;
using NUnit.Framework; using NMock;
using NMock;
namespace Nuclex.Support.Collections {
namespace Nuclex.Support.Collections {
/// <summary>Unit Test for the observable set wrapper</summary>
/// <summary>Unit Test for the observable set wrapper</summary> [TestFixture]
[TestFixture] internal class ObservableSetTest {
internal class ObservableSetTest {
#region interface IObservableCollectionSubscriber<TItem>
#region interface IObservableCollectionSubscriber<TItem>
public interface IObservableCollectionSubscriber<TItem> {
public interface IObservableCollectionSubscriber<TItem> {
/// <summary>Called when an item has been added to the collection</summary>
/// <summary>Called when an item has been added to the collection</summary> void ItemAdded(object sender, ItemEventArgs<TItem> arguments);
void ItemAdded(object sender, ItemEventArgs<TItem> arguments); /// <summary>Called when an item is removed from the collection</summary>
/// <summary>Called when an item is removed from the collection</summary> void ItemRemoved(object sender, ItemEventArgs<TItem> arguments);
void ItemRemoved(object sender, ItemEventArgs<TItem> arguments); /// <summary>Called when an item is replaced in the collection</summary>
/// <summary>Called when an item is replaced in the collection</summary> void ItemReplaced(object sender, ItemReplaceEventArgs<TItem> arguments);
void ItemReplaced(object sender, ItemReplaceEventArgs<TItem> arguments); /// <summary>Called when the collection is about to be cleared</summary>
/// <summary>Called when the collection is about to be cleared</summary> void Clearing(object sender, EventArgs arguments);
void Clearing(object sender, EventArgs arguments); /// <summary>Called when the collection has been cleared</summary>
/// <summary>Called when the collection has been cleared</summary> void Cleared(object sender, EventArgs arguments);
void Cleared(object sender, EventArgs arguments);
}
}
#endregion // interface IObservableCollectionSubscriber<TItem>
#endregion // interface IObservableCollectionSubscriber<TItem>
/// <summary>Called before each test is run</summary>
/// <summary>Called before each test is run</summary> [SetUp]
[SetUp] public void Setup() {
public void Setup() { this.mockFactory = new MockFactory();
this.mockFactory = new MockFactory(); this.observableSet = new ObservableSet<int>();
this.observableSet = new ObservableSet<int>();
this.subscriber = this.mockFactory.CreateMock<IObservableCollectionSubscriber<int>>();
this.subscriber = this.mockFactory.CreateMock<IObservableCollectionSubscriber<int>>(); this.observableSet.ItemAdded += this.subscriber.MockObject.ItemAdded;
this.observableSet.ItemAdded += this.subscriber.MockObject.ItemAdded; this.observableSet.ItemRemoved += this.subscriber.MockObject.ItemRemoved;
this.observableSet.ItemRemoved += this.subscriber.MockObject.ItemRemoved; this.observableSet.ItemReplaced += this.subscriber.MockObject.ItemReplaced;
this.observableSet.ItemReplaced += this.subscriber.MockObject.ItemReplaced; this.observableSet.Clearing += this.subscriber.MockObject.Clearing;
this.observableSet.Clearing += this.subscriber.MockObject.Clearing; this.observableSet.Cleared += this.subscriber.MockObject.Cleared;
this.observableSet.Cleared += this.subscriber.MockObject.Cleared; }
}
/// <summary>Called after each test has run</summary>
/// <summary>Called after each test has run</summary> [TearDown]
[TearDown] public void Teardown() {
public void Teardown() { if(this.mockFactory != null) {
if(this.mockFactory != null) { this.mockFactory.VerifyAllExpectationsHaveBeenMet();
this.mockFactory.VerifyAllExpectationsHaveBeenMet();
this.subscriber = null;
this.subscriber = null; this.mockFactory.Dispose();
this.mockFactory.Dispose(); this.mockFactory = null;
this.mockFactory = null; }
} }
}
/// <summary>
/// <summary> /// Verifies that the observable set has a default constructor
/// Verifies that the observable set has a default constructor /// </summary>
/// </summary> [Test]
[Test] public void HasDefaultConstructor() {
public void HasDefaultConstructor() { Assert.IsNotNull(new ObservableSet<int>());
Assert.IsNotNull(new ObservableSet<int>()); }
}
/// <summary>
/// <summary> /// Verifies that adding items to the set triggers the 'ItemAdded' event
/// Verifies that adding items to the set triggers the 'ItemAdded' event /// </summary>
/// </summary> [Test]
[Test] public void AddingItemsTriggersEvent() {
public void AddingItemsTriggersEvent() { this.subscriber.Expects.One.Method((s) => s.ItemAdded(null, null)).WithAnyArguments();
this.subscriber.Expects.One.Method((s) => s.ItemAdded(null, null)).WithAnyArguments(); this.observableSet.Add(123);
this.observableSet.Add(123); }
}
/// <summary>
/// <summary> /// Verifies that removing items from the set triggers the 'ItemRemoved' event
/// Verifies that removing items from the set triggers the 'ItemRemoved' event /// </summary>
/// </summary> [Test]
[Test] public void RemovingItemsTriggersEvent() {
public void RemovingItemsTriggersEvent() { this.subscriber.Expects.One.Method((s) => s.ItemAdded(null, null)).WithAnyArguments();
this.subscriber.Expects.One.Method((s) => s.ItemAdded(null, null)).WithAnyArguments(); this.observableSet.Add(123);
this.observableSet.Add(123);
this.subscriber.Expects.One.Method((s) => s.ItemRemoved(null, null)).WithAnyArguments();
this.subscriber.Expects.One.Method((s) => s.ItemRemoved(null, null)).WithAnyArguments(); this.observableSet.Remove(123);
this.observableSet.Remove(123); }
}
/// <summary>
/// <summary> /// Verifies that adding items to the set triggers the 'ItemAdded' event
/// Verifies that adding items to the set triggers the 'ItemAdded' event /// </summary>
/// </summary> [Test]
[Test] public void AddingAlreadyContainedItemDoesNotTriggerEvent() {
public void AddingAlreadyContainedItemDoesNotTriggerEvent() { this.subscriber.Expects.One.Method((s) => s.ItemAdded(null, null)).WithAnyArguments();
this.subscriber.Expects.One.Method((s) => s.ItemAdded(null, null)).WithAnyArguments(); this.observableSet.Add(123);
this.observableSet.Add(123);
this.subscriber.Expects.No.Method((s) => s.ItemAdded(null, null)).WithAnyArguments();
this.subscriber.Expects.No.Method((s) => s.ItemAdded(null, null)).WithAnyArguments(); this.observableSet.Add(123);
this.observableSet.Add(123); }
}
/// <summary>
/// <summary> /// Verifies that excepting the set with itself empties the set
/// Verifies that excepting the set with itself empties the set /// </summary>
/// </summary> [Test]
[Test] public void ExceptWithSelfEmptiesSet() {
public void ExceptWithSelfEmptiesSet() { this.subscriber.Expects.Exactly(3).Method(
this.subscriber.Expects.Exactly(3).Method( (s) => s.ItemAdded(null, null)
(s) => s.ItemAdded(null, null) ).WithAnyArguments();
).WithAnyArguments();
this.observableSet.Add(1);
this.observableSet.Add(1); this.observableSet.Add(2);
this.observableSet.Add(2); this.observableSet.Add(3);
this.observableSet.Add(3);
Assert.AreEqual(3, this.observableSet.Count);
Assert.AreEqual(3, this.observableSet.Count);
this.subscriber.Expects.One.Method((s) => s.Clearing(null, null)).WithAnyArguments();
this.subscriber.Expects.One.Method((s) => s.Clearing(null, null)).WithAnyArguments(); this.subscriber.Expects.One.Method((s) => s.Cleared(null, null)).WithAnyArguments();
this.subscriber.Expects.One.Method((s) => s.Cleared(null, null)).WithAnyArguments();
this.observableSet.ExceptWith(this.observableSet);
this.observableSet.ExceptWith(this.observableSet); Assert.AreEqual(0, this.observableSet.Count);
Assert.AreEqual(0, this.observableSet.Count); }
}
/// <summary>
/// <summary> /// Verifies that a set can be excepted with a collection
/// Verifies that a set can be excepted with a collection /// </summary>
/// </summary> [Test]
[Test] public void SetCanBeExceptedWithCollection() {
public void SetCanBeExceptedWithCollection() { this.subscriber.Expects.Exactly(2).Method(
this.subscriber.Expects.Exactly(2).Method( (s) => s.ItemAdded(null, null)
(s) => s.ItemAdded(null, null) ).WithAnyArguments();
).WithAnyArguments();
this.observableSet.Add(1);
this.observableSet.Add(1); this.observableSet.Add(2);
this.observableSet.Add(2);
var collection = new List<int>() { 1 };
var collection = new List<int>() { 1 };
this.subscriber.Expects.One.Method((s) => s.ItemRemoved(null, null)).WithAnyArguments();
this.subscriber.Expects.One.Method((s) => s.ItemRemoved(null, null)).WithAnyArguments(); this.observableSet.ExceptWith(collection);
this.observableSet.ExceptWith(collection); Assert.AreEqual(1, this.observableSet.Count);
Assert.AreEqual(1, this.observableSet.Count); Assert.IsTrue(this.observableSet.Contains(2));
Assert.IsTrue(this.observableSet.Contains(2)); }
}
/// <summary>
/// <summary> /// Verifies that a set can be intersected with a collection
/// Verifies that a set can be intersected with a collection /// </summary>
/// </summary> [Test]
[Test] public void SetCanBeIntersectedWithCollection() {
public void SetCanBeIntersectedWithCollection() { this.subscriber.Expects.Exactly(2).Method(
this.subscriber.Expects.Exactly(2).Method( (s) => s.ItemAdded(null, null)
(s) => s.ItemAdded(null, null) ).WithAnyArguments();
).WithAnyArguments();
this.observableSet.Add(1);
this.observableSet.Add(1); this.observableSet.Add(2);
this.observableSet.Add(2);
var collection = new List<int>() { 1 };
var collection = new List<int>() { 1 };
this.subscriber.Expects.One.Method((s) => s.ItemRemoved(null, null)).WithAnyArguments();
this.subscriber.Expects.One.Method((s) => s.ItemRemoved(null, null)).WithAnyArguments(); this.observableSet.IntersectWith(collection);
this.observableSet.IntersectWith(collection); Assert.AreEqual(1, this.observableSet.Count);
Assert.AreEqual(1, this.observableSet.Count); Assert.IsTrue(this.observableSet.Contains(1));
Assert.IsTrue(this.observableSet.Contains(1)); }
}
/// <summary>
/// <summary> /// Verifies that it's possible to determine whether a set is a proper subset
/// Verifies that it's possible to determine whether a set is a proper subset /// or superset of another set
/// or superset of another set /// </summary>
/// </summary> [Test]
[Test] public void CanDetermineProperSubsetAndSuperset() {
public void CanDetermineProperSubsetAndSuperset() { var set1 = new ObservableSet<int>() { 1, 2, 3 };
var set1 = new ObservableSet<int>() { 1, 2, 3 }; var set2 = new HashSet<int>() { 1, 3 };
var set2 = new HashSet<int>() { 1, 3 };
Assert.IsTrue(set1.IsProperSupersetOf(set2));
Assert.IsTrue(set1.IsProperSupersetOf(set2)); Assert.IsTrue(set2.IsProperSubsetOf(set1));
Assert.IsTrue(set2.IsProperSubsetOf(set1));
set2.Add(2);
set2.Add(2);
Assert.IsFalse(set1.IsProperSupersetOf(set2));
Assert.IsFalse(set1.IsProperSupersetOf(set2)); Assert.IsFalse(set2.IsProperSubsetOf(set1));
Assert.IsFalse(set2.IsProperSubsetOf(set1)); }
}
/// <summary>
/// <summary> /// Verifies that it's possible to determine whether a set is a subset
/// Verifies that it's possible to determine whether a set is a subset /// or a superset of another set
/// or a superset of another set /// </summary>
/// </summary> [Test]
[Test] public void CanDetermineSubsetAndSuperset() {
public void CanDetermineSubsetAndSuperset() { var set1 = new ObservableSet<int>() { 1, 2, 3 };
var set1 = new ObservableSet<int>() { 1, 2, 3 }; var set2 = new HashSet<int>() { 1, 2, 3 };
var set2 = new HashSet<int>() { 1, 2, 3 };
Assert.IsTrue(set1.IsSupersetOf(set2));
Assert.IsTrue(set1.IsSupersetOf(set2)); Assert.IsTrue(set2.IsSubsetOf(set1));
Assert.IsTrue(set2.IsSubsetOf(set1));
set2.Add(4);
set2.Add(4);
Assert.IsFalse(set1.IsSupersetOf(set2));
Assert.IsFalse(set1.IsSupersetOf(set2)); Assert.IsFalse(set2.IsSubsetOf(set1));
Assert.IsFalse(set2.IsSubsetOf(set1)); }
}
/// <summary>
/// <summary> /// Verifies that a set can determine if another set overlaps with it
/// Verifies that a set can determine if another set overlaps with it /// </summary>
/// </summary> [Test]
[Test] public void CanDetermineOverlap() {
public void CanDetermineOverlap() { var set1 = new ObservableSet<int>() { 1, 3, 5 };
var set1 = new ObservableSet<int>() { 1, 3, 5 }; var set2 = new HashSet<int>() { 3 };
var set2 = new HashSet<int>() { 3 };
Assert.IsTrue(set1.Overlaps(set2));
Assert.IsTrue(set1.Overlaps(set2)); Assert.IsTrue(set2.Overlaps(set1));
Assert.IsTrue(set2.Overlaps(set1)); }
}
/// <summary>
/// <summary> /// Verifies that a set can determine if another set contains the same elements
/// Verifies that a set can determine if another set contains the same elements /// </summary>
/// </summary> [Test]
[Test] public void CanDetermineSetEquality() {
public void CanDetermineSetEquality() { var set1 = new ObservableSet<int>() { 1, 3, 5 };
var set1 = new ObservableSet<int>() { 1, 3, 5 }; var set2 = new HashSet<int>() { 3, 1, 5 };
var set2 = new HashSet<int>() { 3, 1, 5 };
Assert.IsTrue(set1.SetEquals(set2));
Assert.IsTrue(set1.SetEquals(set2)); Assert.IsTrue(set2.SetEquals(set1));
Assert.IsTrue(set2.SetEquals(set1));
set1.Add(7);
set1.Add(7);
Assert.IsFalse(set1.SetEquals(set2));
Assert.IsFalse(set1.SetEquals(set2)); Assert.IsFalse(set2.SetEquals(set1));
Assert.IsFalse(set2.SetEquals(set1)); }
}
/// <summary>
/// <summary> /// Verifies that a set can be symmetrically excepted with another set
/// Verifies that a set can be symmetrically excepted with another set /// </summary>
/// </summary> [Test]
[Test] public void CanBeSymmetricallyExcepted() {
public void CanBeSymmetricallyExcepted() { var set1 = new ObservableSet<int>() { 1, 2, 3 };
var set1 = new ObservableSet<int>() { 1, 2, 3 }; var set2 = new HashSet<int>() { 3, 4, 5 };
var set2 = new HashSet<int>() { 3, 4, 5 };
set1.SymmetricExceptWith(set2);
set1.SymmetricExceptWith(set2);
Assert.AreEqual(4, set1.Count);
Assert.AreEqual(4, set1.Count); }
}
/// <summary>
/// <summary> /// Verifies that a union of two sets can be built
/// Verifies that a union of two sets can be built /// </summary>
/// </summary> [Test]
[Test] public void CanBeUnioned() {
public void CanBeUnioned() { this.subscriber.Expects.Exactly(3).Method(
this.subscriber.Expects.Exactly(3).Method( (s) => s.ItemAdded(null, null)
(s) => s.ItemAdded(null, null) ).WithAnyArguments();
).WithAnyArguments();
this.observableSet.Add(1);
this.observableSet.Add(1); this.observableSet.Add(2);
this.observableSet.Add(2); this.observableSet.Add(3);
this.observableSet.Add(3);
var set2 = new ObservableSet<int>() { 3, 4, 5 };
var set2 = new ObservableSet<int>() { 3, 4, 5 };
this.subscriber.Expects.Exactly(2).Method(
this.subscriber.Expects.Exactly(2).Method( (s) => s.ItemAdded(null, null)
(s) => s.ItemAdded(null, null) ).WithAnyArguments();
).WithAnyArguments(); this.observableSet.UnionWith(set2);
this.observableSet.UnionWith(set2); Assert.AreEqual(5, this.observableSet.Count);
Assert.AreEqual(5, this.observableSet.Count); }
}
/// <summary>Creates mock object for the test</summary>
/// <summary>Creates mock object for the test</summary> private MockFactory mockFactory;
private MockFactory mockFactory; /// <summary>Observable set being tested</summary>
/// <summary>Observable set being tested</summary> private ObservableSet<int> observableSet;
private ObservableSet<int> observableSet; /// <summary>Subscriber for the observable set's events</summary>
/// <summary>Subscriber for the observable set's events</summary> private Mock<IObservableCollectionSubscriber<int>> subscriber;
private Mock<IObservableCollectionSubscriber<int>> subscriber;
}
}
} // namespace Nuclex.Support.Collections
} // namespace Nuclex.Support.Collections
#endif // UNITTEST
#endif // UNITTEST
#endif // !NO_SETS
#endif // !NO_SETS

View File

@ -1,340 +1,339 @@
#region CPL License #region Apache License 2.0
/* /*
Nuclex Framework Nuclex .NET Framework
Copyright (C) 2002-2017 Nuclex Development Labs Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
This library is free software; you can redistribute it and/or Licensed under the Apache License, Version 2.0 (the "License");
modify it under the terms of the IBM Common Public License as you may not use this file except in compliance with the License.
published by the IBM Corporation; either version 1.0 of the You may obtain a copy of the License at
License, or (at your option) any later version.
http://www.apache.org/licenses/LICENSE-2.0
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of Unless required by applicable law or agreed to in writing, software
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the distributed under the License is distributed on an "AS IS" BASIS,
IBM Common Public License for more details. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
You should have received a copy of the IBM Common Public limitations under the License.
License along with this library */
*/ #endregion // Apache License 2.0
#endregion
using System;
using System; using System.Collections.Generic;
using System.Collections.Generic; using System.Collections;
using System.Collections;
#if !NO_SPECIALIZED_COLLECTIONS
#if !NO_SPECIALIZED_COLLECTIONS using System.Collections.Specialized;
using System.Collections.Specialized; #endif
#endif
#if !NO_SETS
#if !NO_SETS
namespace Nuclex.Support.Collections {
namespace Nuclex.Support.Collections {
/// <summary>Set which fires events when items are removed or added to it</summary>
/// <summary>Set which fires events when items are removed or added to it</summary> /// <typeparam name="TItem">Type of items to manage in the set</typeparam>
/// <typeparam name="TItem">Type of items to manage in the set</typeparam> public class ObservableSet<TItem> :
public class ObservableSet<TItem> : ISet<TItem>,
ISet<TItem>, ICollection<TItem>,
ICollection<TItem>, #if !NO_SPECIALIZED_COLLECTIONS
#if !NO_SPECIALIZED_COLLECTIONS INotifyCollectionChanged,
INotifyCollectionChanged, #endif
#endif IObservableCollection<TItem> {
IObservableCollection<TItem> {
/// <summary>Raised when an item has been added to the collection</summary>
/// <summary>Raised when an item has been added to the collection</summary> public event EventHandler<ItemEventArgs<TItem>> ItemAdded;
public event EventHandler<ItemEventArgs<TItem>> ItemAdded; /// <summary>Raised when an item is removed from the collection</summary>
/// <summary>Raised when an item is removed from the collection</summary> public event EventHandler<ItemEventArgs<TItem>> ItemRemoved;
public event EventHandler<ItemEventArgs<TItem>> ItemRemoved; /// <summary>Raised when an item is replaced in the collection</summary>
/// <summary>Raised when an item is replaced in the collection</summary> public event EventHandler<ItemReplaceEventArgs<TItem>> ItemReplaced {
public event EventHandler<ItemReplaceEventArgs<TItem>> ItemReplaced { add { }
add { } remove { }
remove { } }
} /// <summary>Raised when the collection is about to be cleared</summary>
/// <summary>Raised when the collection is about to be cleared</summary> /// <remarks>
/// <remarks> /// This could be covered by calling ItemRemoved for each item currently
/// This could be covered by calling ItemRemoved for each item currently /// contained in the collection, but it is often simpler and more efficient
/// contained in the collection, but it is often simpler and more efficient /// to process the clearing of the entire collection as a special operation.
/// to process the clearing of the entire collection as a special operation. /// </remarks>
/// </remarks> public event EventHandler Clearing;
public event EventHandler Clearing; /// <summary>Raised when the collection has been cleared</summary>
/// <summary>Raised when the collection has been cleared</summary> public event EventHandler Cleared;
public event EventHandler Cleared;
#if !NO_SPECIALIZED_COLLECTIONS
#if !NO_SPECIALIZED_COLLECTIONS /// <summary>Called when the collection has changed</summary>
/// <summary>Called when the collection has changed</summary> public event NotifyCollectionChangedEventHandler CollectionChanged;
public event NotifyCollectionChangedEventHandler CollectionChanged; #endif
#endif
/// <summary>Initializes a new observable set based on a hashed set</summary>
/// <summary>Initializes a new observable set based on a hashed set</summary> public ObservableSet() : this(new HashSet<TItem>()) { }
public ObservableSet() : this(new HashSet<TItem>()) { }
/// <summary>
/// <summary> /// Initializes a new observable set forwarding operations to the specified set
/// Initializes a new observable set forwarding operations to the specified set /// </summary>
/// </summary> /// <param name="set">Set operations will be forwarded to</param>
/// <param name="set">Set operations will be forwarded to</param> public ObservableSet(ISet<TItem> set) {
public ObservableSet(ISet<TItem> set) { this.set = set;
this.set = set; }
}
/// <summary>Adds an item to the set</summary>
/// <summary>Adds an item to the set</summary> /// <param name="item">Item that will be added to the set</param>
/// <param name="item">Item that will be added to the set</param> /// <returns>
/// <returns> /// True if the element was added, false if it was already contained in the set
/// True if the element was added, false if it was already contained in the set /// </returns>
/// </returns> public bool Add(TItem item) {
public bool Add(TItem item) { bool wasAdded = this.set.Add(item);
bool wasAdded = this.set.Add(item); if(wasAdded) {
if(wasAdded) { OnAdded(item);
OnAdded(item); }
} return wasAdded;
return wasAdded; }
}
/// <summary>Removes all elements that are contained in the collection</summary>
/// <summary>Removes all elements that are contained in the collection</summary> /// <param name="other">Collection whose elements will be removed from this set</param>
/// <param name="other">Collection whose elements will be removed from this set</param> public void ExceptWith(IEnumerable<TItem> other) {
public void ExceptWith(IEnumerable<TItem> other) { if(other == this) {
if(other == this) { Clear();
Clear(); return;
return; }
}
foreach(TItem item in other) {
foreach(TItem item in other) { if(this.set.Remove(item)) {
if(this.set.Remove(item)) { OnRemoved(item);
OnRemoved(item); }
} }
} }
}
/// <summary>
/// <summary> /// Only keeps those elements in this set that are contained in the collection
/// Only keeps those elements in this set that are contained in the collection /// </summary>
/// </summary> /// <param name="other">Other set this set will be filtered by</param>
/// <param name="other">Other set this set will be filtered by</param> public void IntersectWith(IEnumerable<TItem> other) {
public void IntersectWith(IEnumerable<TItem> other) { var otherSet = other as ISet<TItem>;
var otherSet = other as ISet<TItem>; if(otherSet == null) {
if(otherSet == null) { otherSet = new HashSet<TItem>(other);
otherSet = new HashSet<TItem>(other); }
}
var itemsToRemove = new List<TItem>();
var itemsToRemove = new List<TItem>(); foreach(TItem item in this.set) {
foreach(TItem item in this.set) { if(!otherSet.Contains(item)) {
if(!otherSet.Contains(item)) { itemsToRemove.Add(item);
itemsToRemove.Add(item); }
} }
}
for(int index = 0; index < itemsToRemove.Count; ++index) {
for(int index = 0; index < itemsToRemove.Count; ++index) { this.set.Remove(itemsToRemove[index]);
this.set.Remove(itemsToRemove[index]); OnRemoved(itemsToRemove[index]);
OnRemoved(itemsToRemove[index]); }
} }
}
/// <summary>
/// <summary> /// Determines whether the current set is a proper (strict) subset of a collection
/// Determines whether the current set is a proper (strict) subset of a collection /// </summary>
/// </summary> /// <param name="other">Collection against which the set will be tested</param>
/// <param name="other">Collection against which the set will be tested</param> /// <returns>True if the set is a proper subset of the specified collection</returns>
/// <returns>True if the set is a proper subset of the specified collection</returns> public bool IsProperSubsetOf(IEnumerable<TItem> other) {
public bool IsProperSubsetOf(IEnumerable<TItem> other) { return this.set.IsProperSubsetOf(other);
return this.set.IsProperSubsetOf(other); }
}
/// <summary>
/// <summary> /// Determines whether the current set is a proper (strict) superset of a collection
/// Determines whether the current set is a proper (strict) superset of a collection /// </summary>
/// </summary> /// <param name="other">Collection against which the set will be tested</param>
/// <param name="other">Collection against which the set will be tested</param> /// <returns>True if the set is a proper superset of the specified collection</returns>
/// <returns>True if the set is a proper superset of the specified collection</returns> public bool IsProperSupersetOf(IEnumerable<TItem> other) {
public bool IsProperSupersetOf(IEnumerable<TItem> other) { return this.set.IsProperSupersetOf(other);
return this.set.IsProperSupersetOf(other); }
}
/// <summary>Determines whether the current set is a subset of a collection</summary>
/// <summary>Determines whether the current set is a subset of a collection</summary> /// <param name="other">Collection against which the set will be tested</param>
/// <param name="other">Collection against which the set will be tested</param> /// <returns>True if the set is a subset of the specified collection</returns>
/// <returns>True if the set is a subset of the specified collection</returns> public bool IsSubsetOf(IEnumerable<TItem> other) {
public bool IsSubsetOf(IEnumerable<TItem> other) { return this.set.IsSubsetOf(other);
return this.set.IsSubsetOf(other); }
}
/// <summary>Determines whether the current set is a superset of a collection</summary>
/// <summary>Determines whether the current set is a superset of a collection</summary> /// <param name="other">Collection against which the set will be tested</param>
/// <param name="other">Collection against which the set will be tested</param> /// <returns>True if the set is a superset of the specified collection</returns>
/// <returns>True if the set is a superset of the specified collection</returns> public bool IsSupersetOf(IEnumerable<TItem> other) {
public bool IsSupersetOf(IEnumerable<TItem> other) { return this.set.IsSupersetOf(other);
return this.set.IsSupersetOf(other); }
}
/// <summary>
/// <summary> /// Determines if the set shares at least one common element with the collection
/// Determines if the set shares at least one common element with the collection /// </summary>
/// </summary> /// <param name="other">Collection the set will be tested against</param>
/// <param name="other">Collection the set will be tested against</param> /// <returns>
/// <returns> /// True if the set shares at least one common element with the collection
/// True if the set shares at least one common element with the collection /// </returns>
/// </returns> public bool Overlaps(IEnumerable<TItem> other) {
public bool Overlaps(IEnumerable<TItem> other) { return this.set.Overlaps(other);
return this.set.Overlaps(other); }
}
/// <summary>
/// <summary> /// Determines whether the set contains the same elements as the specified collection
/// Determines whether the set contains the same elements as the specified collection /// </summary>
/// </summary> /// <param name="other">Collection the set will be tested against</param>
/// <param name="other">Collection the set will be tested against</param> /// <returns>True if the set contains the same elements as the collection</returns>
/// <returns>True if the set contains the same elements as the collection</returns> public bool SetEquals(IEnumerable<TItem> other) {
public bool SetEquals(IEnumerable<TItem> other) { return this.set.SetEquals(other);
return this.set.SetEquals(other); }
}
/// <summary>
/// <summary> /// Modifies the current set so that it contains only elements that are present either
/// Modifies the current set so that it contains only elements that are present either /// in the current set or in the specified collection, but not both
/// in the current set or in the specified collection, but not both /// </summary>
/// </summary> /// <param name="other">Collection the set will be excepted with</param>
/// <param name="other">Collection the set will be excepted with</param> public void SymmetricExceptWith(IEnumerable<TItem> other) {
public void SymmetricExceptWith(IEnumerable<TItem> other) { foreach(TItem item in other) {
foreach(TItem item in other) { if(this.set.Remove(item)) {
if(this.set.Remove(item)) { OnRemoved(item);
OnRemoved(item); } else {
} else { this.Add(item);
this.Add(item); OnAdded(item);
OnAdded(item); }
} }
} }
}
/// <summary>
/// <summary> /// Modifies the current set so that it contains all elements that are present in both
/// Modifies the current set so that it contains all elements that are present in both /// the current set and in the specified collection
/// the current set and in the specified collection /// </summary>
/// </summary> /// <param name="other">Collection an union will be built with</param>
/// <param name="other">Collection an union will be built with</param> public void UnionWith(IEnumerable<TItem> other) {
public void UnionWith(IEnumerable<TItem> other) { foreach(TItem item in other) {
foreach(TItem item in other) { if(this.set.Add(item)) {
if(this.set.Add(item)) { OnAdded(item);
OnAdded(item); }
} }
} }
}
/// <summary>Removes all items from the set</summary>
/// <summary>Removes all items from the set</summary> public void Clear() {
public void Clear() { OnClearing();
OnClearing(); this.set.Clear();
this.set.Clear(); OnCleared();
OnCleared(); }
}
/// <summary>Determines whether the set contains the specified item</summary>
/// <summary>Determines whether the set contains the specified item</summary> /// <param name="item">Item the set will be tested for</param>
/// <param name="item">Item the set will be tested for</param> /// <returns>True if the set contains the specified item</returns>
/// <returns>True if the set contains the specified item</returns> public bool Contains(TItem item) {
public bool Contains(TItem item) { return this.set.Contains(item);
return this.set.Contains(item); }
}
/// <summary>Copies the contents of the set into an array</summary>
/// <summary>Copies the contents of the set into an array</summary> /// <param name="array">Array the set's contents will be copied to</param>
/// <param name="array">Array the set's contents will be copied to</param> /// <param name="arrayIndex">
/// <param name="arrayIndex"> /// Index in the array the first copied element will be written to
/// Index in the array the first copied element will be written to /// </param>
/// </param> public void CopyTo(TItem[] array, int arrayIndex) {
public void CopyTo(TItem[] array, int arrayIndex) { this.set.CopyTo(array, arrayIndex);
this.set.CopyTo(array, arrayIndex); }
}
/// <summary>Counts the number of items contained in the set</summary>
/// <summary>Counts the number of items contained in the set</summary> public int Count {
public int Count { get { return this.set.Count; }
get { return this.set.Count; } }
}
/// <summary>Determines whether the set is readonly</summary>
/// <summary>Determines whether the set is readonly</summary> public bool IsReadOnly {
public bool IsReadOnly { get { return this.set.IsReadOnly; }
get { return this.set.IsReadOnly; } }
}
/// <summary>Removes an item from the set</summary>
/// <summary>Removes an item from the set</summary> /// <param name="item">Item that will be removed from the set</param>
/// <param name="item">Item that will be removed from the set</param> /// <returns>
/// <returns> /// True if the item was contained in the set and is now removed
/// True if the item was contained in the set and is now removed /// </returns>
/// </returns> public bool Remove(TItem item) {
public bool Remove(TItem item) { bool wasRemoved = this.set.Remove(item);
bool wasRemoved = this.set.Remove(item); if(wasRemoved) {
if(wasRemoved) { OnRemoved(item);
OnRemoved(item); }
} return wasRemoved;
return wasRemoved; }
}
/// <summary>Creates an enumerator for the set's contents</summary>
/// <summary>Creates an enumerator for the set's contents</summary> /// <returns>A new enumerator for the sets contents</returns>
/// <returns>A new enumerator for the sets contents</returns> public IEnumerator<TItem> GetEnumerator() {
public IEnumerator<TItem> GetEnumerator() { return this.set.GetEnumerator();
return this.set.GetEnumerator(); }
}
/// <summary>Fires the 'ItemAdded' event</summary>
/// <summary>Fires the 'ItemAdded' event</summary> /// <param name="item">Item that has been added to the collection</param>
/// <param name="item">Item that has been added to the collection</param> protected virtual void OnAdded(TItem item) {
protected virtual void OnAdded(TItem item) { if(ItemAdded != null) {
if(ItemAdded != null) { ItemAdded(this, new ItemEventArgs<TItem>(item));
ItemAdded(this, new ItemEventArgs<TItem>(item)); }
} #if !NO_SPECIALIZED_COLLECTIONS
#if !NO_SPECIALIZED_COLLECTIONS if(CollectionChanged != null) {
if(CollectionChanged != null) { CollectionChanged(
CollectionChanged( this,
this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item)
new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item) );
); }
} #endif
#endif }
}
/// <summary>Fires the 'ItemRemoved' event</summary>
/// <summary>Fires the 'ItemRemoved' event</summary> /// <param name="item">Item that has been removed from the collection</param>
/// <param name="item">Item that has been removed from the collection</param> protected virtual void OnRemoved(TItem item) {
protected virtual void OnRemoved(TItem item) { if(ItemRemoved != null) {
if(ItemRemoved != null) { ItemRemoved(this, new ItemEventArgs<TItem>(item));
ItemRemoved(this, new ItemEventArgs<TItem>(item)); }
} #if !NO_SPECIALIZED_COLLECTIONS
#if !NO_SPECIALIZED_COLLECTIONS if(CollectionChanged != null) {
if(CollectionChanged != null) { CollectionChanged(
CollectionChanged( this,
this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, item)
new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, item) );
); }
} #endif
#endif }
}
/// <summary>Fires the 'Clearing' event</summary>
/// <summary>Fires the 'Clearing' event</summary> protected virtual void OnClearing() {
protected virtual void OnClearing() { if(Clearing != null) {
if(Clearing != null) { Clearing(this, EventArgs.Empty);
Clearing(this, EventArgs.Empty); }
} }
}
/// <summary>Fires the 'Cleared' event</summary>
/// <summary>Fires the 'Cleared' event</summary> protected virtual void OnCleared() {
protected virtual void OnCleared() { if(Cleared != null) {
if(Cleared != null) { Cleared(this, EventArgs.Empty);
Cleared(this, EventArgs.Empty); }
} #if !NO_SPECIALIZED_COLLECTIONS
#if !NO_SPECIALIZED_COLLECTIONS if(CollectionChanged != null) {
if(CollectionChanged != null) { CollectionChanged(this, Constants.NotifyCollectionResetEventArgs);
CollectionChanged(this, Constants.NotifyCollectionResetEventArgs); }
} #endif
#endif }
}
#region ICollection<T> implementation
#region ICollection<T> implementation
/// <summary>Adds an item to the set</summary>
/// <summary>Adds an item to the set</summary> /// <param name="item">Item that will be added to the set</param>
/// <param name="item">Item that will be added to the set</param> void ICollection<TItem>.Add(TItem item) {
void ICollection<TItem>.Add(TItem item) { this.set.Add(item);
this.set.Add(item); }
}
#endregion // ICollection<T> implementation
#endregion // ICollection<T> implementation
#region IEnumerable implementation
#region IEnumerable implementation
/// <summary>Creates an enumerator for the set's contents</summary>
/// <summary>Creates an enumerator for the set's contents</summary> /// <returns>A new enumerator for the sets contents</returns>
/// <returns>A new enumerator for the sets contents</returns> IEnumerator IEnumerable.GetEnumerator() {
IEnumerator IEnumerable.GetEnumerator() { return this.set.GetEnumerator();
return this.set.GetEnumerator(); }
}
#endregion // IEnumerable implementation
#endregion // IEnumerable implementation
/// <summary>The set being wrapped</summary>
/// <summary>The set being wrapped</summary> private ISet<TItem> set;
private ISet<TItem> set;
}
}
} // namespace Nuclex.Support.Collections
} // namespace Nuclex.Support.Collections
#endif // !NO_SETS
#endif // !NO_SETS

View File

@ -1,151 +1,150 @@
#region CPL License #region Apache License 2.0
/* /*
Nuclex Framework Nuclex .NET Framework
Copyright (C) 2002-2017 Nuclex Development Labs Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
This library is free software; you can redistribute it and/or Licensed under the Apache License, Version 2.0 (the "License");
modify it under the terms of the IBM Common Public License as you may not use this file except in compliance with the License.
published by the IBM Corporation; either version 1.0 of the You may obtain a copy of the License at
License, or (at your option) any later version.
http://www.apache.org/licenses/LICENSE-2.0
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of Unless required by applicable law or agreed to in writing, software
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the distributed under the License is distributed on an "AS IS" BASIS,
IBM Common Public License for more details. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
You should have received a copy of the IBM Common Public limitations under the License.
License along with this library */
*/ #endregion // Apache License 2.0
#endregion
using System;
using System; using System.Collections.Generic;
using System.Collections.Generic;
#if UNITTEST
#if UNITTEST
using NUnit.Framework;
using NUnit.Framework;
namespace Nuclex.Support.Collections {
namespace Nuclex.Support.Collections {
/// <summary>Unit Test for the priority queue class</summary>
/// <summary>Unit Test for the priority queue class</summary> [TestFixture]
[TestFixture] internal class PairPriorityQueueTest {
internal class PairPriorityQueueTest {
/// <summary>Tests to ensure the count property is properly updated</summary>
/// <summary>Tests to ensure the count property is properly updated</summary> [Test]
[Test] public void TestCount() {
public void TestCount() { PairPriorityQueue<float, string> testQueue = new PairPriorityQueue<float, string>();
PairPriorityQueue<float, string> testQueue = new PairPriorityQueue<float, string>();
Assert.AreEqual(0, testQueue.Count);
Assert.AreEqual(0, testQueue.Count); testQueue.Enqueue(12.34f, "a");
testQueue.Enqueue(12.34f, "a"); Assert.AreEqual(1, testQueue.Count);
Assert.AreEqual(1, testQueue.Count); testQueue.Enqueue(56.78f, "b");
testQueue.Enqueue(56.78f, "b"); Assert.AreEqual(2, testQueue.Count);
Assert.AreEqual(2, testQueue.Count); testQueue.Dequeue();
testQueue.Dequeue(); Assert.AreEqual(1, testQueue.Count);
Assert.AreEqual(1, testQueue.Count); testQueue.Enqueue(9.0f, "c");
testQueue.Enqueue(9.0f, "c"); Assert.AreEqual(2, testQueue.Count);
Assert.AreEqual(2, testQueue.Count); testQueue.Clear();
testQueue.Clear(); Assert.AreEqual(0, testQueue.Count);
Assert.AreEqual(0, testQueue.Count); }
}
/// <summary>Tests to ensure that the priority collection actually sorts items</summary>
/// <summary>Tests to ensure that the priority collection actually sorts items</summary> [Test]
[Test] public void TestOrdering() {
public void TestOrdering() { PairPriorityQueue<float, string> testQueue = new PairPriorityQueue<float, string>();
PairPriorityQueue<float, string> testQueue = new PairPriorityQueue<float, string>();
testQueue.Enqueue(1.0f, "a");
testQueue.Enqueue(1.0f, "a"); testQueue.Enqueue(9.0f, "i");
testQueue.Enqueue(9.0f, "i"); testQueue.Enqueue(2.0f, "b");
testQueue.Enqueue(2.0f, "b"); testQueue.Enqueue(8.0f, "h");
testQueue.Enqueue(8.0f, "h"); testQueue.Enqueue(3.0f, "c");
testQueue.Enqueue(3.0f, "c"); testQueue.Enqueue(7.0f, "g");
testQueue.Enqueue(7.0f, "g"); testQueue.Enqueue(4.0f, "d");
testQueue.Enqueue(4.0f, "d"); testQueue.Enqueue(6.0f, "f");
testQueue.Enqueue(6.0f, "f"); testQueue.Enqueue(5.0f, "e");
testQueue.Enqueue(5.0f, "e");
Assert.AreEqual("i", testQueue.Dequeue().Item);
Assert.AreEqual("i", testQueue.Dequeue().Item); Assert.AreEqual("h", testQueue.Dequeue().Item);
Assert.AreEqual("h", testQueue.Dequeue().Item); Assert.AreEqual("g", testQueue.Dequeue().Item);
Assert.AreEqual("g", testQueue.Dequeue().Item); Assert.AreEqual("f", testQueue.Dequeue().Item);
Assert.AreEqual("f", testQueue.Dequeue().Item); Assert.AreEqual("e", testQueue.Dequeue().Item);
Assert.AreEqual("e", testQueue.Dequeue().Item); Assert.AreEqual("d", testQueue.Dequeue().Item);
Assert.AreEqual("d", testQueue.Dequeue().Item); Assert.AreEqual("c", testQueue.Dequeue().Item);
Assert.AreEqual("c", testQueue.Dequeue().Item); Assert.AreEqual("b", testQueue.Dequeue().Item);
Assert.AreEqual("b", testQueue.Dequeue().Item); Assert.AreEqual("a", testQueue.Dequeue().Item);
Assert.AreEqual("a", testQueue.Dequeue().Item); }
}
/// <summary>Tests to ensure that the priority collection's Peek() method works</summary>
/// <summary>Tests to ensure that the priority collection's Peek() method works</summary> [Test]
[Test] public void TestPeek() {
public void TestPeek() { PairPriorityQueue<float, string> testQueue = new PairPriorityQueue<float, string>();
PairPriorityQueue<float, string> testQueue = new PairPriorityQueue<float, string>();
testQueue.Enqueue(1.0f, "a");
testQueue.Enqueue(1.0f, "a"); testQueue.Enqueue(2.0f, "b");
testQueue.Enqueue(2.0f, "b"); testQueue.Enqueue(0.0f, "c");
testQueue.Enqueue(0.0f, "c");
Assert.AreEqual("b", testQueue.Peek().Item);
Assert.AreEqual("b", testQueue.Peek().Item); }
}
/// <summary>Tests whether the priority collection can copy itself into an array</summary>
/// <summary>Tests whether the priority collection can copy itself into an array</summary> [Test]
[Test] public void TestCopyTo() {
public void TestCopyTo() { PairPriorityQueue<float, string> testQueue = new PairPriorityQueue<float, string>();
PairPriorityQueue<float, string> testQueue = new PairPriorityQueue<float, string>();
testQueue.Enqueue(1.0f, "a");
testQueue.Enqueue(1.0f, "a"); testQueue.Enqueue(9.0f, "i");
testQueue.Enqueue(9.0f, "i"); testQueue.Enqueue(2.0f, "b");
testQueue.Enqueue(2.0f, "b"); testQueue.Enqueue(8.0f, "h");
testQueue.Enqueue(8.0f, "h"); testQueue.Enqueue(3.0f, "c");
testQueue.Enqueue(3.0f, "c"); testQueue.Enqueue(7.0f, "g");
testQueue.Enqueue(7.0f, "g"); testQueue.Enqueue(4.0f, "d");
testQueue.Enqueue(4.0f, "d"); testQueue.Enqueue(6.0f, "f");
testQueue.Enqueue(6.0f, "f"); testQueue.Enqueue(5.0f, "e");
testQueue.Enqueue(5.0f, "e");
PriorityItemPair<float, string>[] itemArray = new PriorityItemPair<float, string>[9];
PriorityItemPair<float, string>[] itemArray = new PriorityItemPair<float, string>[9]; testQueue.CopyTo(itemArray, 0);
testQueue.CopyTo(itemArray, 0);
CollectionAssert.AreEquivalent(testQueue, itemArray);
CollectionAssert.AreEquivalent(testQueue, itemArray); }
}
/// <summary>
/// <summary> /// Tests whether the priority collection provides a synchronization root
/// Tests whether the priority collection provides a synchronization root /// </summary>
/// </summary> [Test]
[Test] public void TestSyncRoot() {
public void TestSyncRoot() { PairPriorityQueue<int, int> testQueue = new PairPriorityQueue<int, int>();
PairPriorityQueue<int, int> testQueue = new PairPriorityQueue<int, int>();
// If IsSynchronized returns true, SyncRoot is allowed to be null
// If IsSynchronized returns true, SyncRoot is allowed to be null if(!testQueue.IsSynchronized) {
if(!testQueue.IsSynchronized) { lock(testQueue.SyncRoot) {
lock(testQueue.SyncRoot) { testQueue.Clear();
testQueue.Clear(); }
} }
} }
}
/// <summary>
/// <summary> /// Tests whether the priority collection provides a working type-safe enumerator
/// Tests whether the priority collection provides a working type-safe enumerator /// </summary>
/// </summary> [Test]
[Test] public void TestEnumerator() {
public void TestEnumerator() { PairPriorityQueue<float, string> testQueue = new PairPriorityQueue<float, string>();
PairPriorityQueue<float, string> testQueue = new PairPriorityQueue<float, string>();
testQueue.Enqueue(1.0f, "a");
testQueue.Enqueue(1.0f, "a"); testQueue.Enqueue(2.0f, "b");
testQueue.Enqueue(2.0f, "b"); testQueue.Enqueue(0.0f, "c");
testQueue.Enqueue(0.0f, "c");
List<PriorityItemPair<float, string>> testList =
List<PriorityItemPair<float, string>> testList = new List<PriorityItemPair<float,string>>();
new List<PriorityItemPair<float,string>>();
foreach(PriorityItemPair<float, string> entry in testQueue) {
foreach(PriorityItemPair<float, string> entry in testQueue) { testList.Add(entry);
testList.Add(entry); }
}
CollectionAssert.AreEquivalent(testQueue, testList);
CollectionAssert.AreEquivalent(testQueue, testList); }
}
}
}
} // namespace Nuclex.Support.Collections
} // namespace Nuclex.Support.Collections
#endif // UNITTEST
#endif // UNITTEST

View File

@ -1,144 +1,143 @@
#region CPL License #region Apache License 2.0
/* /*
Nuclex Framework Nuclex .NET Framework
Copyright (C) 2002-2017 Nuclex Development Labs Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
This library is free software; you can redistribute it and/or Licensed under the Apache License, Version 2.0 (the "License");
modify it under the terms of the IBM Common Public License as you may not use this file except in compliance with the License.
published by the IBM Corporation; either version 1.0 of the You may obtain a copy of the License at
License, or (at your option) any later version.
http://www.apache.org/licenses/LICENSE-2.0
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of Unless required by applicable law or agreed to in writing, software
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the distributed under the License is distributed on an "AS IS" BASIS,
IBM Common Public License for more details. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
You should have received a copy of the IBM Common Public limitations under the License.
License along with this library */
*/ #endregion // Apache License 2.0
#endregion
using System;
using System; using System.Collections.Generic;
using System.Collections.Generic; using System.Collections;
using System.Collections;
namespace Nuclex.Support.Collections {
namespace Nuclex.Support.Collections {
/// <summary>Queue that dequeues items in order of their priority</summary>
/// <summary>Queue that dequeues items in order of their priority</summary> /// <remarks>
/// <remarks> /// This variant of the priority queue uses an external priority value. If the
/// This variant of the priority queue uses an external priority value. If the /// priority data type implements the IComparable interface, the user does not
/// priority data type implements the IComparable interface, the user does not /// even
/// even /// </remarks>
/// </remarks> public class PairPriorityQueue<TPriority, TItem> :
public class PairPriorityQueue<TPriority, TItem> : ICollection, IEnumerable<PriorityItemPair<TPriority, TItem>> {
ICollection, IEnumerable<PriorityItemPair<TPriority, TItem>> {
#region class PairComparer
#region class PairComparer
/// <summary>Compares two priority queue entries based on their priority</summary>
/// <summary>Compares two priority queue entries based on their priority</summary> private class PairComparer : IComparer<PriorityItemPair<TPriority, TItem>> {
private class PairComparer : IComparer<PriorityItemPair<TPriority, TItem>> {
/// <summary>Initializes a new entry comparer</summary>
/// <summary>Initializes a new entry comparer</summary> /// <param name="priorityComparer">Comparer used to compare entry priorities</param>
/// <param name="priorityComparer">Comparer used to compare entry priorities</param> public PairComparer(IComparer<TPriority> priorityComparer) {
public PairComparer(IComparer<TPriority> priorityComparer) { this.priorityComparer = priorityComparer;
this.priorityComparer = priorityComparer; }
}
/// <summary>Compares the left entry to the right entry</summary>
/// <summary>Compares the left entry to the right entry</summary> /// <param name="left">Entry on the left side</param>
/// <param name="left">Entry on the left side</param> /// <param name="right">Entry on the right side</param>
/// <param name="right">Entry on the right side</param> /// <returns>The relationship of the two entries</returns>
/// <returns>The relationship of the two entries</returns> public int Compare(
public int Compare( PriorityItemPair<TPriority, TItem> left,
PriorityItemPair<TPriority, TItem> left, PriorityItemPair<TPriority, TItem> right
PriorityItemPair<TPriority, TItem> right ) {
) { return this.priorityComparer.Compare(left.Priority, right.Priority);
return this.priorityComparer.Compare(left.Priority, right.Priority); }
}
/// <summary>Comparer used to compare the priorities of the entries</summary>
/// <summary>Comparer used to compare the priorities of the entries</summary> private IComparer<TPriority> priorityComparer;
private IComparer<TPriority> priorityComparer;
}
}
#endregion // class EntryComparer
#endregion // class EntryComparer
/// <summary>Initializes a new non-intrusive priority queue</summary>
/// <summary>Initializes a new non-intrusive priority queue</summary> public PairPriorityQueue() : this(Comparer<TPriority>.Default) { }
public PairPriorityQueue() : this(Comparer<TPriority>.Default) { }
/// <summary>Initializes a new non-intrusive priority queue</summary>
/// <summary>Initializes a new non-intrusive priority queue</summary> /// <param name="priorityComparer">Comparer used to compare the item priorities</param>
/// <param name="priorityComparer">Comparer used to compare the item priorities</param> public PairPriorityQueue(IComparer<TPriority> priorityComparer) {
public PairPriorityQueue(IComparer<TPriority> priorityComparer) { this.internalQueue = new PriorityQueue<PriorityItemPair<TPriority, TItem>>(
this.internalQueue = new PriorityQueue<PriorityItemPair<TPriority, TItem>>( new PairComparer(priorityComparer)
new PairComparer(priorityComparer) );
); }
}
/// <summary>Returns the topmost item in the queue without dequeueing it</summary>
/// <summary>Returns the topmost item in the queue without dequeueing it</summary> /// <returns>The topmost item in the queue</returns>
/// <returns>The topmost item in the queue</returns> public PriorityItemPair<TPriority, TItem> Peek() {
public PriorityItemPair<TPriority, TItem> Peek() { return this.internalQueue.Peek();
return this.internalQueue.Peek(); }
}
/// <summary>Takes the item with the highest priority off from the queue</summary>
/// <summary>Takes the item with the highest priority off from the queue</summary> /// <returns>The item with the highest priority in the list</returns>
/// <returns>The item with the highest priority in the list</returns> public PriorityItemPair<TPriority, TItem> Dequeue() {
public PriorityItemPair<TPriority, TItem> Dequeue() { return this.internalQueue.Dequeue();
return this.internalQueue.Dequeue(); }
}
/// <summary>Puts an item into the priority queue</summary>
/// <summary>Puts an item into the priority queue</summary> /// <param name="priority">Priority of the item to be queued</param>
/// <param name="priority">Priority of the item to be queued</param> /// <param name="item">Item to be queued</param>
/// <param name="item">Item to be queued</param> public void Enqueue(TPriority priority, TItem item) {
public void Enqueue(TPriority priority, TItem item) { this.internalQueue.Enqueue(
this.internalQueue.Enqueue( new PriorityItemPair<TPriority, TItem>(priority, item)
new PriorityItemPair<TPriority, TItem>(priority, item) );
); }
}
/// <summary>Removes all items from the priority queue</summary>
/// <summary>Removes all items from the priority queue</summary> public void Clear() {
public void Clear() { this.internalQueue.Clear();
this.internalQueue.Clear(); }
}
/// <summary>Total number of items in the priority queue</summary>
/// <summary>Total number of items in the priority queue</summary> public int Count {
public int Count { get { return this.internalQueue.Count; }
get { return this.internalQueue.Count; } }
}
/// <summary>Copies the contents of the priority queue into an array</summary>
/// <summary>Copies the contents of the priority queue into an array</summary> /// <param name="array">Array to copy the priority queue into</param>
/// <param name="array">Array to copy the priority queue into</param> /// <param name="index">Starting index for the destination array</param>
/// <param name="index">Starting index for the destination array</param> public void CopyTo(Array array, int index) {
public void CopyTo(Array array, int index) { this.internalQueue.CopyTo(array, index);
this.internalQueue.CopyTo(array, index); }
}
/// <summary>
/// <summary> /// Obtains an object that can be used to synchronize accesses to the priority queue
/// Obtains an object that can be used to synchronize accesses to the priority queue /// from different threads
/// from different threads /// </summary>
/// </summary> public object SyncRoot {
public object SyncRoot { get { return this.internalQueue.SyncRoot; }
get { return this.internalQueue.SyncRoot; } }
}
/// <summary>Whether operations performed on this priority queue are thread safe</summary>
/// <summary>Whether operations performed on this priority queue are thread safe</summary> public bool IsSynchronized {
public bool IsSynchronized { get { return this.internalQueue.IsSynchronized; }
get { return this.internalQueue.IsSynchronized; } }
}
/// <summary>Returns a typesafe enumerator for the priority queue</summary>
/// <summary>Returns a typesafe enumerator for the priority queue</summary> /// <returns>A new enumerator for the priority queue</returns>
/// <returns>A new enumerator for the priority queue</returns> public IEnumerator<PriorityItemPair<TPriority, TItem>> GetEnumerator() {
public IEnumerator<PriorityItemPair<TPriority, TItem>> GetEnumerator() { return this.internalQueue.GetEnumerator();
return this.internalQueue.GetEnumerator(); }
}
/// <summary>Returns an enumerator for the priority queue</summary>
/// <summary>Returns an enumerator for the priority queue</summary> /// <returns>A new enumerator for the priority queue</returns>
/// <returns>A new enumerator for the priority queue</returns> IEnumerator IEnumerable.GetEnumerator() {
IEnumerator IEnumerable.GetEnumerator() { return this.internalQueue.GetEnumerator();
return this.internalQueue.GetEnumerator(); }
}
/// <summary>Intrusive priority queue being wrapped by this class</summary>
/// <summary>Intrusive priority queue being wrapped by this class</summary> private PriorityQueue<PriorityItemPair<TPriority, TItem>> internalQueue;
private PriorityQueue<PriorityItemPair<TPriority, TItem>> internalQueue;
}
}
} // namespace Nuclex.Support.Collections
} // namespace Nuclex.Support.Collections

View File

@ -1,101 +1,100 @@
#region CPL License #region Apache License 2.0
/* /*
Nuclex Framework Nuclex .NET Framework
Copyright (C) 2002-2017 Nuclex Development Labs Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
This library is free software; you can redistribute it and/or Licensed under the Apache License, Version 2.0 (the "License");
modify it under the terms of the IBM Common Public License as you may not use this file except in compliance with the License.
published by the IBM Corporation; either version 1.0 of the You may obtain a copy of the License at
License, or (at your option) any later version.
http://www.apache.org/licenses/LICENSE-2.0
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of Unless required by applicable law or agreed to in writing, software
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the distributed under the License is distributed on an "AS IS" BASIS,
IBM Common Public License for more details. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
You should have received a copy of the IBM Common Public limitations under the License.
License along with this library */
*/ #endregion // Apache License 2.0
#endregion
using System;
using System; using System.Collections.Generic;
using System.Collections.Generic;
#if UNITTEST
#if UNITTEST
using NUnit.Framework;
using NUnit.Framework;
namespace Nuclex.Support.Collections {
namespace Nuclex.Support.Collections {
/// <summary>Unit Test for the Parentable class</summary>
/// <summary>Unit Test for the Parentable class</summary> [TestFixture]
[TestFixture] internal class ParentableTest {
internal class ParentableTest {
#region class TestParentable
#region class TestParentable
/// <summary>Parentable object that can be the child of an int</summary>
/// <summary>Parentable object that can be the child of an int</summary> private class TestParentable : Parentable<int> {
private class TestParentable : Parentable<int> {
/// <summary>Initializes a new instance of the parentable test class</summary>
/// <summary>Initializes a new instance of the parentable test class</summary> public TestParentable() { }
public TestParentable() { }
/// <summary>The parent object that owns this instance</summary>
/// <summary>The parent object that owns this instance</summary> public int GetParent() {
public int GetParent() { return base.Parent;
return base.Parent; }
}
/// <summary>Invoked whenever the instance's owner changes</summary>
/// <summary>Invoked whenever the instance's owner changes</summary> /// <remarks>
/// <remarks> /// When items are parented for the first time, the oldParent argument will
/// When items are parented for the first time, the oldParent argument will /// be null. Also, if the element is removed from the collection, the
/// be null. Also, if the element is removed from the collection, the /// current parent will be null.
/// current parent will be null. /// </remarks>
/// </remarks> /// <param name="oldParent">Previous owner of the instance</param>
/// <param name="oldParent">Previous owner of the instance</param> protected override void OnParentChanged(int oldParent) {
protected override void OnParentChanged(int oldParent) { this.parentChangedCalled = true;
this.parentChangedCalled = true;
base.OnParentChanged(oldParent); // to satisfy NCover :-/
base.OnParentChanged(oldParent); // to satisfy NCover :-/ }
}
/// <summary>Whether the OnParentChanged method has been called</summary>
/// <summary>Whether the OnParentChanged method has been called</summary> public bool ParentChangedCalled {
public bool ParentChangedCalled { get { return this.parentChangedCalled; }
get { return this.parentChangedCalled; } }
}
/// <summary>Whether the OnParentChanged method has been called</summary>
/// <summary>Whether the OnParentChanged method has been called</summary> private bool parentChangedCalled;
private bool parentChangedCalled;
}
}
#endregion // class TestParentable
#endregion // class TestParentable
/// <summary>
/// <summary> /// Tests whether a parent can be assigned and then retrieved from
/// Tests whether a parent can be assigned and then retrieved from /// the parentable object
/// the parentable object /// </summary>
/// </summary> [Test]
[Test] public void TestParentAssignment() {
public void TestParentAssignment() { TestParentable testParentable = new TestParentable();
TestParentable testParentable = new TestParentable();
testParentable.SetParent(12345);
testParentable.SetParent(12345); Assert.AreEqual(12345, testParentable.GetParent());
Assert.AreEqual(12345, testParentable.GetParent()); }
}
/// <summary>
/// <summary> /// Tests whether a parent can be assigned and then retrieved from
/// Tests whether a parent can be assigned and then retrieved from /// the parentable object
/// the parentable object /// </summary>
/// </summary> [Test]
[Test] public void TestParentChangedNotification() {
public void TestParentChangedNotification() { TestParentable testParentable = new TestParentable();
TestParentable testParentable = new TestParentable();
testParentable.SetParent(12345);
testParentable.SetParent(12345);
Assert.IsTrue(testParentable.ParentChangedCalled);
Assert.IsTrue(testParentable.ParentChangedCalled); }
}
}
}
} // namespace Nuclex.Support.Collections
} // namespace Nuclex.Support.Collections
#endif // UNITTEST
#endif // UNITTEST

View File

@ -1,56 +1,55 @@
#region CPL License #region Apache License 2.0
/* /*
Nuclex Framework Nuclex .NET Framework
Copyright (C) 2002-2017 Nuclex Development Labs Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
This library is free software; you can redistribute it and/or Licensed under the Apache License, Version 2.0 (the "License");
modify it under the terms of the IBM Common Public License as you may not use this file except in compliance with the License.
published by the IBM Corporation; either version 1.0 of the You may obtain a copy of the License at
License, or (at your option) any later version.
http://www.apache.org/licenses/LICENSE-2.0
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of Unless required by applicable law or agreed to in writing, software
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the distributed under the License is distributed on an "AS IS" BASIS,
IBM Common Public License for more details. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
You should have received a copy of the IBM Common Public limitations under the License.
License along with this library */
*/ #endregion // Apache License 2.0
#endregion
using System;
using System;
namespace Nuclex.Support.Collections {
namespace Nuclex.Support.Collections {
/// <summary>Base class for objects that can be parented to an owner</summary>
/// <summary>Base class for objects that can be parented to an owner</summary> /// <typeparam name="TParent">Type of the parent object</typeparam>
/// <typeparam name="TParent">Type of the parent object</typeparam> public class Parentable<TParent> {
public class Parentable<TParent> {
/// <summary>The parent object that owns this instance</summary>
/// <summary>The parent object that owns this instance</summary> protected TParent Parent {
protected TParent Parent { get { return this.parent; }
get { return this.parent; } }
}
/// <summary>Invoked whenever the instance's owner changes</summary>
/// <summary>Invoked whenever the instance's owner changes</summary> /// <remarks>
/// <remarks> /// When items are parented for the first time, the oldParent argument will
/// When items are parented for the first time, the oldParent argument will /// be null. Also, if the element is removed from the collection, the
/// be null. Also, if the element is removed from the collection, the /// current parent will be null.
/// current parent will be null. /// </remarks>
/// </remarks> /// <param name="oldParent">Previous owner of the instance</param>
/// <param name="oldParent">Previous owner of the instance</param> protected virtual void OnParentChanged(TParent oldParent) { }
protected virtual void OnParentChanged(TParent oldParent) { }
/// <summary>Assigns a new parent to this instance</summary>
/// <summary>Assigns a new parent to this instance</summary> internal void SetParent(TParent parent) {
internal void SetParent(TParent parent) { TParent oldParent = this.parent;
TParent oldParent = this.parent; this.parent = parent;
this.parent = parent;
OnParentChanged(oldParent);
OnParentChanged(oldParent); }
}
/// <summary>Current parent of this object</summary>
/// <summary>Current parent of this object</summary> private TParent parent;
private TParent parent;
}
}
} // namespace Nuclex.Support.Collections
} // namespace Nuclex.Support.Collections

View File

@ -1,190 +1,189 @@
#region CPL License #region Apache License 2.0
/* /*
Nuclex Framework Nuclex .NET Framework
Copyright (C) 2002-2017 Nuclex Development Labs Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
This library is free software; you can redistribute it and/or Licensed under the Apache License, Version 2.0 (the "License");
modify it under the terms of the IBM Common Public License as you may not use this file except in compliance with the License.
published by the IBM Corporation; either version 1.0 of the You may obtain a copy of the License at
License, or (at your option) any later version.
http://www.apache.org/licenses/LICENSE-2.0
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of Unless required by applicable law or agreed to in writing, software
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the distributed under the License is distributed on an "AS IS" BASIS,
IBM Common Public License for more details. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
You should have received a copy of the IBM Common Public limitations under the License.
License along with this library */
*/ #endregion // Apache License 2.0
#endregion
using System;
using System; using System.Collections.Generic;
using System.Collections.Generic;
#if UNITTEST
#if UNITTEST
using NUnit.Framework;
using NUnit.Framework;
namespace Nuclex.Support.Collections {
namespace Nuclex.Support.Collections {
/// <summary>Unit Test for the Parenting Collection class</summary>
/// <summary>Unit Test for the Parenting Collection class</summary> [TestFixture]
[TestFixture] internal class ParentingCollectionTest {
internal class ParentingCollectionTest {
#region class TestParentable
#region class TestParentable
/// <summary>Parentable object that can be the child of an int</summary>
/// <summary>Parentable object that can be the child of an int</summary> private class TestParentable : Parentable<int>, IDisposable {
private class TestParentable : Parentable<int>, IDisposable {
/// <summary>Initializes a new instance of the parentable test class</summary>
/// <summary>Initializes a new instance of the parentable test class</summary> public TestParentable() { }
public TestParentable() { }
/// <summary>The parent object that owns this instance</summary>
/// <summary>The parent object that owns this instance</summary> public int GetParent() {
public int GetParent() { return base.Parent;
return base.Parent; }
}
/// <summary>Immediately releases all resources owned by the item</summary>
/// <summary>Immediately releases all resources owned by the item</summary> public void Dispose() {
public void Dispose() { this.disposeCalled = true;
this.disposeCalled = true; }
}
/// <summary>Whether Dispose() has been called on this item</summary>
/// <summary>Whether Dispose() has been called on this item</summary> public bool DisposeCalled {
public bool DisposeCalled { get { return this.disposeCalled; }
get { return this.disposeCalled; } }
}
/// <summary>Whether Dispose() has been called on this item</summary>
/// <summary>Whether Dispose() has been called on this item</summary> private bool disposeCalled;
private bool disposeCalled;
}
}
#endregion // class TestParentable
#endregion // class TestParentable
#region class TestParentingCollection
#region class TestParentingCollection
/// <summary>Parentable object that can be the child of an int</summary>
/// <summary>Parentable object that can be the child of an int</summary> private class TestParentingCollection : ParentingCollection<int, TestParentable> {
private class TestParentingCollection : ParentingCollection<int, TestParentable> {
/// <summary>Changes the parent of the collection</summary>
/// <summary>Changes the parent of the collection</summary> /// <param name="parent">New parent to assign to the collection</param>
/// <param name="parent">New parent to assign to the collection</param> public void SetParent(int parent) {
public void SetParent(int parent) { base.Reparent(parent);
base.Reparent(parent); }
}
/// <summary>Disposes all items contained in the collection</summary>
/// <summary>Disposes all items contained in the collection</summary> public new void DisposeItems() {
public new void DisposeItems() { base.DisposeItems();
base.DisposeItems(); }
}
}
}
#endregion // class TestParentingCollection
#endregion // class TestParentingCollection
/// <summary>
/// <summary> /// Tests whether the parenting collection propagates its parent to an item that
/// Tests whether the parenting collection propagates its parent to an item that /// is added to the collection after the collection's aprent is already assigned
/// is added to the collection after the collection's aprent is already assigned /// </summary>
/// </summary> [Test]
[Test] public void TestPropagatePreassignedParent() {
public void TestPropagatePreassignedParent() { TestParentingCollection testCollection = new TestParentingCollection();
TestParentingCollection testCollection = new TestParentingCollection(); TestParentable testParentable = new TestParentable();
TestParentable testParentable = new TestParentable();
testCollection.SetParent(54321);
testCollection.SetParent(54321); testCollection.Add(testParentable);
testCollection.Add(testParentable);
Assert.AreEqual(54321, testParentable.GetParent());
Assert.AreEqual(54321, testParentable.GetParent()); }
}
/// <summary>
/// <summary> /// Tests whether the parenting collection propagates a new parent to all items
/// Tests whether the parenting collection propagates a new parent to all items /// contained in it when its parent is changed
/// contained in it when its parent is changed /// </summary>
/// </summary> [Test]
[Test] public void TestPropagateParentChange() {
public void TestPropagateParentChange() { TestParentingCollection testCollection = new TestParentingCollection();
TestParentingCollection testCollection = new TestParentingCollection(); TestParentable testParentable = new TestParentable();
TestParentable testParentable = new TestParentable();
testCollection.Add(testParentable);
testCollection.Add(testParentable); testCollection.SetParent(54321);
testCollection.SetParent(54321);
Assert.AreEqual(54321, testParentable.GetParent());
Assert.AreEqual(54321, testParentable.GetParent()); }
}
/// <summary>
/// <summary> /// Tests whether the parenting collection propagates its parent to an item that
/// Tests whether the parenting collection propagates its parent to an item that /// is added to the collection after the collection's aprent is already assigned
/// is added to the collection after the collection's aprent is already assigned /// </summary>
/// </summary> [Test]
[Test] public void TestPropagateParentOnReplace() {
public void TestPropagateParentOnReplace() { TestParentingCollection testCollection = new TestParentingCollection();
TestParentingCollection testCollection = new TestParentingCollection(); TestParentable testParentable1 = new TestParentable();
TestParentable testParentable1 = new TestParentable(); TestParentable testParentable2 = new TestParentable();
TestParentable testParentable2 = new TestParentable();
testCollection.SetParent(54321);
testCollection.SetParent(54321); testCollection.Add(testParentable1);
testCollection.Add(testParentable1); testCollection[0] = testParentable2;
testCollection[0] = testParentable2;
Assert.AreEqual(0, testParentable1.GetParent());
Assert.AreEqual(0, testParentable1.GetParent()); Assert.AreEqual(54321, testParentable2.GetParent());
Assert.AreEqual(54321, testParentable2.GetParent()); }
}
/// <summary>
/// <summary> /// Tests whether the parenting collection unsets the parent when an item is removed
/// Tests whether the parenting collection unsets the parent when an item is removed /// from the collection
/// from the collection /// </summary>
/// </summary> [Test]
[Test] public void TestUnsetParentOnRemoveItem() {
public void TestUnsetParentOnRemoveItem() { TestParentingCollection testCollection = new TestParentingCollection();
TestParentingCollection testCollection = new TestParentingCollection(); TestParentable testParentable = new TestParentable();
TestParentable testParentable = new TestParentable();
testCollection.Add(testParentable);
testCollection.Add(testParentable); testCollection.SetParent(54321);
testCollection.SetParent(54321);
Assert.AreEqual(54321, testParentable.GetParent());
Assert.AreEqual(54321, testParentable.GetParent());
testCollection.RemoveAt(0);
testCollection.RemoveAt(0);
Assert.AreEqual(0, testParentable.GetParent());
Assert.AreEqual(0, testParentable.GetParent()); }
}
/// <summary>
/// <summary> /// Tests whether the parenting collection unsets the parent when all item are
/// Tests whether the parenting collection unsets the parent when all item are /// removed from the collection by clearing it
/// removed from the collection by clearing it /// </summary>
/// </summary> [Test]
[Test] public void TestUnsetParentOnClear() {
public void TestUnsetParentOnClear() { TestParentingCollection testCollection = new TestParentingCollection();
TestParentingCollection testCollection = new TestParentingCollection(); TestParentable testParentable = new TestParentable();
TestParentable testParentable = new TestParentable();
testCollection.Add(testParentable);
testCollection.Add(testParentable); testCollection.SetParent(54321);
testCollection.SetParent(54321);
Assert.AreEqual(54321, testParentable.GetParent());
Assert.AreEqual(54321, testParentable.GetParent());
testCollection.Clear();
testCollection.Clear();
Assert.AreEqual(0, testParentable.GetParent());
Assert.AreEqual(0, testParentable.GetParent()); }
}
/// <summary>
/// <summary> /// Tests whether the parenting collection calls Dispose() on all contained items
/// Tests whether the parenting collection calls Dispose() on all contained items /// that implement IDisposable when its DisposeItems() method is called
/// that implement IDisposable when its DisposeItems() method is called /// </summary>
/// </summary> [Test]
[Test] public void TestDisposeItems() {
public void TestDisposeItems() { TestParentingCollection testCollection = new TestParentingCollection();
TestParentingCollection testCollection = new TestParentingCollection(); TestParentable testParentable = new TestParentable();
TestParentable testParentable = new TestParentable();
testCollection.Add(testParentable);
testCollection.Add(testParentable);
testCollection.DisposeItems();
testCollection.DisposeItems();
Assert.IsTrue(testParentable.DisposeCalled);
Assert.IsTrue(testParentable.DisposeCalled); }
}
}
}
} // namespace Nuclex.Support.Collections
} // namespace Nuclex.Support.Collections
#endif // UNITTEST
#endif // UNITTEST

View File

@ -1,115 +1,114 @@
#region CPL License #region Apache License 2.0
/* /*
Nuclex Framework Nuclex .NET Framework
Copyright (C) 2002-2017 Nuclex Development Labs Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
This library is free software; you can redistribute it and/or Licensed under the Apache License, Version 2.0 (the "License");
modify it under the terms of the IBM Common Public License as you may not use this file except in compliance with the License.
published by the IBM Corporation; either version 1.0 of the You may obtain a copy of the License at
License, or (at your option) any later version.
http://www.apache.org/licenses/LICENSE-2.0
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of Unless required by applicable law or agreed to in writing, software
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the distributed under the License is distributed on an "AS IS" BASIS,
IBM Common Public License for more details. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
You should have received a copy of the IBM Common Public limitations under the License.
License along with this library */
*/ #endregion // Apache License 2.0
#endregion
using System;
using System; using System.Collections.ObjectModel;
using System.Collections.ObjectModel;
namespace Nuclex.Support.Collections {
namespace Nuclex.Support.Collections {
/// <summary>Collection that automatically assigns an owner to all its elements</summary>
/// <summary>Collection that automatically assigns an owner to all its elements</summary> /// <remarks>
/// <remarks> /// This collection automatically assigns a parent object to elements that
/// This collection automatically assigns a parent object to elements that /// are managed in it. The elements have to derive from the Parentable&lt;&gt;
/// are managed in it. The elements have to derive from the Parentable&lt;&gt; /// base class.
/// base class. /// </remarks>
/// </remarks> /// <typeparam name="TParent">Type of the parent object to assign to items</typeparam>
/// <typeparam name="TParent">Type of the parent object to assign to items</typeparam> /// <typeparam name="TItem">Type of the items being managed in the collection</typeparam>
/// <typeparam name="TItem">Type of the items being managed in the collection</typeparam> public class ParentingCollection<TParent, TItem> : Collection<TItem>
public class ParentingCollection<TParent, TItem> : Collection<TItem> where TItem : Parentable<TParent> {
where TItem : Parentable<TParent> {
/// <summary>Reparents all elements in the collection</summary>
/// <summary>Reparents all elements in the collection</summary> /// <param name="parent">New parent to take ownership of the items</param>
/// <param name="parent">New parent to take ownership of the items</param> protected void Reparent(TParent parent) {
protected void Reparent(TParent parent) { this.parent = parent;
this.parent = parent;
for(int index = 0; index < Count; ++index)
for(int index = 0; index < Count; ++index) base[index].SetParent(parent);
base[index].SetParent(parent); }
}
/// <summary>Clears all elements from the collection</summary>
/// <summary>Clears all elements from the collection</summary> protected override void ClearItems() {
protected override void ClearItems() { for(int index = 0; index < Count; ++index)
for(int index = 0; index < Count; ++index) base[index].SetParent(default(TParent));
base[index].SetParent(default(TParent));
base.ClearItems();
base.ClearItems(); }
}
/// <summary>Inserts a new element into the collection</summary>
/// <summary>Inserts a new element into the collection</summary> /// <param name="index">Index at which to insert the element</param>
/// <param name="index">Index at which to insert the element</param> /// <param name="item">Item to be inserted</param>
/// <param name="item">Item to be inserted</param> protected override void InsertItem(int index, TItem item) {
protected override void InsertItem(int index, TItem item) { base.InsertItem(index, item);
base.InsertItem(index, item); item.SetParent(this.parent);
item.SetParent(this.parent); }
}
/// <summary>Removes an element from the collection</summary>
/// <summary>Removes an element from the collection</summary> /// <param name="index">Index of the element to remove</param>
/// <param name="index">Index of the element to remove</param> protected override void RemoveItem(int index) {
protected override void RemoveItem(int index) { base[index].SetParent(default(TParent));
base[index].SetParent(default(TParent)); base.RemoveItem(index);
base.RemoveItem(index); }
}
/// <summary>Takes over a new element that is directly assigned</summary>
/// <summary>Takes over a new element that is directly assigned</summary> /// <param name="index">Index of the element that was assigned</param>
/// <param name="index">Index of the element that was assigned</param> /// <param name="item">New item</param>
/// <param name="item">New item</param> protected override void SetItem(int index, TItem item) {
protected override void SetItem(int index, TItem item) { base[index].SetParent(default(TParent));
base[index].SetParent(default(TParent)); base.SetItem(index, item);
base.SetItem(index, item); item.SetParent(this.parent);
item.SetParent(this.parent); }
}
/// <summary>Disposes all items contained in the collection</summary>
/// <summary>Disposes all items contained in the collection</summary> /// <remarks>
/// <remarks> /// <para>
/// <para> /// This method is intended to support collections that need to dispose their
/// This method is intended to support collections that need to dispose their /// items. It will unparent all of the collection's items and call Dispose()
/// items. It will unparent all of the collection's items and call Dispose() /// on any item that implements IDisposable.
/// on any item that implements IDisposable. /// </para>
/// </para> /// <para>
/// <para> /// Do not call this method from your destructor as it will access the
/// Do not call this method from your destructor as it will access the /// contained items in order to unparent and to Dispose() them, which leads
/// contained items in order to unparent and to Dispose() them, which leads /// to undefined behavior since the object might have already been collected
/// to undefined behavior since the object might have already been collected /// by the GC. Call it only if your object is being manually disposed.
/// by the GC. Call it only if your object is being manually disposed. /// </para>
/// </para> /// </remarks>
/// </remarks> protected void DisposeItems() {
protected void DisposeItems() {
// Dispose all the items in the collection that implement IDisposable,
// Dispose all the items in the collection that implement IDisposable, // starting from the last item in the assumption that this is the fastest
// starting from the last item in the assumption that this is the fastest // way to empty a list without causing excessive shiftings in the array.
// way to empty a list without causing excessive shiftings in the array. for(int index = base.Count - 1; index >= 0; --index) {
for(int index = base.Count - 1; index >= 0; --index) { IDisposable disposable = base[index] as IDisposable;
IDisposable disposable = base[index] as IDisposable;
// If the item is disposable, destroy it now
// If the item is disposable, destroy it now if(disposable != null) {
if(disposable != null) { disposable.Dispose();
disposable.Dispose(); }
} }
}
base.ClearItems();
base.ClearItems();
}
}
/// <summary>Parent this collection currently belongs to</summary>
/// <summary>Parent this collection currently belongs to</summary> private TParent parent;
private TParent parent;
}
}
} // namespace Nuclex.Support.Collections
} // namespace Nuclex.Support.Collections

View File

@ -1,118 +1,117 @@
#region CPL License #region Apache License 2.0
/* /*
Nuclex Framework Nuclex .NET Framework
Copyright (C) 2002-2017 Nuclex Development Labs Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
This library is free software; you can redistribute it and/or Licensed under the Apache License, Version 2.0 (the "License");
modify it under the terms of the IBM Common Public License as you may not use this file except in compliance with the License.
published by the IBM Corporation; either version 1.0 of the You may obtain a copy of the License at
License, or (at your option) any later version.
http://www.apache.org/licenses/LICENSE-2.0
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of Unless required by applicable law or agreed to in writing, software
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the distributed under the License is distributed on an "AS IS" BASIS,
IBM Common Public License for more details. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
You should have received a copy of the IBM Common Public limitations under the License.
License along with this library */
*/ #endregion // Apache License 2.0
#endregion
#if UNITTEST
#if UNITTEST
using System;
using System; using System.Collections.Generic;
using System.Collections.Generic;
using NUnit.Framework;
using NUnit.Framework;
namespace Nuclex.Support.Collections {
namespace Nuclex.Support.Collections {
/// <summary>Unit tests for the Pool class</summary>
/// <summary>Unit tests for the Pool class</summary> [TestFixture]
[TestFixture] internal class PoolTest {
internal class PoolTest {
#region class TestClass
#region class TestClass
/// <summary>Used to test the pool</summary>
/// <summary>Used to test the pool</summary> private class TestClass : IRecyclable {
private class TestClass : IRecyclable {
/// <summary>Returns the object to its initial state</summary>
/// <summary>Returns the object to its initial state</summary> public void Recycle() {
public void Recycle() { this.Recycled = true;
this.Recycled = true; }
}
/// <summary>Whether the instance has been recycled</summary>
/// <summary>Whether the instance has been recycled</summary> public bool Recycled;
public bool Recycled;
}
}
#endregion // class TestClass
#endregion // class TestClass
#region class NoDefaultConstructor
#region class NoDefaultConstructor
/// <summary>Used to test the pool</summary>
/// <summary>Used to test the pool</summary> private class NoDefaultConstructor {
private class NoDefaultConstructor {
/// <summary>Private constructor so no instances can be created</summary>
/// <summary>Private constructor so no instances can be created</summary> private NoDefaultConstructor() { }
private NoDefaultConstructor() { }
}
}
#endregion // class NoDefaultConstructor
#endregion // class NoDefaultConstructor
/// <summary>
/// <summary> /// Verifies that the pool can return newly constructed objects
/// Verifies that the pool can return newly constructed objects /// </summary>
/// </summary> [Test]
[Test] public void NewInstancesCanBeObtained() {
public void NewInstancesCanBeObtained() { Pool<TestClass> pool = new Pool<TestClass>();
Pool<TestClass> pool = new Pool<TestClass>(); Assert.IsNotNull(pool.Get());
Assert.IsNotNull(pool.Get()); }
}
/// <summary>
/// <summary> /// Verifies that an exception is thrown if the pool's default instance creator is used
/// Verifies that an exception is thrown if the pool's default instance creator is used /// on a type that doesn't have a default constructor
/// on a type that doesn't have a default constructor /// </summary>
/// </summary> [Test]
[Test] public void UsingDefaultInstanceCreatorRequiresDefaultConstructor() {
public void UsingDefaultInstanceCreatorRequiresDefaultConstructor() { Assert.Throws<ArgumentException>(
Assert.Throws<ArgumentException>( delegate() { new Pool<NoDefaultConstructor>(); }
delegate() { new Pool<NoDefaultConstructor>(); } );
); }
}
/// <summary>
/// <summary> /// Tests whether the pool can redeem objects that are no longer used
/// Tests whether the pool can redeem objects that are no longer used /// </summary>
/// </summary> [Test]
[Test] public void InstancesCanBeRedeemed() {
public void InstancesCanBeRedeemed() { Pool<TestClass> pool = new Pool<TestClass>();
Pool<TestClass> pool = new Pool<TestClass>(); pool.Redeem(new TestClass());
pool.Redeem(new TestClass()); }
}
/// <summary>
/// <summary> /// Tests whether the Recycle() method is called at the appropriate time
/// Tests whether the Recycle() method is called at the appropriate time /// </summary>
/// </summary> [Test]
[Test] public void RedeemedItemsWillBeRecycled() {
public void RedeemedItemsWillBeRecycled() { Pool<TestClass> pool = new Pool<TestClass>();
Pool<TestClass> pool = new Pool<TestClass>(); TestClass x = new TestClass();
TestClass x = new TestClass();
Assert.IsFalse(x.Recycled);
Assert.IsFalse(x.Recycled); pool.Redeem(x);
pool.Redeem(x); Assert.IsTrue(x.Recycled);
Assert.IsTrue(x.Recycled); }
}
/// <summary>Verifies that the pool's Capacity is applied correctly</summary>
/// <summary>Verifies that the pool's Capacity is applied correctly</summary> [Test]
[Test] public void PoolCapacityCanBeAdjusted() {
public void PoolCapacityCanBeAdjusted() { Pool<TestClass> pool = new Pool<TestClass>(123);
Pool<TestClass> pool = new Pool<TestClass>(123); Assert.AreEqual(123, pool.Capacity);
Assert.AreEqual(123, pool.Capacity); pool.Capacity = 321;
pool.Capacity = 321; Assert.AreEqual(321, pool.Capacity);
Assert.AreEqual(321, pool.Capacity); }
}
}
}
} // namespace Nuclex.Support.Collections
} // namespace Nuclex.Support.Collections
#endif // UNITTEST
#endif // UNITTEST

View File

@ -1,175 +1,174 @@
#region CPL License #region Apache License 2.0
/* /*
Nuclex Framework Nuclex .NET Framework
Copyright (C) 2002-2017 Nuclex Development Labs Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
This library is free software; you can redistribute it and/or Licensed under the Apache License, Version 2.0 (the "License");
modify it under the terms of the IBM Common Public License as you may not use this file except in compliance with the License.
published by the IBM Corporation; either version 1.0 of the You may obtain a copy of the License at
License, or (at your option) any later version.
http://www.apache.org/licenses/LICENSE-2.0
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of Unless required by applicable law or agreed to in writing, software
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the distributed under the License is distributed on an "AS IS" BASIS,
IBM Common Public License for more details. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
You should have received a copy of the IBM Common Public limitations under the License.
License along with this library */
*/ #endregion // Apache License 2.0
#endregion
using System;
using System; using System.Collections.Generic;
using System.Collections.Generic;
namespace Nuclex.Support.Collections {
namespace Nuclex.Support.Collections {
/// <summary>Pool that recycles objects in order to avoid garbage build-up</summary>
/// <summary>Pool that recycles objects in order to avoid garbage build-up</summary> /// <typeparam name="TItem">Type of objects being pooled</typeparam>
/// <typeparam name="TItem">Type of objects being pooled</typeparam> /// <remarks>
/// <remarks> /// <para>
/// <para> /// Use this class to recycle objects instead of letting them become garbage,
/// Use this class to recycle objects instead of letting them become garbage, /// creating new instances each time. The Pool class is designed to either be
/// creating new instances each time. The Pool class is designed to either be /// used on its own or as a building block for a static class that wraps it.
/// used on its own or as a building block for a static class that wraps it. /// </para>
/// </para> /// <para>
/// <para> /// Special care has to be taken to revert the entire state of a recycled
/// Special care has to be taken to revert the entire state of a recycled /// object when it is returned to the pool. For example, events will need to
/// object when it is returned to the pool. For example, events will need to /// have their subscriber lists emptied to avoid sending out events to the
/// have their subscriber lists emptied to avoid sending out events to the /// wrong subscribers and accumulating more and more subscribers each time
/// wrong subscribers and accumulating more and more subscribers each time /// they are reused.
/// they are reused. /// </para>
/// </para> /// <para>
/// <para> /// To simplify such cleanup, pooled objects can implement the IRecyclable
/// To simplify such cleanup, pooled objects can implement the IRecyclable /// interface. When an object is returned to the pool, the pool will
/// interface. When an object is returned to the pool, the pool will /// automatically call its IRecyclable.Recycle() method.
/// automatically call its IRecyclable.Recycle() method. /// </para>
/// </para> /// </remarks>
/// </remarks> public class Pool<TItem> {
public class Pool<TItem> {
/// <summary>Default number of recyclable objects the pool will store</summary>
/// <summary>Default number of recyclable objects the pool will store</summary> public const int DefaultPoolSize = 64;
public const int DefaultPoolSize = 64;
/// <summary>Initializes a new pool using the default capacity</summary>
/// <summary>Initializes a new pool using the default capacity</summary> public Pool() : this(DefaultPoolSize, null, null) { }
public Pool() : this(DefaultPoolSize, null, null) { }
/// <summary>Initializes a new pool using the default capacity</summary>
/// <summary>Initializes a new pool using the default capacity</summary> /// <param name="createNewDelegate">Delegate that will be used to create new items</param>
/// <param name="createNewDelegate">Delegate that will be used to create new items</param> public Pool(Func<TItem> createNewDelegate) :
public Pool(Func<TItem> createNewDelegate) : this(DefaultPoolSize, createNewDelegate, null) { }
this(DefaultPoolSize, createNewDelegate, null) { }
/// <summary>Initializes a new pool using the default capacity</summary>
/// <summary>Initializes a new pool using the default capacity</summary> /// <param name="createNewDelegate">Delegate that will be used to create new items</param>
/// <param name="createNewDelegate">Delegate that will be used to create new items</param> /// <param name="recycleDelegate">Delegate that will be used to recycle items</param>
/// <param name="recycleDelegate">Delegate that will be used to recycle items</param> public Pool(Func<TItem> createNewDelegate, Action<TItem> recycleDelegate) :
public Pool(Func<TItem> createNewDelegate, Action<TItem> recycleDelegate) : this(DefaultPoolSize, createNewDelegate, recycleDelegate) { }
this(DefaultPoolSize, createNewDelegate, recycleDelegate) { }
/// <summary>Initializes a new pool using a user-specified capacity</summary>
/// <summary>Initializes a new pool using a user-specified capacity</summary> /// <param name="capacity">Capacity of the pool</param>
/// <param name="capacity">Capacity of the pool</param> public Pool(int capacity) :
public Pool(int capacity) : this(capacity, null, null) { }
this(capacity, null, null) { }
/// <summary>Initializes a new pool using a user-specified capacity</summary>
/// <summary>Initializes a new pool using a user-specified capacity</summary> /// <param name="capacity">Capacity of the pool</param>
/// <param name="capacity">Capacity of the pool</param> /// <param name="createNewDelegate">Delegate that will be used to create new items</param>
/// <param name="createNewDelegate">Delegate that will be used to create new items</param> public Pool(int capacity, Func<TItem> createNewDelegate) :
public Pool(int capacity, Func<TItem> createNewDelegate) : this(capacity, createNewDelegate, null) { }
this(capacity, createNewDelegate, null) { }
/// <summary>Initializes a new pool using a user-specified capacity</summary>
/// <summary>Initializes a new pool using a user-specified capacity</summary> /// <param name="capacity">Capacity of the pool</param>
/// <param name="capacity">Capacity of the pool</param> /// <param name="createNewDelegate">Delegate that will be used to create new items</param>
/// <param name="createNewDelegate">Delegate that will be used to create new items</param> /// <param name="recycleDelegate">Delegate that will be used to recycle items</param>
/// <param name="recycleDelegate">Delegate that will be used to recycle items</param> public Pool(int capacity, Func<TItem> createNewDelegate, Action<TItem> recycleDelegate) {
public Pool(int capacity, Func<TItem> createNewDelegate, Action<TItem> recycleDelegate) { Capacity = capacity;
Capacity = capacity;
if(createNewDelegate == null) {
if(createNewDelegate == null) { if(!typeof(TItem).HasDefaultConstructor()) {
if(!typeof(TItem).HasDefaultConstructor()) { throw new ArgumentException(
throw new ArgumentException( "Type " + typeof(TItem).Name + " has no default constructor and " +
"Type " + typeof(TItem).Name + " has no default constructor and " + "requires a custom 'create instance' delegate"
"requires a custom 'create instance' delegate" );
); }
} createNewDelegate = new Func<TItem>(Activator.CreateInstance<TItem>);
createNewDelegate = new Func<TItem>(Activator.CreateInstance<TItem>); }
} if(recycleDelegate == null) {
if(recycleDelegate == null) { recycleDelegate = new Action<TItem>(callRecycleIfSupported);
recycleDelegate = new Action<TItem>(callRecycleIfSupported); }
}
this.createNewDelegate = createNewDelegate;
this.createNewDelegate = createNewDelegate; this.recycleDelegate = recycleDelegate;
this.recycleDelegate = recycleDelegate; }
}
/// <summary>
/// <summary> /// Returns a new or recycled instance of the types managed by the pool
/// Returns a new or recycled instance of the types managed by the pool /// </summary>
/// </summary> /// <returns>A new or recycled instance</returns>
/// <returns>A new or recycled instance</returns> public TItem Get() {
public TItem Get() { lock(this) {
lock(this) { if(this.items.Count > 0) {
if(this.items.Count > 0) { return this.items.Dequeue();
return this.items.Dequeue(); } else {
} else { return this.createNewDelegate();
return this.createNewDelegate(); }
} }
} }
}
/// <summary>
/// <summary> /// Redeems an instance that is no longer used to be recycled by the pool
/// Redeems an instance that is no longer used to be recycled by the pool /// </summary>
/// </summary> /// <param name="item">The instance that will be redeemed</param>
/// <param name="item">The instance that will be redeemed</param> public void Redeem(TItem item) {
public void Redeem(TItem item) {
// Call Recycle() when the object is redeemed (instead of when it leaves
// Call Recycle() when the object is redeemed (instead of when it leaves // the pool again) in order to eliminate any references the object may hold
// the pool again) in order to eliminate any references the object may hold // to other objects.
// to other objects. this.recycleDelegate(item);
this.recycleDelegate(item);
lock(this) {
lock(this) { if(this.items.Count < this.capacity) {
if(this.items.Count < this.capacity) { this.items.Enqueue(item);
this.items.Enqueue(item); }
} }
}
}
}
/// <summary>Number of objects the pool can retain</summary>
/// <summary>Number of objects the pool can retain</summary> /// <remarks>
/// <remarks> /// Changing this value causes the pool to be emtpied. It is recommended that
/// Changing this value causes the pool to be emtpied. It is recommended that /// you only read the pool's capacity, never change it.
/// you only read the pool's capacity, never change it. /// </remarks>
/// </remarks> public int Capacity {
public int Capacity { get { return this.capacity; }
get { return this.capacity; } set {
set { this.capacity = value;
this.capacity = value; this.items = new Queue<TItem>(value);
this.items = new Queue<TItem>(value); }
} }
}
/// <summary>
/// <summary> /// Calls the Recycle() method on an objects if it implements
/// Calls the Recycle() method on an objects if it implements /// the IRecyclable interface
/// the IRecyclable interface /// </summary>
/// </summary> /// <param name="item">
/// <param name="item"> /// Object whose Recycle() method will be called if supported by the object
/// Object whose Recycle() method will be called if supported by the object /// </param>
/// </param> private static void callRecycleIfSupported(TItem item) {
private static void callRecycleIfSupported(TItem item) { IRecyclable recycleable = item as IRecyclable;
IRecyclable recycleable = item as IRecyclable; if(recycleable != null) {
if(recycleable != null) { recycleable.Recycle();
recycleable.Recycle(); }
} }
}
/// <summary>Objects being retained for recycling</summary>
/// <summary>Objects being retained for recycling</summary> private Queue<TItem> items;
private Queue<TItem> items; /// <summary>Capacity of the pool</summary>
/// <summary>Capacity of the pool</summary> /// <remarks>
/// <remarks> /// Required because the Queue class doesn't allow this value to be retrieved
/// Required because the Queue class doesn't allow this value to be retrieved /// </remarks>
/// </remarks> private int capacity;
private int capacity; /// <summary>Delegate used to create new instances of the pool's type</summary>
/// <summary>Delegate used to create new instances of the pool's type</summary> private Func<TItem> createNewDelegate;
private Func<TItem> createNewDelegate; /// <summary>Delegate used to recycle instances</summary>
/// <summary>Delegate used to recycle instances</summary> private Action<TItem> recycleDelegate;
private Action<TItem> recycleDelegate;
}
}
} // namespace Nuclex.Support.Collections
} // namespace Nuclex.Support.Collections

View File

@ -1,100 +1,99 @@
#region CPL License #region Apache License 2.0
/* /*
Nuclex Framework Nuclex .NET Framework
Copyright (C) 2002-2017 Nuclex Development Labs Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
This library is free software; you can redistribute it and/or Licensed under the Apache License, Version 2.0 (the "License");
modify it under the terms of the IBM Common Public License as you may not use this file except in compliance with the License.
published by the IBM Corporation; either version 1.0 of the You may obtain a copy of the License at
License, or (at your option) any later version.
http://www.apache.org/licenses/LICENSE-2.0
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of Unless required by applicable law or agreed to in writing, software
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the distributed under the License is distributed on an "AS IS" BASIS,
IBM Common Public License for more details. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
You should have received a copy of the IBM Common Public limitations under the License.
License along with this library */
*/ #endregion // Apache License 2.0
#endregion
#if UNITTEST
#if UNITTEST
using System;
using System; using System.Collections.Generic;
using System.Collections.Generic;
using NUnit.Framework;
using NUnit.Framework;
namespace Nuclex.Support.Collections {
namespace Nuclex.Support.Collections {
/// <summary>Unit Test for the Priority/Item pair class</summary>
/// <summary>Unit Test for the Priority/Item pair class</summary> [TestFixture]
[TestFixture] internal class PriorityItemPairTest {
internal class PriorityItemPairTest {
#region class ToStringNullReturner
#region class ToStringNullReturner
/// <summary>Test class in which ToString() can return null</summary>
/// <summary>Test class in which ToString() can return null</summary> private class ToStringNullReturner {
private class ToStringNullReturner {
/// <summary>
/// <summary> /// Returns a System.String that represents the current System.Object
/// Returns a System.String that represents the current System.Object /// </summary>
/// </summary> /// <returns>A System.String that represents the current System.Object</returns>
/// <returns>A System.String that represents the current System.Object</returns> public override string ToString() { return null; }
public override string ToString() { return null; }
}
}
#endregion // class ToStringNullReturner
#endregion // class ToStringNullReturner
/// <summary>Tests whether the pair's default constructor works</summary>
/// <summary>Tests whether the pair's default constructor works</summary> [Test]
[Test] public void TestDefaultConstructor() {
public void TestDefaultConstructor() { new PriorityItemPair<int, string>();
new PriorityItemPair<int, string>(); }
}
/// <summary>Tests whether the priority can be retrieved from the pair</summary>
/// <summary>Tests whether the priority can be retrieved from the pair</summary> [Test]
[Test] public void TestPriorityRetrieval() {
public void TestPriorityRetrieval() { PriorityItemPair<int, string> testPair = new PriorityItemPair<int, string>(
PriorityItemPair<int, string> testPair = new PriorityItemPair<int, string>( 12345, "hello world"
12345, "hello world" );
);
Assert.AreEqual(12345, testPair.Priority);
Assert.AreEqual(12345, testPair.Priority); }
}
/// <summary>Tests whether the item can be retrieved from the pair</summary>
/// <summary>Tests whether the item can be retrieved from the pair</summary> [Test]
[Test] public void TestItemRetrieval() {
public void TestItemRetrieval() { PriorityItemPair<int, string> testPair = new PriorityItemPair<int, string>(
PriorityItemPair<int, string> testPair = new PriorityItemPair<int, string>( 12345, "hello world"
12345, "hello world" );
);
Assert.AreEqual("hello world", testPair.Item);
Assert.AreEqual("hello world", testPair.Item); }
}
/// <summary>Tests whether the ToString() methods works with valid strings</summary>
/// <summary>Tests whether the ToString() methods works with valid strings</summary> [Test]
[Test] public void TestToStringWithValidStrings() {
public void TestToStringWithValidStrings() { PriorityItemPair<string, string> testPair = new PriorityItemPair<string, string>(
PriorityItemPair<string, string> testPair = new PriorityItemPair<string, string>( "hello", "world"
"hello", "world" );
);
Assert.AreEqual("[hello, world]", testPair.ToString());
Assert.AreEqual("[hello, world]", testPair.ToString()); }
}
/// <summary>Tests whether the ToString() methods works with null strings</summary>
/// <summary>Tests whether the ToString() methods works with null strings</summary> [Test]
[Test] public void TestToStringWithNullStrings() {
public void TestToStringWithNullStrings() { PriorityItemPair<ToStringNullReturner, ToStringNullReturner> testPair =
PriorityItemPair<ToStringNullReturner, ToStringNullReturner> testPair = new PriorityItemPair<ToStringNullReturner, ToStringNullReturner>(
new PriorityItemPair<ToStringNullReturner, ToStringNullReturner>( new ToStringNullReturner(), new ToStringNullReturner()
new ToStringNullReturner(), new ToStringNullReturner() );
);
Assert.AreEqual("[, ]", testPair.ToString());
Assert.AreEqual("[, ]", testPair.ToString()); }
}
}
}
} // namespace Nuclex.Support.Collections
} // namespace Nuclex.Support.Collections
#endif // UNITTEST
#endif // UNITTEST

View File

@ -1,75 +1,74 @@
#region CPL License #region Apache License 2.0
/* /*
Nuclex Framework Nuclex .NET Framework
Copyright (C) 2002-2017 Nuclex Development Labs Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
This library is free software; you can redistribute it and/or Licensed under the Apache License, Version 2.0 (the "License");
modify it under the terms of the IBM Common Public License as you may not use this file except in compliance with the License.
published by the IBM Corporation; either version 1.0 of the You may obtain a copy of the License at
License, or (at your option) any later version.
http://www.apache.org/licenses/LICENSE-2.0
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of Unless required by applicable law or agreed to in writing, software
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the distributed under the License is distributed on an "AS IS" BASIS,
IBM Common Public License for more details. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
You should have received a copy of the IBM Common Public limitations under the License.
License along with this library */
*/ #endregion // Apache License 2.0
#endregion
using System;
using System; using System.Text;
using System.Text;
namespace Nuclex.Support.Collections {
namespace Nuclex.Support.Collections {
/// <summary>An pair of a priority and an item</summary>
/// <summary>An pair of a priority and an item</summary> public struct PriorityItemPair<TPriority, TItem> {
public struct PriorityItemPair<TPriority, TItem> {
/// <summary>Initializes a new priority / item pair</summary>
/// <summary>Initializes a new priority / item pair</summary> /// <param name="priority">Priority of the item in the pair</param>
/// <param name="priority">Priority of the item in the pair</param> /// <param name="item">Item to be stored in the pair</param>
/// <param name="item">Item to be stored in the pair</param> public PriorityItemPair(TPriority priority, TItem item) {
public PriorityItemPair(TPriority priority, TItem item) { this.Priority = priority;
this.Priority = priority; this.Item = item;
this.Item = item; }
}
/// <summary>Priority assigned to this priority / item pair</summary>
/// <summary>Priority assigned to this priority / item pair</summary> public TPriority Priority;
public TPriority Priority; /// <summary>Item contained in this priority / item pair</summary>
/// <summary>Item contained in this priority / item pair</summary> public TItem Item;
public TItem Item;
/// <summary>Converts the priority / item pair into a string</summary>
/// <summary>Converts the priority / item pair into a string</summary> /// <returns>A string describing the priority / item pair</returns>
/// <returns>A string describing the priority / item pair</returns> public override string ToString() {
public override string ToString() { int length = 4;
int length = 4;
// Convert the priority value into a string or use the empty string
// Convert the priority value into a string or use the empty string // constant if the ToString() overload returns null
// constant if the ToString() overload returns null string priorityString = this.Priority.ToString();
string priorityString = this.Priority.ToString(); if(priorityString != null)
if(priorityString != null) length += priorityString.Length;
length += priorityString.Length; else
else priorityString = string.Empty;
priorityString = string.Empty;
// Convert the item value into a string or use the empty string
// Convert the item value into a string or use the empty string // constant if the ToString() overload returns null
// constant if the ToString() overload returns null string itemString = this.Item.ToString();
string itemString = this.Item.ToString(); if(itemString != null)
if(itemString != null) length += itemString.Length;
length += itemString.Length; else
else itemString = string.Empty;
itemString = string.Empty;
// Concatenate priority and item into a single string
// Concatenate priority and item into a single string StringBuilder builder = new StringBuilder(length);
StringBuilder builder = new StringBuilder(length); builder.Append('[');
builder.Append('['); builder.Append(priorityString);
builder.Append(priorityString); builder.Append(", ");
builder.Append(", "); builder.Append(itemString);
builder.Append(itemString); builder.Append(']');
builder.Append(']'); return builder.ToString();
return builder.ToString(); }
}
}
}
} // namespace Nuclex.Support.Collections
} // namespace Nuclex.Support.Collections

View File

@ -1,158 +1,157 @@
#region CPL License #region Apache License 2.0
/* /*
Nuclex Framework Nuclex .NET Framework
Copyright (C) 2002-2017 Nuclex Development Labs Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
This library is free software; you can redistribute it and/or Licensed under the Apache License, Version 2.0 (the "License");
modify it under the terms of the IBM Common Public License as you may not use this file except in compliance with the License.
published by the IBM Corporation; either version 1.0 of the You may obtain a copy of the License at
License, or (at your option) any later version.
http://www.apache.org/licenses/LICENSE-2.0
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of Unless required by applicable law or agreed to in writing, software
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the distributed under the License is distributed on an "AS IS" BASIS,
IBM Common Public License for more details. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
You should have received a copy of the IBM Common Public limitations under the License.
License along with this library */
*/ #endregion // Apache License 2.0
#endregion
using System;
using System; using System.Collections.Generic;
using System.Collections.Generic;
#if UNITTEST
#if UNITTEST
using NUnit.Framework;
using NUnit.Framework;
namespace Nuclex.Support.Collections {
namespace Nuclex.Support.Collections {
/// <summary>Unit Test for the priority queue class</summary>
/// <summary>Unit Test for the priority queue class</summary> [TestFixture]
[TestFixture] internal class PriorityQueueTest {
internal class PriorityQueueTest {
#region class FloatComparer
#region class FloatComparer
/// <summary>Comparer for two floating point values</summary>
/// <summary>Comparer for two floating point values</summary> private class FloatComparer : IComparer<float> {
private class FloatComparer : IComparer<float> {
/// <summary>The default instance of this comparer</summary>
/// <summary>The default instance of this comparer</summary> public static readonly FloatComparer Default = new FloatComparer();
public static readonly FloatComparer Default = new FloatComparer();
/// <summary>Compares two floating points against each other</summary>
/// <summary>Compares two floating points against each other</summary> /// <param name="left">First float to compare</param>
/// <param name="left">First float to compare</param> /// <param name="right">Second float to compare</param>
/// <param name="right">Second float to compare</param> /// <returns>The relationship of the two floats to each other</returns>
/// <returns>The relationship of the two floats to each other</returns> public int Compare(float left, float right) {
public int Compare(float left, float right) { return Math.Sign(left - right);
return Math.Sign(left - right); }
}
}
}
#endregion // class FloatComparer
#endregion // class FloatComparer
/// <summary>Tests to ensure the count property is properly updated</summary>
/// <summary>Tests to ensure the count property is properly updated</summary> [Test]
[Test] public void TestCount() {
public void TestCount() { PriorityQueue<float> testQueue = new PriorityQueue<float>(FloatComparer.Default);
PriorityQueue<float> testQueue = new PriorityQueue<float>(FloatComparer.Default);
Assert.AreEqual(0, testQueue.Count);
Assert.AreEqual(0, testQueue.Count); testQueue.Enqueue(12.34f);
testQueue.Enqueue(12.34f); Assert.AreEqual(1, testQueue.Count);
Assert.AreEqual(1, testQueue.Count); testQueue.Enqueue(56.78f);
testQueue.Enqueue(56.78f); Assert.AreEqual(2, testQueue.Count);
Assert.AreEqual(2, testQueue.Count); testQueue.Dequeue();
testQueue.Dequeue(); Assert.AreEqual(1, testQueue.Count);
Assert.AreEqual(1, testQueue.Count); testQueue.Enqueue(9.0f);
testQueue.Enqueue(9.0f); Assert.AreEqual(2, testQueue.Count);
Assert.AreEqual(2, testQueue.Count); testQueue.Clear();
testQueue.Clear(); Assert.AreEqual(0, testQueue.Count);
Assert.AreEqual(0, testQueue.Count); }
}
/// <summary>Tests to ensure that the priority collection actually sorts items</summary>
/// <summary>Tests to ensure that the priority collection actually sorts items</summary> [Test]
[Test] public void TestOrdering() {
public void TestOrdering() { PriorityQueue<float> testQueue = new PriorityQueue<float>(FloatComparer.Default);
PriorityQueue<float> testQueue = new PriorityQueue<float>(FloatComparer.Default);
testQueue.Enqueue(1.0f);
testQueue.Enqueue(1.0f); testQueue.Enqueue(9.0f);
testQueue.Enqueue(9.0f); testQueue.Enqueue(2.0f);
testQueue.Enqueue(2.0f); testQueue.Enqueue(8.0f);
testQueue.Enqueue(8.0f); testQueue.Enqueue(3.0f);
testQueue.Enqueue(3.0f); testQueue.Enqueue(7.0f);
testQueue.Enqueue(7.0f); testQueue.Enqueue(4.0f);
testQueue.Enqueue(4.0f); testQueue.Enqueue(6.0f);
testQueue.Enqueue(6.0f); testQueue.Enqueue(5.0f);
testQueue.Enqueue(5.0f);
Assert.AreEqual(9.0f, testQueue.Dequeue());
Assert.AreEqual(9.0f, testQueue.Dequeue()); Assert.AreEqual(8.0f, testQueue.Dequeue());
Assert.AreEqual(8.0f, testQueue.Dequeue()); Assert.AreEqual(7.0f, testQueue.Dequeue());
Assert.AreEqual(7.0f, testQueue.Dequeue()); Assert.AreEqual(6.0f, testQueue.Dequeue());
Assert.AreEqual(6.0f, testQueue.Dequeue()); Assert.AreEqual(5.0f, testQueue.Dequeue());
Assert.AreEqual(5.0f, testQueue.Dequeue()); Assert.AreEqual(4.0f, testQueue.Dequeue());
Assert.AreEqual(4.0f, testQueue.Dequeue()); Assert.AreEqual(3.0f, testQueue.Dequeue());
Assert.AreEqual(3.0f, testQueue.Dequeue()); Assert.AreEqual(2.0f, testQueue.Dequeue());
Assert.AreEqual(2.0f, testQueue.Dequeue()); Assert.AreEqual(1.0f, testQueue.Dequeue());
Assert.AreEqual(1.0f, testQueue.Dequeue()); }
}
#if DEBUG
#if DEBUG /// <summary>
/// <summary> /// Tests whether the priority queue's enumerators are invalidated when the queue's
/// Tests whether the priority queue's enumerators are invalidated when the queue's /// contents are modified
/// contents are modified /// </summary>
/// </summary> [Test]
[Test] public void TestEnumeratorInvalidationOnModify() {
public void TestEnumeratorInvalidationOnModify() { PriorityQueue<int> testQueue = new PriorityQueue<int>();
PriorityQueue<int> testQueue = new PriorityQueue<int>(); IEnumerator<int> testQueueEnumerator = testQueue.GetEnumerator();
IEnumerator<int> testQueueEnumerator = testQueue.GetEnumerator();
testQueue.Enqueue(123);
testQueue.Enqueue(123);
Assert.Throws<InvalidOperationException>(
Assert.Throws<InvalidOperationException>( delegate() { testQueueEnumerator.MoveNext(); }
delegate() { testQueueEnumerator.MoveNext(); } );
); }
} #endif
#endif
/// <summary>
/// <summary> /// Verifies that an exception is thrown when Peek() is called on an empty queue
/// Verifies that an exception is thrown when Peek() is called on an empty queue /// </summary>
/// </summary> [Test]
[Test] public void TestPeekEmptyQueue() {
public void TestPeekEmptyQueue() { PriorityQueue<int> testQueue = new PriorityQueue<int>();
PriorityQueue<int> testQueue = new PriorityQueue<int>(); Assert.Throws<InvalidOperationException>(
Assert.Throws<InvalidOperationException>( delegate() { testQueue.Peek(); }
delegate() { testQueue.Peek(); } );
); }
}
/// <summary>
/// <summary> /// Verifies that an exception is thrown when Dequeue() is called on an empty queue
/// Verifies that an exception is thrown when Dequeue() is called on an empty queue /// </summary>
/// </summary> [Test]
[Test] public void TestDequeueEmptyQueue() {
public void TestDequeueEmptyQueue() { PriorityQueue<int> testQueue = new PriorityQueue<int>();
PriorityQueue<int> testQueue = new PriorityQueue<int>(); Assert.Throws<InvalidOperationException>(
Assert.Throws<InvalidOperationException>( delegate() { testQueue.Dequeue(); }
delegate() { testQueue.Dequeue(); } );
); }
}
/// <summary>
/// <summary> /// Verifies that the priority queue can handle large amounts of data
/// Verifies that the priority queue can handle large amounts of data /// </summary>
/// </summary> [Test]
[Test] public void TestLargeQueue() {
public void TestLargeQueue() { PriorityQueue<int> testQueue = new PriorityQueue<int>();
PriorityQueue<int> testQueue = new PriorityQueue<int>(); List<int> testList = new List<int>();
List<int> testList = new List<int>();
for(int index = 0; index < 1000; ++index) {
for(int index = 0; index < 1000; ++index) { testQueue.Enqueue(index * 2);
testQueue.Enqueue(index * 2); testList.Add(index * 2);
testList.Add(index * 2); }
}
CollectionAssert.AreEquivalent(testList, testQueue);
CollectionAssert.AreEquivalent(testList, testQueue); }
}
}
}
} // namespace Nuclex.Support.Collections
} // namespace Nuclex.Support.Collections
#endif // UNITTEST
#endif // UNITTEST

View File

@ -1,285 +1,284 @@
#region CPL License #region Apache License 2.0
/* /*
Nuclex Framework Nuclex .NET Framework
Copyright (C) 2002-2017 Nuclex Development Labs Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
This library is free software; you can redistribute it and/or Licensed under the Apache License, Version 2.0 (the "License");
modify it under the terms of the IBM Common Public License as you may not use this file except in compliance with the License.
published by the IBM Corporation; either version 1.0 of the You may obtain a copy of the License at
License, or (at your option) any later version.
http://www.apache.org/licenses/LICENSE-2.0
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of Unless required by applicable law or agreed to in writing, software
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the distributed under the License is distributed on an "AS IS" BASIS,
IBM Common Public License for more details. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
You should have received a copy of the IBM Common Public limitations under the License.
License along with this library */
*/ #endregion // Apache License 2.0
#endregion
using System;
using System; using System.Collections.Generic;
using System.Collections.Generic; using System.Collections;
using System.Collections;
namespace Nuclex.Support.Collections {
namespace Nuclex.Support.Collections {
/// <summary>Queue that dequeues items in order of their priority</summary>
/// <summary>Queue that dequeues items in order of their priority</summary> public class PriorityQueue<TItem> : ICollection, IEnumerable<TItem> {
public class PriorityQueue<TItem> : ICollection, IEnumerable<TItem> {
#region class Enumerator
#region class Enumerator
/// <summary>Enumerates all items contained in a priority queue</summary>
/// <summary>Enumerates all items contained in a priority queue</summary> private class Enumerator : IEnumerator<TItem> {
private class Enumerator : IEnumerator<TItem> {
/// <summary>Initializes a new priority queue enumerator</summary>
/// <summary>Initializes a new priority queue enumerator</summary> /// <param name="priorityQueue">Priority queue to be enumerated</param>
/// <param name="priorityQueue">Priority queue to be enumerated</param> public Enumerator(PriorityQueue<TItem> priorityQueue) {
public Enumerator(PriorityQueue<TItem> priorityQueue) { this.priorityQueue = priorityQueue;
this.priorityQueue = priorityQueue; Reset();
Reset(); }
}
/// <summary>Resets the enumerator to its initial state</summary>
/// <summary>Resets the enumerator to its initial state</summary> public void Reset() {
public void Reset() { this.index = -1;
this.index = -1; #if DEBUG
#if DEBUG this.expectedVersion = this.priorityQueue.version;
this.expectedVersion = this.priorityQueue.version; #endif
#endif }
}
/// <summary>The current item being enumerated</summary>
/// <summary>The current item being enumerated</summary> TItem IEnumerator<TItem>.Current {
TItem IEnumerator<TItem>.Current { get {
get { #if DEBUG
#if DEBUG checkVersion();
checkVersion(); #endif
#endif return this.priorityQueue.heap[index];
return this.priorityQueue.heap[index]; }
} }
}
/// <summary>Moves to the next item in the priority queue</summary>
/// <summary>Moves to the next item in the priority queue</summary> /// <returns>True if a next item was found, false if the end has been reached</returns>
/// <returns>True if a next item was found, false if the end has been reached</returns> public bool MoveNext() {
public bool MoveNext() { #if DEBUG
#if DEBUG checkVersion();
checkVersion(); #endif
#endif if(this.index + 1 == this.priorityQueue.count)
if(this.index + 1 == this.priorityQueue.count) return false;
return false;
++this.index;
++this.index;
return true;
return true; }
}
/// <summary>Releases all resources used by the enumerator</summary>
/// <summary>Releases all resources used by the enumerator</summary> public void Dispose() { }
public void Dispose() { }
#if DEBUG
#if DEBUG /// <summary>Ensures that the priority queue has not changed</summary>
/// <summary>Ensures that the priority queue has not changed</summary> private void checkVersion() {
private void checkVersion() { if(this.expectedVersion != this.priorityQueue.version)
if(this.expectedVersion != this.priorityQueue.version) throw new InvalidOperationException("Priority queue has been modified");
throw new InvalidOperationException("Priority queue has been modified"); }
} #endif
#endif
/// <summary>The current item being enumerated</summary>
/// <summary>The current item being enumerated</summary> object IEnumerator.Current {
object IEnumerator.Current { get {
get { #if DEBUG
#if DEBUG checkVersion();
checkVersion(); #endif
#endif return this.priorityQueue.heap[index];
return this.priorityQueue.heap[index]; }
} }
}
/// <summary>Index of the current item in the priority queue</summary>
/// <summary>Index of the current item in the priority queue</summary> private int index;
private int index; /// <summary>The priority queue whose items this instance enumerates</summary>
/// <summary>The priority queue whose items this instance enumerates</summary> private PriorityQueue<TItem> priorityQueue;
private PriorityQueue<TItem> priorityQueue; #if DEBUG
#if DEBUG /// <summary>Expected version of the priority queue</summary>
/// <summary>Expected version of the priority queue</summary> private int expectedVersion;
private int expectedVersion; #endif
#endif
}
}
#endregion // class Enumerator
#endregion // class Enumerator
/// <summary>
/// <summary> /// Initializes a new priority queue using IComparable for comparing items
/// Initializes a new priority queue using IComparable for comparing items /// </summary>
/// </summary> public PriorityQueue() : this(Comparer<TItem>.Default) { }
public PriorityQueue() : this(Comparer<TItem>.Default) { }
/// <summary>Initializes a new priority queue</summary>
/// <summary>Initializes a new priority queue</summary> /// <param name="comparer">Comparer to use for ordering the items</param>
/// <param name="comparer">Comparer to use for ordering the items</param> public PriorityQueue(IComparer<TItem> comparer) {
public PriorityQueue(IComparer<TItem> comparer) { this.comparer = comparer;
this.comparer = comparer; this.capacity = 15; // 15 is equal to 4 complete levels
this.capacity = 15; // 15 is equal to 4 complete levels this.heap = new TItem[this.capacity];
this.heap = new TItem[this.capacity]; }
}
/// <summary>Returns the topmost item in the queue without dequeueing it</summary>
/// <summary>Returns the topmost item in the queue without dequeueing it</summary> /// <returns>The topmost item in the queue</returns>
/// <returns>The topmost item in the queue</returns> public TItem Peek() {
public TItem Peek() { if(this.count == 0) {
if(this.count == 0) { throw new InvalidOperationException("No items queued");
throw new InvalidOperationException("No items queued"); }
}
return this.heap[0];
return this.heap[0]; }
}
/// <summary>Takes the item with the highest priority off from the queue</summary>
/// <summary>Takes the item with the highest priority off from the queue</summary> /// <returns>The item with the highest priority in the list</returns>
/// <returns>The item with the highest priority in the list</returns> /// <exception cref="InvalidOperationException">When the queue is empty</exception>
/// <exception cref="InvalidOperationException">When the queue is empty</exception> public TItem Dequeue() {
public TItem Dequeue() { if(this.count == 0) {
if(this.count == 0) { throw new InvalidOperationException("No items available to dequeue");
throw new InvalidOperationException("No items available to dequeue"); }
}
TItem result = this.heap[0];
TItem result = this.heap[0]; --this.count;
--this.count; trickleDown(0, this.heap[this.count]);
trickleDown(0, this.heap[this.count]); #if DEBUG
#if DEBUG ++this.version;
++this.version; #endif
#endif return result;
return result; }
}
/// <summary>Puts an item into the priority queue</summary>
/// <summary>Puts an item into the priority queue</summary> /// <param name="item">Item to be queued</param>
/// <param name="item">Item to be queued</param> public void Enqueue(TItem item) {
public void Enqueue(TItem item) { if(this.count == capacity)
if(this.count == capacity) growHeap();
growHeap();
++this.count;
++this.count; bubbleUp(this.count - 1, item);
bubbleUp(this.count - 1, item); #if DEBUG
#if DEBUG ++this.version;
++this.version; #endif
#endif }
}
/// <summary>Removes all items from the priority queue</summary>
/// <summary>Removes all items from the priority queue</summary> public void Clear() {
public void Clear() { this.count = 0;
this.count = 0; #if DEBUG
#if DEBUG ++this.version;
++this.version; #endif
#endif }
}
/// <summary>Total number of items in the priority queue</summary>
/// <summary>Total number of items in the priority queue</summary> public int Count {
public int Count { get { return this.count; }
get { return this.count; } }
}
/// <summary>Copies the contents of the priority queue into an array</summary>
/// <summary>Copies the contents of the priority queue into an array</summary> /// <param name="array">Array to copy the priority queue into</param>
/// <param name="array">Array to copy the priority queue into</param> /// <param name="index">Starting index for the destination array</param>
/// <param name="index">Starting index for the destination array</param> public void CopyTo(Array array, int index) {
public void CopyTo(Array array, int index) { Array.Copy(this.heap, 0, array, index, this.count);
Array.Copy(this.heap, 0, array, index, this.count); }
}
/// <summary>
/// <summary> /// Obtains an object that can be used to synchronize accesses to the priority queue
/// Obtains an object that can be used to synchronize accesses to the priority queue /// from different threads
/// from different threads /// </summary>
/// </summary> public object SyncRoot {
public object SyncRoot { get { return this; }
get { return this; } }
}
/// <summary>Whether operations performed on this priority queue are thread safe</summary>
/// <summary>Whether operations performed on this priority queue are thread safe</summary> public bool IsSynchronized {
public bool IsSynchronized { get { return false; }
get { return false; } }
}
/// <summary>Returns a typesafe enumerator for the priority queue</summary>
/// <summary>Returns a typesafe enumerator for the priority queue</summary> /// <returns>A new enumerator for the priority queue</returns>
/// <returns>A new enumerator for the priority queue</returns> public IEnumerator<TItem> GetEnumerator() {
public IEnumerator<TItem> GetEnumerator() { return new Enumerator(this);
return new Enumerator(this); }
}
/// <summary>Moves an item upwards in the heap tree</summary>
/// <summary>Moves an item upwards in the heap tree</summary> /// <param name="index">Index of the item to be moved</param>
/// <param name="index">Index of the item to be moved</param> /// <param name="item">Item to be moved</param>
/// <param name="item">Item to be moved</param> private void bubbleUp(int index, TItem item) {
private void bubbleUp(int index, TItem item) { int parent = getParent(index);
int parent = getParent(index);
// Note: (index > 0) means there is a parent
// Note: (index > 0) means there is a parent while((index > 0) && (this.comparer.Compare(this.heap[parent], item) < 0)) {
while((index > 0) && (this.comparer.Compare(this.heap[parent], item) < 0)) { this.heap[index] = this.heap[parent];
this.heap[index] = this.heap[parent]; index = parent;
index = parent; parent = getParent(index);
parent = getParent(index); }
}
this.heap[index] = item;
this.heap[index] = item; }
}
/// <summary>Move the item downwards in the heap tree</summary>
/// <summary>Move the item downwards in the heap tree</summary> /// <param name="index">Index of the item to be moved</param>
/// <param name="index">Index of the item to be moved</param> /// <param name="item">Item to be moved</param>
/// <param name="item">Item to be moved</param> private void trickleDown(int index, TItem item) {
private void trickleDown(int index, TItem item) { int child = getLeftChild(index);
int child = getLeftChild(index);
while(child < this.count) {
while(child < this.count) {
bool needsToBeMoved =
bool needsToBeMoved = ((child + 1) < this.count) &&
((child + 1) < this.count) && (this.comparer.Compare(heap[child], this.heap[child + 1]) < 0);
(this.comparer.Compare(heap[child], this.heap[child + 1]) < 0);
if(needsToBeMoved)
if(needsToBeMoved) ++child;
++child;
this.heap[index] = this.heap[child];
this.heap[index] = this.heap[child]; index = child;
index = child; child = getLeftChild(index);
child = getLeftChild(index);
}
}
bubbleUp(index, item);
bubbleUp(index, item); }
}
/// <summary>Obtains the left child item in the heap tree</summary>
/// <summary>Obtains the left child item in the heap tree</summary> /// <param name="index">Index of the item whose left child to return</param>
/// <param name="index">Index of the item whose left child to return</param> /// <returns>The left child item of the provided parent item</returns>
/// <returns>The left child item of the provided parent item</returns> private int getLeftChild(int index) {
private int getLeftChild(int index) { return (index * 2) + 1;
return (index * 2) + 1; }
}
/// <summary>Calculates the parent entry of the item on the heap</summary>
/// <summary>Calculates the parent entry of the item on the heap</summary> /// <param name="index">Index of the item whose parent to calculate</param>
/// <param name="index">Index of the item whose parent to calculate</param> /// <returns>The index of the parent to the specified item</returns>
/// <returns>The index of the parent to the specified item</returns> private int getParent(int index) {
private int getParent(int index) { return (index - 1) / 2;
return (index - 1) / 2; }
}
/// <summary>Increases the size of the priority collection's heap</summary>
/// <summary>Increases the size of the priority collection's heap</summary> private void growHeap() {
private void growHeap() { this.capacity = (capacity * 2) + 1;
this.capacity = (capacity * 2) + 1;
TItem[] newHeap = new TItem[this.capacity];
TItem[] newHeap = new TItem[this.capacity]; Array.Copy(this.heap, 0, newHeap, 0, this.count);
Array.Copy(this.heap, 0, newHeap, 0, this.count); this.heap = newHeap;
this.heap = newHeap; }
}
/// <summary>Returns an enumerator for the priority queue</summary>
/// <summary>Returns an enumerator for the priority queue</summary> /// <returns>A new enumerator for the priority queue</returns>
/// <returns>A new enumerator for the priority queue</returns> IEnumerator IEnumerable.GetEnumerator() {
IEnumerator IEnumerable.GetEnumerator() { return new Enumerator(this);
return new Enumerator(this); }
}
/// <summary>Comparer used to order the items in the priority queue</summary>
/// <summary>Comparer used to order the items in the priority queue</summary> private IComparer<TItem> comparer;
private IComparer<TItem> comparer; /// <summary>Total number of items in the priority queue</summary>
/// <summary>Total number of items in the priority queue</summary> private int count;
private int count; /// <summary>Available space in the priority queue</summary>
/// <summary>Available space in the priority queue</summary> private int capacity;
private int capacity; /// <summary>Tree containing the items in the priority queue</summary>
/// <summary>Tree containing the items in the priority queue</summary> private TItem[] heap;
private TItem[] heap; #if DEBUG
#if DEBUG /// <summary>Incremented whenever the priority queue is modified</summary>
/// <summary>Incremented whenever the priority queue is modified</summary> private int version;
private int version; #endif
#endif
}
}
} // namespace Nuclex.Support.Collections
} // namespace Nuclex.Support.Collections

View File

@ -1,163 +1,162 @@
#region CPL License #region Apache License 2.0
/* /*
Nuclex Framework Nuclex .NET Framework
Copyright (C) 2002-2017 Nuclex Development Labs Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
This library is free software; you can redistribute it and/or Licensed under the Apache License, Version 2.0 (the "License");
modify it under the terms of the IBM Common Public License as you may not use this file except in compliance with the License.
published by the IBM Corporation; either version 1.0 of the You may obtain a copy of the License at
License, or (at your option) any later version.
http://www.apache.org/licenses/LICENSE-2.0
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of Unless required by applicable law or agreed to in writing, software
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the distributed under the License is distributed on an "AS IS" BASIS,
IBM Common Public License for more details. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
You should have received a copy of the IBM Common Public limitations under the License.
License along with this library */
*/ #endregion // Apache License 2.0
#endregion
using System;
using System; using System.Collections;
using System.Collections; using System.Collections.Generic;
using System.Collections.Generic;
#if UNITTEST
#if UNITTEST
using NUnit.Framework;
using NUnit.Framework;
namespace Nuclex.Support.Collections {
namespace Nuclex.Support.Collections {
/// <summary>Unit Test for the read only collection wrapper</summary>
/// <summary>Unit Test for the read only collection wrapper</summary> [TestFixture]
[TestFixture] internal class ReadOnlyCollectionTest {
internal class ReadOnlyCollectionTest {
/// <summary>
/// <summary> /// Verifies that the copy constructor of the read only collection works
/// Verifies that the copy constructor of the read only collection works /// </summary>
/// </summary> [Test]
[Test] public void TestCopyConstructor() {
public void TestCopyConstructor() { int[] integers = new int[] { 12, 34, 56, 78 };
int[] integers = new int[] { 12, 34, 56, 78 }; ReadOnlyCollection<int> testCollection = new ReadOnlyCollection<int>(integers);
ReadOnlyCollection<int> testCollection = new ReadOnlyCollection<int>(integers);
CollectionAssert.AreEqual(integers, testCollection);
CollectionAssert.AreEqual(integers, testCollection); }
}
/// <summary>Verifies that the IsReadOnly property returns true</summary>
/// <summary>Verifies that the IsReadOnly property returns true</summary> [Test]
[Test] public void TestIsReadOnly() {
public void TestIsReadOnly() { ReadOnlyCollection<int> testCollection = new ReadOnlyCollection<int>(new int[0]);
ReadOnlyCollection<int> testCollection = new ReadOnlyCollection<int>(new int[0]);
Assert.IsTrue(testCollection.IsReadOnly);
Assert.IsTrue(testCollection.IsReadOnly); }
}
/// <summary>
/// <summary> /// Verifies that the CopyTo() of the read only collection works
/// Verifies that the CopyTo() of the read only collection works /// </summary>
/// </summary> [Test]
[Test] public void TestCopyToArray() {
public void TestCopyToArray() { int[] inputIntegers = new int[] { 12, 34, 56, 78 };
int[] inputIntegers = new int[] { 12, 34, 56, 78 }; ReadOnlyCollection<int> testCollection = new ReadOnlyCollection<int>(inputIntegers);
ReadOnlyCollection<int> testCollection = new ReadOnlyCollection<int>(inputIntegers);
int[] outputIntegers = new int[testCollection.Count];
int[] outputIntegers = new int[testCollection.Count]; testCollection.CopyTo(outputIntegers, 0);
testCollection.CopyTo(outputIntegers, 0);
CollectionAssert.AreEqual(inputIntegers, outputIntegers);
CollectionAssert.AreEqual(inputIntegers, outputIntegers); }
}
/// <summary>
/// <summary> /// Checks whether the Contains() method of the read only collection is able to
/// Checks whether the Contains() method of the read only collection is able to /// determine if the collection contains an item
/// determine if the collection contains an item /// </summary>
/// </summary> [Test]
[Test] public void TestContains() {
public void TestContains() { int[] integers = new int[] { 1234, 6789 };
int[] integers = new int[] { 1234, 6789 }; ReadOnlyCollection<int> testCollection = new ReadOnlyCollection<int>(integers);
ReadOnlyCollection<int> testCollection = new ReadOnlyCollection<int>(integers);
Assert.IsTrue(testCollection.Contains(1234));
Assert.IsTrue(testCollection.Contains(1234)); Assert.IsFalse(testCollection.Contains(4321));
Assert.IsFalse(testCollection.Contains(4321)); }
}
/// <summary>
/// <summary> /// Ensures that the Add() method of the read only collection throws an exception
/// Ensures that the Add() method of the read only collection throws an exception /// </summary>
/// </summary> [Test]
[Test] public void TestThrowOnAdd() {
public void TestThrowOnAdd() { ReadOnlyCollection<int> testCollection = new ReadOnlyCollection<int>(new int[0]);
ReadOnlyCollection<int> testCollection = new ReadOnlyCollection<int>(new int[0]); Assert.Throws<NotSupportedException>(
Assert.Throws<NotSupportedException>( delegate() { (testCollection as ICollection<int>).Add(123); }
delegate() { (testCollection as ICollection<int>).Add(123); } );
); }
}
/// <summary>
/// <summary> /// Ensures that the Remove() method of the read only collection throws an exception
/// Ensures that the Remove() method of the read only collection throws an exception /// </summary>
/// </summary> [Test]
[Test] public void TestThrowOnRemove() {
public void TestThrowOnRemove() { ReadOnlyCollection<int> testCollection = new ReadOnlyCollection<int>(new int[0]);
ReadOnlyCollection<int> testCollection = new ReadOnlyCollection<int>(new int[0]); Assert.Throws<NotSupportedException>(
Assert.Throws<NotSupportedException>( delegate() { (testCollection as ICollection<int>).Remove(123); }
delegate() { (testCollection as ICollection<int>).Remove(123); } );
); }
}
/// <summary>
/// <summary> /// Ensures that the Clear() method of the read only collection throws an exception
/// Ensures that the Clear() method of the read only collection throws an exception /// </summary>
/// </summary> [Test]
[Test] public void TestThrowOnClear() {
public void TestThrowOnClear() { ReadOnlyCollection<int> testCollection = new ReadOnlyCollection<int>(new int[0]);
ReadOnlyCollection<int> testCollection = new ReadOnlyCollection<int>(new int[0]); Assert.Throws<NotSupportedException>(
Assert.Throws<NotSupportedException>( delegate() { (testCollection as ICollection<int>).Clear(); }
delegate() { (testCollection as ICollection<int>).Clear(); } );
); }
}
/// <summary>
/// <summary> /// Tests whether the typesafe enumerator of the read only collection is working
/// Tests whether the typesafe enumerator of the read only collection is working /// </summary>
/// </summary> [Test]
[Test] public void TestTypesafeEnumerator() {
public void TestTypesafeEnumerator() { int[] inputIntegers = new int[] { 12, 34, 56, 78 };
int[] inputIntegers = new int[] { 12, 34, 56, 78 }; ReadOnlyCollection<int> testCollection = new ReadOnlyCollection<int>(inputIntegers);
ReadOnlyCollection<int> testCollection = new ReadOnlyCollection<int>(inputIntegers);
List<int> outputIntegers = new List<int>();
List<int> outputIntegers = new List<int>(); foreach(int value in testCollection) {
foreach(int value in testCollection) { outputIntegers.Add(value);
outputIntegers.Add(value); }
}
CollectionAssert.AreEqual(inputIntegers, outputIntegers);
CollectionAssert.AreEqual(inputIntegers, outputIntegers); }
}
/// <summary>
/// <summary> /// Verifies that the CopyTo() of the read only collection works if invoked via
/// Verifies that the CopyTo() of the read only collection works if invoked via /// the ICollection interface
/// the ICollection interface /// </summary>
/// </summary> [Test]
[Test] public void TestCopyToArrayViaICollection() {
public void TestCopyToArrayViaICollection() { int[] inputIntegers = new int[] { 12, 34, 56, 78 };
int[] inputIntegers = new int[] { 12, 34, 56, 78 }; ReadOnlyCollection<int> testCollection = new ReadOnlyCollection<int>(inputIntegers);
ReadOnlyCollection<int> testCollection = new ReadOnlyCollection<int>(inputIntegers);
int[] outputIntegers = new int[testCollection.Count];
int[] outputIntegers = new int[testCollection.Count]; (testCollection as ICollection).CopyTo(outputIntegers, 0);
(testCollection as ICollection).CopyTo(outputIntegers, 0);
CollectionAssert.AreEqual(inputIntegers, outputIntegers);
CollectionAssert.AreEqual(inputIntegers, outputIntegers); }
}
/// <summary>
/// <summary> /// Verifies that the IsSynchronized property and the SyncRoot property are working
/// Verifies that the IsSynchronized property and the SyncRoot property are working /// </summary>
/// </summary> [Test]
[Test] public void TestSynchronization() {
public void TestSynchronization() { ReadOnlyCollection<int> testCollection = new ReadOnlyCollection<int>(new int[0]);
ReadOnlyCollection<int> testCollection = new ReadOnlyCollection<int>(new int[0]);
if(!(testCollection as ICollection).IsSynchronized) {
if(!(testCollection as ICollection).IsSynchronized) { lock((testCollection as ICollection).SyncRoot) {
lock((testCollection as ICollection).SyncRoot) { Assert.AreEqual(0, testCollection.Count);
Assert.AreEqual(0, testCollection.Count); }
} }
} }
}
}
}
} // namespace Nuclex.Support.Collections
} // namespace Nuclex.Support.Collections
#endif // UNITTEST
#endif // UNITTEST

View File

@ -1,140 +1,139 @@
#region CPL License #region Apache License 2.0
/* /*
Nuclex Framework Nuclex .NET Framework
Copyright (C) 2002-2017 Nuclex Development Labs Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
This library is free software; you can redistribute it and/or Licensed under the Apache License, Version 2.0 (the "License");
modify it under the terms of the IBM Common Public License as you may not use this file except in compliance with the License.
published by the IBM Corporation; either version 1.0 of the You may obtain a copy of the License at
License, or (at your option) any later version.
http://www.apache.org/licenses/LICENSE-2.0
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of Unless required by applicable law or agreed to in writing, software
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the distributed under the License is distributed on an "AS IS" BASIS,
IBM Common Public License for more details. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
You should have received a copy of the IBM Common Public limitations under the License.
License along with this library */
*/ #endregion // Apache License 2.0
#endregion
using System;
using System; using System.Collections;
using System.Collections; using System.Collections.Generic;
using System.Collections.Generic;
namespace Nuclex.Support.Collections {
namespace Nuclex.Support.Collections {
/// <summary>Wraps a Collection and prevents users from modifying it</summary>
/// <summary>Wraps a Collection and prevents users from modifying it</summary> /// <typeparam name="TItem">Type of items to manage in the Collection</typeparam>
/// <typeparam name="TItem">Type of items to manage in the Collection</typeparam> public class ReadOnlyCollection<TItem> :
public class ReadOnlyCollection<TItem> : ICollection<TItem>,
ICollection<TItem>, ICollection {
ICollection {
/// <summary>Initializes a new read-only Collection wrapper</summary>
/// <summary>Initializes a new read-only Collection wrapper</summary> /// <param name="collection">Collection that will be wrapped</param>
/// <param name="collection">Collection that will be wrapped</param> public ReadOnlyCollection(ICollection<TItem> collection) {
public ReadOnlyCollection(ICollection<TItem> collection) { this.typedCollection = collection;
this.typedCollection = collection; this.objectCollection = (collection as ICollection);
this.objectCollection = (collection as ICollection); }
}
/// <summary>Determines whether the List contains the specified item</summary>
/// <summary>Determines whether the List contains the specified item</summary> /// <param name="item">Item that will be checked for</param>
/// <param name="item">Item that will be checked for</param> /// <returns>True if the specified item is contained in the List</returns>
/// <returns>True if the specified item is contained in the List</returns> public bool Contains(TItem item) {
public bool Contains(TItem item) { return this.typedCollection.Contains(item);
return this.typedCollection.Contains(item); }
}
/// <summary>Copies the contents of the List into an array</summary>
/// <summary>Copies the contents of the List into an array</summary> /// <param name="array">Array the List will be copied into</param>
/// <param name="array">Array the List will be copied into</param> /// <param name="arrayIndex">
/// <param name="arrayIndex"> /// Starting index at which to begin filling the destination array
/// Starting index at which to begin filling the destination array /// </param>
/// </param> public void CopyTo(TItem[] array, int arrayIndex) {
public void CopyTo(TItem[] array, int arrayIndex) { this.typedCollection.CopyTo(array, arrayIndex);
this.typedCollection.CopyTo(array, arrayIndex); }
}
/// <summary>The number of items current contained in the List</summary>
/// <summary>The number of items current contained in the List</summary> public int Count {
public int Count { get { return this.typedCollection.Count; }
get { return this.typedCollection.Count; } }
}
/// <summary>Whether the List is write-protected</summary>
/// <summary>Whether the List is write-protected</summary> public bool IsReadOnly {
public bool IsReadOnly { get { return true; }
get { return true; } }
}
/// <summary>Returns a new enumerator over the contents of the List</summary>
/// <summary>Returns a new enumerator over the contents of the List</summary> /// <returns>The new List contents enumerator</returns>
/// <returns>The new List contents enumerator</returns> public IEnumerator<TItem> GetEnumerator() {
public IEnumerator<TItem> GetEnumerator() { return this.typedCollection.GetEnumerator();
return this.typedCollection.GetEnumerator(); }
}
#region ICollection<> implementation
#region ICollection<> implementation
/// <summary>Adds an item to the end of the List</summary>
/// <summary>Adds an item to the end of the List</summary> /// <param name="item">Item that will be added to the List</param>
/// <param name="item">Item that will be added to the List</param> void ICollection<TItem>.Add(TItem item) {
void ICollection<TItem>.Add(TItem item) { throw new NotSupportedException(
throw new NotSupportedException( "Adding items is not supported by the read-only List"
"Adding items is not supported by the read-only List" );
); }
}
/// <summary>Removes all items from the List</summary>
/// <summary>Removes all items from the List</summary> void ICollection<TItem>.Clear() {
void ICollection<TItem>.Clear() { throw new NotSupportedException(
throw new NotSupportedException( "Clearing is not supported by the read-only List"
"Clearing is not supported by the read-only List" );
); }
}
/// <summary>Removes the specified item from the List</summary>
/// <summary>Removes the specified item from the List</summary> /// <param name="item">Item that will be removed from the List</param>
/// <param name="item">Item that will be removed from the List</param> /// <returns>True of the specified item was found in the List and removed</returns>
/// <returns>True of the specified item was found in the List and removed</returns> bool ICollection<TItem>.Remove(TItem item) {
bool ICollection<TItem>.Remove(TItem item) { throw new NotSupportedException(
throw new NotSupportedException( "Removing items is not supported by the read-only List"
"Removing items is not supported by the read-only List" );
); }
}
#endregion
#endregion
#region IEnumerable implementation
#region IEnumerable implementation
/// <summary>Returns a new enumerator over the contents of the List</summary>
/// <summary>Returns a new enumerator over the contents of the List</summary> /// <returns>The new List contents enumerator</returns>
/// <returns>The new List contents enumerator</returns> IEnumerator IEnumerable.GetEnumerator() {
IEnumerator IEnumerable.GetEnumerator() { return this.objectCollection.GetEnumerator();
return this.objectCollection.GetEnumerator(); }
}
#endregion
#endregion
#region ICollection implementation
#region ICollection implementation
/// <summary>Copies the contents of the List into an array</summary>
/// <summary>Copies the contents of the List into an array</summary> /// <param name="array">Array the List will be copied into</param>
/// <param name="array">Array the List will be copied into</param> /// <param name="index">
/// <param name="index"> /// Starting index at which to begin filling the destination array
/// Starting index at which to begin filling the destination array /// </param>
/// </param> void ICollection.CopyTo(Array array, int index) {
void ICollection.CopyTo(Array array, int index) { this.objectCollection.CopyTo(array, index);
this.objectCollection.CopyTo(array, index); }
}
/// <summary>Whether the List is synchronized for multi-threaded usage</summary>
/// <summary>Whether the List is synchronized for multi-threaded usage</summary> bool ICollection.IsSynchronized {
bool ICollection.IsSynchronized { get { return this.objectCollection.IsSynchronized; }
get { return this.objectCollection.IsSynchronized; } }
}
/// <summary>Synchronization root on which the List locks</summary>
/// <summary>Synchronization root on which the List locks</summary> object ICollection.SyncRoot {
object ICollection.SyncRoot { get { return this.objectCollection.SyncRoot; }
get { return this.objectCollection.SyncRoot; } }
}
#endregion
#endregion
/// <summary>The wrapped Collection under its type-safe interface</summary>
/// <summary>The wrapped Collection under its type-safe interface</summary> private ICollection<TItem> typedCollection;
private ICollection<TItem> typedCollection; /// <summary>The wrapped Collection under its object interface</summary>
/// <summary>The wrapped Collection under its object interface</summary> private ICollection objectCollection;
private ICollection objectCollection;
}
}
} // namespace Nuclex.Support.Collections
} // namespace Nuclex.Support.Collections

File diff suppressed because it is too large Load Diff

View File

@ -1,407 +1,406 @@
#region CPL License #region Apache License 2.0
/* /*
Nuclex Framework Nuclex .NET Framework
Copyright (C) 2002-2017 Nuclex Development Labs Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
This library is free software; you can redistribute it and/or Licensed under the Apache License, Version 2.0 (the "License");
modify it under the terms of the IBM Common Public License as you may not use this file except in compliance with the License.
published by the IBM Corporation; either version 1.0 of the You may obtain a copy of the License at
License, or (at your option) any later version.
http://www.apache.org/licenses/LICENSE-2.0
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of Unless required by applicable law or agreed to in writing, software
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the distributed under the License is distributed on an "AS IS" BASIS,
IBM Common Public License for more details. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
You should have received a copy of the IBM Common Public limitations under the License.
License along with this library */
*/ #endregion // Apache License 2.0
#endregion
using System;
using System; using System.Collections.Generic;
using System.Collections.Generic; using System.Collections;
using System.Collections; using System.Runtime.Serialization;
using System.Runtime.Serialization;
namespace Nuclex.Support.Collections {
namespace Nuclex.Support.Collections {
/// <summary>Wraps a dictionary and prevents users from modifying it</summary>
/// <summary>Wraps a dictionary and prevents users from modifying it</summary> /// <typeparam name="KeyType">Type of the keys used in the dictionary</typeparam>
/// <typeparam name="KeyType">Type of the keys used in the dictionary</typeparam> /// <typeparam name="ValueType">Type of the values used in the dictionary</typeparam>
/// <typeparam name="ValueType">Type of the values used in the dictionary</typeparam> #if !NO_SERIALIZATION
#if !NO_SERIALIZATION [Serializable]
[Serializable] #endif
#endif public class ReadOnlyDictionary<KeyType, ValueType> :
public class ReadOnlyDictionary<KeyType, ValueType> : #if !NO_SERIALIZATION
#if !NO_SERIALIZATION ISerializable,
ISerializable, IDeserializationCallback,
IDeserializationCallback, #endif
#endif IDictionary,
IDictionary, IDictionary<KeyType, ValueType> {
IDictionary<KeyType, ValueType> {
#if !NO_SERIALIZATION
#if !NO_SERIALIZATION
#region class SerializedDictionary
#region class SerializedDictionary
/// <summary>
/// <summary> /// Dictionary wrapped used to reconstruct a serialized read only dictionary
/// Dictionary wrapped used to reconstruct a serialized read only dictionary /// </summary>
/// </summary> private class SerializedDictionary : Dictionary<KeyType, ValueType> {
private class SerializedDictionary : Dictionary<KeyType, ValueType> {
/// <summary>
/// <summary> /// Initializes a new instance of the System.WeakReference class, using deserialized
/// Initializes a new instance of the System.WeakReference class, using deserialized /// data from the specified serialization and stream objects.
/// data from the specified serialization and stream objects. /// </summary>
/// </summary> /// <param name="info">
/// <param name="info"> /// An object that holds all the data needed to serialize or deserialize the
/// An object that holds all the data needed to serialize or deserialize the /// current System.WeakReference object.
/// current System.WeakReference object. /// </param>
/// </param> /// <param name="context">
/// <param name="context"> /// (Reserved) Describes the source and destination of the serialized stream
/// (Reserved) Describes the source and destination of the serialized stream /// specified by info.
/// specified by info. /// </param>
/// </param> /// <exception cref="System.ArgumentNullException">
/// <exception cref="System.ArgumentNullException"> /// The info parameter is null.
/// The info parameter is null. /// </exception>
/// </exception> public SerializedDictionary(SerializationInfo info, StreamingContext context) :
public SerializedDictionary(SerializationInfo info, StreamingContext context) : base(info, context) { }
base(info, context) { }
}
}
#endregion // class SerializedDictionary
#endregion // class SerializedDictionary
/// <summary>
/// <summary> /// Initializes a new instance of the System.WeakReference class, using deserialized
/// Initializes a new instance of the System.WeakReference class, using deserialized /// data from the specified serialization and stream objects.
/// data from the specified serialization and stream objects. /// </summary>
/// </summary> /// <param name="info">
/// <param name="info"> /// An object that holds all the data needed to serialize or deserialize the
/// An object that holds all the data needed to serialize or deserialize the /// current System.WeakReference object.
/// current System.WeakReference object. /// </param>
/// </param> /// <param name="context">
/// <param name="context"> /// (Reserved) Describes the source and destination of the serialized stream
/// (Reserved) Describes the source and destination of the serialized stream /// specified by info.
/// specified by info. /// </param>
/// </param> /// <exception cref="System.ArgumentNullException">
/// <exception cref="System.ArgumentNullException"> /// The info parameter is null.
/// The info parameter is null. /// </exception>
/// </exception> protected ReadOnlyDictionary(SerializationInfo info, StreamingContext context) :
protected ReadOnlyDictionary(SerializationInfo info, StreamingContext context) : this(new SerializedDictionary(info, context)) { }
this(new SerializedDictionary(info, context)) { }
#endif // !NO_SERIALIZATION
#endif // !NO_SERIALIZATION
/// <summary>Initializes a new read-only dictionary wrapper</summary>
/// <summary>Initializes a new read-only dictionary wrapper</summary> /// <param name="dictionary">Dictionary that will be wrapped</param>
/// <param name="dictionary">Dictionary that will be wrapped</param> public ReadOnlyDictionary(IDictionary<KeyType, ValueType> dictionary) {
public ReadOnlyDictionary(IDictionary<KeyType, ValueType> dictionary) { this.typedDictionary = dictionary;
this.typedDictionary = dictionary; this.objectDictionary = (this.typedDictionary as IDictionary);
this.objectDictionary = (this.typedDictionary as IDictionary); }
}
/// <summary>Whether the directory is write-protected</summary>
/// <summary>Whether the directory is write-protected</summary> public bool IsReadOnly {
public bool IsReadOnly { get { return true; }
get { return true; } }
}
/// <summary>
/// <summary> /// Determines whether the specified KeyValuePair is contained in the Dictionary
/// Determines whether the specified KeyValuePair is contained in the Dictionary /// </summary>
/// </summary> /// <param name="item">KeyValuePair that will be checked for</param>
/// <param name="item">KeyValuePair that will be checked for</param> /// <returns>True if the provided KeyValuePair was contained in the Dictionary</returns>
/// <returns>True if the provided KeyValuePair was contained in the Dictionary</returns> public bool Contains(KeyValuePair<KeyType, ValueType> item) {
public bool Contains(KeyValuePair<KeyType, ValueType> item) { return this.typedDictionary.Contains(item);
return this.typedDictionary.Contains(item); }
}
/// <summary>Determines whether the Dictionary contains the specified key</summary>
/// <summary>Determines whether the Dictionary contains the specified key</summary> /// <param name="key">Key that will be checked for</param>
/// <param name="key">Key that will be checked for</param> /// <returns>
/// <returns> /// True if an entry with the specified key was contained in the Dictionary
/// True if an entry with the specified key was contained in the Dictionary /// </returns>
/// </returns> public bool ContainsKey(KeyType key) {
public bool ContainsKey(KeyType key) { return this.typedDictionary.ContainsKey(key);
return this.typedDictionary.ContainsKey(key); }
}
/// <summary>Copies the contents of the Dictionary into an array</summary>
/// <summary>Copies the contents of the Dictionary into an array</summary> /// <param name="array">Array the Dictionary will be copied into</param>
/// <param name="array">Array the Dictionary will be copied into</param> /// <param name="arrayIndex">
/// <param name="arrayIndex"> /// Starting index at which to begin filling the destination array
/// Starting index at which to begin filling the destination array /// </param>
/// </param> public void CopyTo(KeyValuePair<KeyType, ValueType>[] array, int arrayIndex) {
public void CopyTo(KeyValuePair<KeyType, ValueType>[] array, int arrayIndex) { this.typedDictionary.CopyTo(array, arrayIndex);
this.typedDictionary.CopyTo(array, arrayIndex); }
}
/// <summary>Number of elements contained in the Dictionary</summary>
/// <summary>Number of elements contained in the Dictionary</summary> public int Count {
public int Count { get { return this.typedDictionary.Count; }
get { return this.typedDictionary.Count; } }
}
/// <summary>Creates a new enumerator for the Dictionary</summary>
/// <summary>Creates a new enumerator for the Dictionary</summary> /// <returns>The new Dictionary enumerator</returns>
/// <returns>The new Dictionary enumerator</returns> public IEnumerator<KeyValuePair<KeyType, ValueType>> GetEnumerator() {
public IEnumerator<KeyValuePair<KeyType, ValueType>> GetEnumerator() { return this.typedDictionary.GetEnumerator();
return this.typedDictionary.GetEnumerator(); }
}
/// <summary>Collection of all keys contained in the Dictionary</summary>
/// <summary>Collection of all keys contained in the Dictionary</summary> public ICollection<KeyType> Keys {
public ICollection<KeyType> Keys { get {
get { if(this.readonlyKeyCollection == null) {
if(this.readonlyKeyCollection == null) { this.readonlyKeyCollection = new ReadOnlyCollection<KeyType>(
this.readonlyKeyCollection = new ReadOnlyCollection<KeyType>( this.typedDictionary.Keys
this.typedDictionary.Keys );
); }
}
return this.readonlyKeyCollection;
return this.readonlyKeyCollection; }
} }
}
/// <summary>Collection of all values contained in the Dictionary</summary>
/// <summary>Collection of all values contained in the Dictionary</summary> public ICollection<ValueType> Values {
public ICollection<ValueType> Values { get {
get { if(this.readonlyValueCollection == null) {
if(this.readonlyValueCollection == null) { this.readonlyValueCollection = new ReadOnlyCollection<ValueType>(
this.readonlyValueCollection = new ReadOnlyCollection<ValueType>( this.typedDictionary.Values
this.typedDictionary.Values );
); }
}
return this.readonlyValueCollection;
return this.readonlyValueCollection; }
} }
}
/// <summary>
/// <summary> /// Attempts to retrieve the item with the specified key from the Dictionary
/// Attempts to retrieve the item with the specified key from the Dictionary /// </summary>
/// </summary> /// <param name="key">Key of the item to attempt to retrieve</param>
/// <param name="key">Key of the item to attempt to retrieve</param> /// <param name="value">
/// <param name="value"> /// Output parameter that will receive the key upon successful completion
/// Output parameter that will receive the key upon successful completion /// </param>
/// </param> /// <returns>
/// <returns> /// True if the item was found and has been placed in the output parameter
/// True if the item was found and has been placed in the output parameter /// </returns>
/// </returns> public bool TryGetValue(KeyType key, out ValueType value) {
public bool TryGetValue(KeyType key, out ValueType value) { return this.typedDictionary.TryGetValue(key, out value);
return this.typedDictionary.TryGetValue(key, out value); }
}
/// <summary>Accesses an item in the Dictionary by its key</summary>
/// <summary>Accesses an item in the Dictionary by its key</summary> /// <param name="key">Key of the item that will be accessed</param>
/// <param name="key">Key of the item that will be accessed</param> public ValueType this[KeyType key] {
public ValueType this[KeyType key] { get { return this.typedDictionary[key]; }
get { return this.typedDictionary[key]; } }
}
#region IDictionary<,> implementation
#region IDictionary<,> implementation
/// <summary>Inserts an item into the Dictionary</summary>
/// <summary>Inserts an item into the Dictionary</summary> /// <param name="key">Key under which to add the new item</param>
/// <param name="key">Key under which to add the new item</param> /// <param name="value">Item that will be added to the Dictionary</param>
/// <param name="value">Item that will be added to the Dictionary</param> void IDictionary<KeyType, ValueType>.Add(KeyType key, ValueType value) {
void IDictionary<KeyType, ValueType>.Add(KeyType key, ValueType value) { throw new NotSupportedException(
throw new NotSupportedException( "Adding items is not supported by the read-only Dictionary"
"Adding items is not supported by the read-only Dictionary" );
); }
}
/// <summary>Removes the item with the specified key from the Dictionary</summary>
/// <summary>Removes the item with the specified key from the Dictionary</summary> /// <param name="key">Key of the elementes that will be removed</param>
/// <param name="key">Key of the elementes that will be removed</param> /// <returns>True if an item with the specified key was found and removed</returns>
/// <returns>True if an item with the specified key was found and removed</returns> bool IDictionary<KeyType, ValueType>.Remove(KeyType key) {
bool IDictionary<KeyType, ValueType>.Remove(KeyType key) { throw new NotSupportedException(
throw new NotSupportedException( "Removing items is not supported by the read-only Dictionary"
"Removing items is not supported by the read-only Dictionary" );
); }
}
/// <summary>Accesses an item in the Dictionary by its key</summary>
/// <summary>Accesses an item in the Dictionary by its key</summary> /// <param name="key">Key of the item that will be accessed</param>
/// <param name="key">Key of the item that will be accessed</param> ValueType IDictionary<KeyType, ValueType>.this[KeyType key] {
ValueType IDictionary<KeyType, ValueType>.this[KeyType key] { get { return this.typedDictionary[key]; }
get { return this.typedDictionary[key]; } set {
set { throw new NotSupportedException(
throw new NotSupportedException( "Assigning items is not supported in a read-only Dictionary"
"Assigning items is not supported in a read-only Dictionary" );
); }
} }
}
#endregion
#endregion
#region IEnumerable implementation
#region IEnumerable implementation
/// <summary>Returns a new object enumerator for the Dictionary</summary>
/// <summary>Returns a new object enumerator for the Dictionary</summary> /// <returns>The new object enumerator</returns>
/// <returns>The new object enumerator</returns> System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() {
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return (this.typedDictionary as IEnumerable).GetEnumerator();
return (this.typedDictionary as IEnumerable).GetEnumerator(); }
}
#endregion
#endregion
#region IDictionary implementation
#region IDictionary implementation
/// <summary>Removes all items from the Dictionary</summary>
/// <summary>Removes all items from the Dictionary</summary> void IDictionary.Clear() {
void IDictionary.Clear() { throw new NotSupportedException(
throw new NotSupportedException( "Clearing is not supported in a read-only Dictionary"
"Clearing is not supported in a read-only Dictionary" );
); }
}
/// <summary>Adds an item into the Dictionary</summary>
/// <summary>Adds an item into the Dictionary</summary> /// <param name="key">Key under which the item will be added</param>
/// <param name="key">Key under which the item will be added</param> /// <param name="value">Item that will be added</param>
/// <param name="value">Item that will be added</param> void IDictionary.Add(object key, object value) {
void IDictionary.Add(object key, object value) { throw new NotSupportedException(
throw new NotSupportedException( "Adding items is not supported in a read-only Dictionary"
"Adding items is not supported in a read-only Dictionary" );
); }
}
/// <summary>Determines whether the specified key exists in the Dictionary</summary>
/// <summary>Determines whether the specified key exists in the Dictionary</summary> /// <param name="key">Key that will be checked for</param>
/// <param name="key">Key that will be checked for</param> /// <returns>True if an item with the specified key exists in the Dictionary</returns>
/// <returns>True if an item with the specified key exists in the Dictionary</returns> bool IDictionary.Contains(object key) {
bool IDictionary.Contains(object key) { return this.objectDictionary.Contains(key);
return this.objectDictionary.Contains(key); }
}
/// <summary>Whether the size of the Dictionary is fixed</summary>
/// <summary>Whether the size of the Dictionary is fixed</summary> bool IDictionary.IsFixedSize {
bool IDictionary.IsFixedSize { get { return this.objectDictionary.IsFixedSize; }
get { return this.objectDictionary.IsFixedSize; } }
}
/// <summary>Returns a collection of all keys in the Dictionary</summary>
/// <summary>Returns a collection of all keys in the Dictionary</summary> ICollection IDictionary.Keys {
ICollection IDictionary.Keys { get {
get { if(this.readonlyKeyCollection == null) {
if(this.readonlyKeyCollection == null) { this.readonlyKeyCollection = new ReadOnlyCollection<KeyType>(
this.readonlyKeyCollection = new ReadOnlyCollection<KeyType>( this.typedDictionary.Keys
this.typedDictionary.Keys );
); }
}
return this.readonlyKeyCollection;
return this.readonlyKeyCollection; }
} }
}
/// <summary>Returns a collection of all values stored in the Dictionary</summary>
/// <summary>Returns a collection of all values stored in the Dictionary</summary> ICollection IDictionary.Values {
ICollection IDictionary.Values { get {
get { if(this.readonlyValueCollection == null) {
if(this.readonlyValueCollection == null) { this.readonlyValueCollection = new ReadOnlyCollection<ValueType>(
this.readonlyValueCollection = new ReadOnlyCollection<ValueType>( this.typedDictionary.Values
this.typedDictionary.Values );
); }
}
return this.readonlyValueCollection;
return this.readonlyValueCollection; }
} }
}
/// <summary>Removes an item from the Dictionary</summary>
/// <summary>Removes an item from the Dictionary</summary> /// <param name="key">Key of the item that will be removed</param>
/// <param name="key">Key of the item that will be removed</param> void IDictionary.Remove(object key) {
void IDictionary.Remove(object key) { throw new NotSupportedException(
throw new NotSupportedException( "Removing is not supported by the read-only Dictionary"
"Removing is not supported by the read-only Dictionary" );
); }
}
/// <summary>Accesses an item in the Dictionary by its key</summary>
/// <summary>Accesses an item in the Dictionary by its key</summary> /// <param name="key">Key of the item that will be accessed</param>
/// <param name="key">Key of the item that will be accessed</param> /// <returns>The item with the specified key</returns>
/// <returns>The item with the specified key</returns> object IDictionary.this[object key] {
object IDictionary.this[object key] { get { return this.objectDictionary[key]; }
get { return this.objectDictionary[key]; } set {
set { throw new NotSupportedException(
throw new NotSupportedException( "Assigning items is not supported by the read-only Dictionary"
"Assigning items is not supported by the read-only Dictionary" );
); }
} }
}
#endregion // IDictionary implementation
#endregion // IDictionary implementation
#region IDictionaryEnumerator implementation
#region IDictionaryEnumerator implementation
/// <summary>Returns a new entry enumerator for the dictionary</summary>
/// <summary>Returns a new entry enumerator for the dictionary</summary> /// <returns>The new entry enumerator</returns>
/// <returns>The new entry enumerator</returns> IDictionaryEnumerator IDictionary.GetEnumerator() {
IDictionaryEnumerator IDictionary.GetEnumerator() { return this.objectDictionary.GetEnumerator();
return this.objectDictionary.GetEnumerator(); }
}
#endregion // IDictionaryEnumerator implementation
#endregion // IDictionaryEnumerator implementation
#region ICollection<> implementation
#region ICollection<> implementation
/// <summary>Inserts an already prepared element into the Dictionary</summary>
/// <summary>Inserts an already prepared element into the Dictionary</summary> /// <param name="item">Prepared element that will be added to the Dictionary</param>
/// <param name="item">Prepared element that will be added to the Dictionary</param> void ICollection<KeyValuePair<KeyType, ValueType>>.Add(
void ICollection<KeyValuePair<KeyType, ValueType>>.Add( KeyValuePair<KeyType, ValueType> item
KeyValuePair<KeyType, ValueType> item ) {
) { throw new NotSupportedException(
throw new NotSupportedException( "Adding items is not supported by the read-only Dictionary"
"Adding items is not supported by the read-only Dictionary" );
); }
}
/// <summary>Removes all items from the Dictionary</summary>
/// <summary>Removes all items from the Dictionary</summary> void ICollection<KeyValuePair<KeyType, ValueType>>.Clear() {
void ICollection<KeyValuePair<KeyType, ValueType>>.Clear() { throw new NotSupportedException(
throw new NotSupportedException( "Clearing is not supported in a read-only Dictionary"
"Clearing is not supported in a read-only Dictionary" );
); }
}
/// <summary>Removes all items from the Dictionary</summary>
/// <summary>Removes all items from the Dictionary</summary> /// <param name="itemToRemove">Item that will be removed from the Dictionary</param>
/// <param name="itemToRemove">Item that will be removed from the Dictionary</param> bool ICollection<KeyValuePair<KeyType, ValueType>>.Remove(
bool ICollection<KeyValuePair<KeyType, ValueType>>.Remove( KeyValuePair<KeyType, ValueType> itemToRemove
KeyValuePair<KeyType, ValueType> itemToRemove ) {
) { throw new NotSupportedException(
throw new NotSupportedException( "Removing items is not supported in a read-only Dictionary"
"Removing items is not supported in a read-only Dictionary" );
); }
}
#endregion
#endregion
#region ICollection implementation
#region ICollection implementation
/// <summary>Copies the contents of the Dictionary into an array</summary>
/// <summary>Copies the contents of the Dictionary into an array</summary> /// <param name="array">Array the Dictionary contents will be copied into</param>
/// <param name="array">Array the Dictionary contents will be copied into</param> /// <param name="index">
/// <param name="index"> /// Starting index at which to begin filling the destination array
/// Starting index at which to begin filling the destination array /// </param>
/// </param> void ICollection.CopyTo(Array array, int index) {
void ICollection.CopyTo(Array array, int index) { this.objectDictionary.CopyTo(array, index);
this.objectDictionary.CopyTo(array, index); }
}
/// <summary>Whether the Dictionary is synchronized for multi-threaded usage</summary>
/// <summary>Whether the Dictionary is synchronized for multi-threaded usage</summary> bool ICollection.IsSynchronized {
bool ICollection.IsSynchronized { get { return this.objectDictionary.IsSynchronized; }
get { return this.objectDictionary.IsSynchronized; } }
}
/// <summary>Synchronization root on which the Dictionary locks</summary>
/// <summary>Synchronization root on which the Dictionary locks</summary> object ICollection.SyncRoot {
object ICollection.SyncRoot { get { return this.objectDictionary.SyncRoot; }
get { return this.objectDictionary.SyncRoot; } }
}
#endregion
#endregion
#if !NO_SERIALIZATION
#if !NO_SERIALIZATION #region ISerializable implementation
#region ISerializable implementation
/// <summary>Serializes the Dictionary</summary>
/// <summary>Serializes the Dictionary</summary> /// <param name="info">
/// <param name="info"> /// Provides the container into which the Dictionary will serialize itself
/// Provides the container into which the Dictionary will serialize itself /// </param>
/// </param> /// <param name="context">
/// <param name="context"> /// Contextual informations about the serialization environment
/// Contextual informations about the serialization environment /// </param>
/// </param> void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context) {
void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context) { (this.typedDictionary as ISerializable).GetObjectData(info, context);
(this.typedDictionary as ISerializable).GetObjectData(info, context); }
}
/// <summary>Called after all objects have been successfully deserialized</summary>
/// <summary>Called after all objects have been successfully deserialized</summary> /// <param name="sender">Nicht unterstützt</param>
/// <param name="sender">Nicht unterstützt</param> void IDeserializationCallback.OnDeserialization(object sender) {
void IDeserializationCallback.OnDeserialization(object sender) { (this.typedDictionary as IDeserializationCallback).OnDeserialization(sender);
(this.typedDictionary as IDeserializationCallback).OnDeserialization(sender); }
}
#endregion
#endregion #endif //!NO_SERIALIZATION
#endif //!NO_SERIALIZATION
/// <summary>The wrapped Dictionary under its type-safe interface</summary>
/// <summary>The wrapped Dictionary under its type-safe interface</summary> private IDictionary<KeyType, ValueType> typedDictionary;
private IDictionary<KeyType, ValueType> typedDictionary; /// <summary>The wrapped Dictionary under its object interface</summary>
/// <summary>The wrapped Dictionary under its object interface</summary> private IDictionary objectDictionary;
private IDictionary objectDictionary; /// <summary>ReadOnly wrapper for the keys collection of the Dictionary</summary>
/// <summary>ReadOnly wrapper for the keys collection of the Dictionary</summary> private ReadOnlyCollection<KeyType> readonlyKeyCollection;
private ReadOnlyCollection<KeyType> readonlyKeyCollection; /// <summary>ReadOnly wrapper for the values collection of the Dictionary</summary>
/// <summary>ReadOnly wrapper for the values collection of the Dictionary</summary> private ReadOnlyCollection<ValueType> readonlyValueCollection;
private ReadOnlyCollection<ValueType> readonlyValueCollection; }
}
} // namespace Nuclex.Support.Collections
} // namespace Nuclex.Support.Collections

View File

@ -1,381 +1,380 @@
#region CPL License #region Apache License 2.0
/* /*
Nuclex Framework Nuclex .NET Framework
Copyright (C) 2002-2017 Nuclex Development Labs Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
This library is free software; you can redistribute it and/or Licensed under the Apache License, Version 2.0 (the "License");
modify it under the terms of the IBM Common Public License as you may not use this file except in compliance with the License.
published by the IBM Corporation; either version 1.0 of the You may obtain a copy of the License at
License, or (at your option) any later version.
http://www.apache.org/licenses/LICENSE-2.0
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of Unless required by applicable law or agreed to in writing, software
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the distributed under the License is distributed on an "AS IS" BASIS,
IBM Common Public License for more details. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
You should have received a copy of the IBM Common Public limitations under the License.
License along with this library */
*/ #endregion // Apache License 2.0
#endregion
using System;
using System; using System.Collections;
using System.Collections; using System.Collections.Generic;
using System.Collections.Generic;
#if UNITTEST
#if UNITTEST
using NUnit.Framework;
using NUnit.Framework;
namespace Nuclex.Support.Collections {
namespace Nuclex.Support.Collections {
/// <summary>Unit Test for the read only list wrapper</summary>
/// <summary>Unit Test for the read only list wrapper</summary> [TestFixture]
[TestFixture] internal class ReadOnlyListTest {
internal class ReadOnlyListTest {
/// <summary>
/// <summary> /// Verifies that the copy constructor of the read only list works
/// Verifies that the copy constructor of the read only list works /// </summary>
/// </summary> [Test]
[Test] public void TestCopyConstructor() {
public void TestCopyConstructor() { int[] integers = new int[] { 12, 34, 56, 78 };
int[] integers = new int[] { 12, 34, 56, 78 }; ReadOnlyList<int> testList = new ReadOnlyList<int>(integers);
ReadOnlyList<int> testList = new ReadOnlyList<int>(integers);
CollectionAssert.AreEqual(integers, testList);
CollectionAssert.AreEqual(integers, testList); }
}
/// <summary>Verifies that the IsReadOnly property returns true</summary>
/// <summary>Verifies that the IsReadOnly property returns true</summary> [Test]
[Test] public void TestIsReadOnly() {
public void TestIsReadOnly() { ReadOnlyList<int> testList = new ReadOnlyList<int>(new int[0]);
ReadOnlyList<int> testList = new ReadOnlyList<int>(new int[0]);
Assert.IsTrue(testList.IsReadOnly);
Assert.IsTrue(testList.IsReadOnly); }
}
/// <summary>
/// <summary> /// Verifies that the CopyTo() of the read only list works
/// Verifies that the CopyTo() of the read only list works /// </summary>
/// </summary> [Test]
[Test] public void TestCopyToArray() {
public void TestCopyToArray() { int[] inputIntegers = new int[] { 12, 34, 56, 78 };
int[] inputIntegers = new int[] { 12, 34, 56, 78 }; ReadOnlyList<int> testList = new ReadOnlyList<int>(inputIntegers);
ReadOnlyList<int> testList = new ReadOnlyList<int>(inputIntegers);
int[] outputIntegers = new int[testList.Count];
int[] outputIntegers = new int[testList.Count]; testList.CopyTo(outputIntegers, 0);
testList.CopyTo(outputIntegers, 0);
CollectionAssert.AreEqual(inputIntegers, outputIntegers);
CollectionAssert.AreEqual(inputIntegers, outputIntegers); }
}
/// <summary>
/// <summary> /// Checks whether the Contains() method of the read only list is able to
/// Checks whether the Contains() method of the read only list is able to /// determine if the list contains an item
/// determine if the list contains an item /// </summary>
/// </summary> [Test]
[Test] public void TestContains() {
public void TestContains() { int[] integers = new int[] { 1234, 6789 };
int[] integers = new int[] { 1234, 6789 }; ReadOnlyList<int> testList = new ReadOnlyList<int>(integers);
ReadOnlyList<int> testList = new ReadOnlyList<int>(integers);
Assert.IsTrue(testList.Contains(1234));
Assert.IsTrue(testList.Contains(1234)); Assert.IsFalse(testList.Contains(4321));
Assert.IsFalse(testList.Contains(4321)); }
}
/// <summary>
/// <summary> /// Checks whether the IndexOf() method of the read only list is able to
/// Checks whether the IndexOf() method of the read only list is able to /// determine if the index of an item in the list
/// determine if the index of an item in the list /// </summary>
/// </summary> [Test]
[Test] public void TestIndexOf() {
public void TestIndexOf() { int[] integers = new int[] { 12, 34, 67, 89 };
int[] integers = new int[] { 12, 34, 67, 89 }; ReadOnlyList<int> testList = new ReadOnlyList<int>(integers);
ReadOnlyList<int> testList = new ReadOnlyList<int>(integers);
Assert.AreEqual(0, testList.IndexOf(12));
Assert.AreEqual(0, testList.IndexOf(12)); Assert.AreEqual(1, testList.IndexOf(34));
Assert.AreEqual(1, testList.IndexOf(34)); Assert.AreEqual(2, testList.IndexOf(67));
Assert.AreEqual(2, testList.IndexOf(67)); Assert.AreEqual(3, testList.IndexOf(89));
Assert.AreEqual(3, testList.IndexOf(89)); }
}
/// <summary>
/// <summary> /// Checks whether the indexer method of the read only list is able to
/// Checks whether the indexer method of the read only list is able to /// retrieve items from the list
/// retrieve items from the list /// </summary>
/// </summary> [Test]
[Test] public void TestRetrieveByIndexer() {
public void TestRetrieveByIndexer() { int[] integers = new int[] { 12, 34, 67, 89 };
int[] integers = new int[] { 12, 34, 67, 89 }; ReadOnlyList<int> testList = new ReadOnlyList<int>(integers);
ReadOnlyList<int> testList = new ReadOnlyList<int>(integers);
Assert.AreEqual(12, testList[0]);
Assert.AreEqual(12, testList[0]); Assert.AreEqual(34, testList[1]);
Assert.AreEqual(34, testList[1]); Assert.AreEqual(67, testList[2]);
Assert.AreEqual(67, testList[2]); Assert.AreEqual(89, testList[3]);
Assert.AreEqual(89, testList[3]); }
}
/// <summary>
/// <summary> /// Checks whether the read only list will throw an exception if its Insert() method
/// Checks whether the read only list will throw an exception if its Insert() method /// is called via the generic IList&lt;&gt; interface
/// is called via the generic IList&lt;&gt; interface /// </summary>
/// </summary> [Test]
[Test] public void TestThrowOnInsertViaGenericIList() {
public void TestThrowOnInsertViaGenericIList() { ReadOnlyList<int> testList = new ReadOnlyList<int>(new int[0]);
ReadOnlyList<int> testList = new ReadOnlyList<int>(new int[0]); Assert.Throws<NotSupportedException>(
Assert.Throws<NotSupportedException>( delegate() { (testList as IList<int>).Insert(0, 12345); }
delegate() { (testList as IList<int>).Insert(0, 12345); } );
); }
}
/// <summary>
/// <summary> /// Checks whether the read only list will throw an exception if its RemoveAt() method
/// Checks whether the read only list will throw an exception if its RemoveAt() method /// is called via the generic IList&lt;&gt; interface
/// is called via the generic IList&lt;&gt; interface /// </summary>
/// </summary> [Test]
[Test] public void TestThrowOnRemoveViaGenericIList() {
public void TestThrowOnRemoveViaGenericIList() { ReadOnlyList<int> testList = new ReadOnlyList<int>(new int[1]);
ReadOnlyList<int> testList = new ReadOnlyList<int>(new int[1]); Assert.Throws<NotSupportedException>(
Assert.Throws<NotSupportedException>( delegate() { (testList as IList<int>).RemoveAt(0); }
delegate() { (testList as IList<int>).RemoveAt(0); } );
); }
}
/// <summary>
/// <summary> /// Checks whether the indexer method of the read only list will throw an exception
/// Checks whether the indexer method of the read only list will throw an exception /// if it is attempted to be used for replacing an item
/// if it is attempted to be used for replacing an item /// </summary>
/// </summary> [Test]
[Test] public void TestRetrieveByIndexerViaGenericIList() {
public void TestRetrieveByIndexerViaGenericIList() { int[] integers = new int[] { 12, 34, 67, 89 };
int[] integers = new int[] { 12, 34, 67, 89 }; ReadOnlyList<int> testList = new ReadOnlyList<int>(integers);
ReadOnlyList<int> testList = new ReadOnlyList<int>(integers);
Assert.AreEqual(12, (testList as IList<int>)[0]);
Assert.AreEqual(12, (testList as IList<int>)[0]); Assert.AreEqual(34, (testList as IList<int>)[1]);
Assert.AreEqual(34, (testList as IList<int>)[1]); Assert.AreEqual(67, (testList as IList<int>)[2]);
Assert.AreEqual(67, (testList as IList<int>)[2]); Assert.AreEqual(89, (testList as IList<int>)[3]);
Assert.AreEqual(89, (testList as IList<int>)[3]); }
}
/// <summary>
/// <summary> /// Checks whether the indexer method of the read only list will throw an exception
/// Checks whether the indexer method of the read only list will throw an exception /// if it is attempted to be used for replacing an item
/// if it is attempted to be used for replacing an item /// </summary>
/// </summary> [Test]
[Test] public void TestThrowOnReplaceByIndexerViaGenericIList() {
public void TestThrowOnReplaceByIndexerViaGenericIList() { ReadOnlyList<int> testList = new ReadOnlyList<int>(new int[1]);
ReadOnlyList<int> testList = new ReadOnlyList<int>(new int[1]);
Assert.Throws<NotSupportedException>(
Assert.Throws<NotSupportedException>( delegate() { (testList as IList<int>)[0] = 12345; }
delegate() { (testList as IList<int>)[0] = 12345; } );
); }
}
/// <summary>
/// <summary> /// Checks whether the read only list will throw an exception if its Add() method
/// Checks whether the read only list will throw an exception if its Add() method /// is called via the generic ICollection&lt;&gt; interface
/// is called via the generic ICollection&lt;&gt; interface /// </summary>
/// </summary> [Test]
[Test] public void TestThrowOnAddViaGenericICollection() {
public void TestThrowOnAddViaGenericICollection() { ReadOnlyList<int> testList = new ReadOnlyList<int>(new int[0]);
ReadOnlyList<int> testList = new ReadOnlyList<int>(new int[0]); Assert.Throws<NotSupportedException>(
Assert.Throws<NotSupportedException>( delegate() { (testList as ICollection<int>).Add(12345); }
delegate() { (testList as ICollection<int>).Add(12345); } );
); }
}
/// <summary>
/// <summary> /// Checks whether the read only list will throw an exception if its Clear() method
/// Checks whether the read only list will throw an exception if its Clear() method /// is called via the generic ICollection&lt;&gt; interface
/// is called via the generic ICollection&lt;&gt; interface /// </summary>
/// </summary> [Test]
[Test] public void TestThrowOnClearViaGenericICollection() {
public void TestThrowOnClearViaGenericICollection() { ReadOnlyList<int> testList = new ReadOnlyList<int>(new int[1]);
ReadOnlyList<int> testList = new ReadOnlyList<int>(new int[1]); Assert.Throws<NotSupportedException>(
Assert.Throws<NotSupportedException>( delegate() { (testList as ICollection<int>).Clear(); }
delegate() { (testList as ICollection<int>).Clear(); } );
); }
}
/// <summary>
/// <summary> /// Checks whether the read only list will throw an exception if its Remove() method
/// Checks whether the read only list will throw an exception if its Remove() method /// is called via the generic ICollection&lt;&gt; interface
/// is called via the generic ICollection&lt;&gt; interface /// </summary>
/// </summary> [Test]
[Test] public void TestThrowOnRemoveViaGenericICollection() {
public void TestThrowOnRemoveViaGenericICollection() { int[] integers = new int[] { 12, 34, 67, 89 };
int[] integers = new int[] { 12, 34, 67, 89 }; ReadOnlyList<int> testList = new ReadOnlyList<int>(integers);
ReadOnlyList<int> testList = new ReadOnlyList<int>(integers);
Assert.Throws<NotSupportedException>(
Assert.Throws<NotSupportedException>( delegate() { (testList as ICollection<int>).Remove(89); }
delegate() { (testList as ICollection<int>).Remove(89); } );
); }
}
/// <summary>
/// <summary> /// Tests whether the typesafe enumerator of the read only list is working
/// Tests whether the typesafe enumerator of the read only list is working /// </summary>
/// </summary> [Test]
[Test] public void TestTypesafeEnumerator() {
public void TestTypesafeEnumerator() { int[] inputIntegers = new int[] { 12, 34, 56, 78 };
int[] inputIntegers = new int[] { 12, 34, 56, 78 }; ReadOnlyList<int> testList = new ReadOnlyList<int>(inputIntegers);
ReadOnlyList<int> testList = new ReadOnlyList<int>(inputIntegers);
List<int> outputIntegers = new List<int>();
List<int> outputIntegers = new List<int>(); foreach(int value in testList) {
foreach(int value in testList) { outputIntegers.Add(value);
outputIntegers.Add(value); }
}
CollectionAssert.AreEqual(inputIntegers, outputIntegers);
CollectionAssert.AreEqual(inputIntegers, outputIntegers); }
}
/// <summary>
/// <summary> /// Checks whether the read only list will throw an exception if its Clear() method
/// Checks whether the read only list will throw an exception if its Clear() method /// is called via the IList interface
/// is called via the IList interface /// </summary>
/// </summary> [Test]
[Test] public void TestThrowOnClearViaIList() {
public void TestThrowOnClearViaIList() { ReadOnlyList<int> testList = new ReadOnlyList<int>(new int[1]);
ReadOnlyList<int> testList = new ReadOnlyList<int>(new int[1]); Assert.Throws<NotSupportedException>(
Assert.Throws<NotSupportedException>( delegate() { (testList as IList).Clear(); }
delegate() { (testList as IList).Clear(); } );
); }
}
/// <summary>
/// <summary> /// Checks whether the read only list will throw an exception if its Add() method
/// Checks whether the read only list will throw an exception if its Add() method /// is called via the IList interface
/// is called via the IList interface /// </summary>
/// </summary> [Test]
[Test] public void TestThrowOnAddViaIList() {
public void TestThrowOnAddViaIList() { ReadOnlyList<int> testList = new ReadOnlyList<int>(new int[0]);
ReadOnlyList<int> testList = new ReadOnlyList<int>(new int[0]); Assert.Throws<NotSupportedException>(
Assert.Throws<NotSupportedException>( delegate() { (testList as IList).Add(12345); }
delegate() { (testList as IList).Add(12345); } );
); }
}
/// <summary>
/// <summary> /// Checks whether the Contains() method of the read only list is able to
/// Checks whether the Contains() method of the read only list is able to /// determine if the list contains an item
/// determine if the list contains an item /// </summary>
/// </summary> [Test]
[Test] public void TestContainsViaIList() {
public void TestContainsViaIList() { int[] integers = new int[] { 1234, 6789 };
int[] integers = new int[] { 1234, 6789 }; ReadOnlyList<int> testList = new ReadOnlyList<int>(integers);
ReadOnlyList<int> testList = new ReadOnlyList<int>(integers);
Assert.IsTrue((testList as IList).Contains(1234));
Assert.IsTrue((testList as IList).Contains(1234)); Assert.IsFalse((testList as IList).Contains(4321));
Assert.IsFalse((testList as IList).Contains(4321)); }
}
/// <summary>
/// <summary> /// Checks whether the IndexOf() method of the read only list is able to
/// Checks whether the IndexOf() method of the read only list is able to /// determine if the index of an item in the list
/// determine if the index of an item in the list /// </summary>
/// </summary> [Test]
[Test] public void TestIndexOfViaIList() {
public void TestIndexOfViaIList() { int[] integers = new int[] { 12, 34, 67, 89 };
int[] integers = new int[] { 12, 34, 67, 89 }; ReadOnlyList<int> testList = new ReadOnlyList<int>(integers);
ReadOnlyList<int> testList = new ReadOnlyList<int>(integers);
Assert.AreEqual(0, (testList as IList).IndexOf(12));
Assert.AreEqual(0, (testList as IList).IndexOf(12)); Assert.AreEqual(1, (testList as IList).IndexOf(34));
Assert.AreEqual(1, (testList as IList).IndexOf(34)); Assert.AreEqual(2, (testList as IList).IndexOf(67));
Assert.AreEqual(2, (testList as IList).IndexOf(67)); Assert.AreEqual(3, (testList as IList).IndexOf(89));
Assert.AreEqual(3, (testList as IList).IndexOf(89)); }
}
/// <summary>
/// <summary> /// Checks whether the read only list will throw an exception if its Insert() method
/// Checks whether the read only list will throw an exception if its Insert() method /// is called via the IList interface
/// is called via the IList interface /// </summary>
/// </summary> [Test]
[Test] public void TestThrowOnInsertViaIList() {
public void TestThrowOnInsertViaIList() { ReadOnlyList<int> testList = new ReadOnlyList<int>(new int[0]);
ReadOnlyList<int> testList = new ReadOnlyList<int>(new int[0]); Assert.Throws<NotSupportedException>(
Assert.Throws<NotSupportedException>( delegate() { (testList as IList).Insert(0, 12345); }
delegate() { (testList as IList).Insert(0, 12345); } );
); }
}
/// <summary>
/// <summary> /// Checks whether the IsFixedSize property of the read only list returns the
/// Checks whether the IsFixedSize property of the read only list returns the /// expected result for a read only list based on a fixed array
/// expected result for a read only list based on a fixed array /// </summary>
/// </summary> [Test]
[Test] public void TestIsFixedSizeViaIList() {
public void TestIsFixedSizeViaIList() { int[] integers = new int[] { 12, 34, 67, 89 };
int[] integers = new int[] { 12, 34, 67, 89 }; ReadOnlyList<int> testList = new ReadOnlyList<int>(integers);
ReadOnlyList<int> testList = new ReadOnlyList<int>(integers);
Assert.IsTrue((testList as IList).IsFixedSize);
Assert.IsTrue((testList as IList).IsFixedSize); }
}
/// <summary>
/// <summary> /// Checks whether the read only list will throw an exception if its Remove() method
/// Checks whether the read only list will throw an exception if its Remove() method /// is called via the IList interface
/// is called via the IList interface /// </summary>
/// </summary> [Test]
[Test] public void TestThrowOnRemoveViaIList() {
public void TestThrowOnRemoveViaIList() { int[] integers = new int[] { 1234, 6789 };
int[] integers = new int[] { 1234, 6789 }; ReadOnlyList<int> testList = new ReadOnlyList<int>(integers);
ReadOnlyList<int> testList = new ReadOnlyList<int>(integers);
Assert.Throws<NotSupportedException>(
Assert.Throws<NotSupportedException>( delegate() { (testList as IList).Remove(6789); }
delegate() { (testList as IList).Remove(6789); } );
); }
}
/// <summary>
/// <summary> /// Checks whether the read only list will throw an exception if its Remove() method
/// Checks whether the read only list will throw an exception if its Remove() method /// is called via the IList interface
/// is called via the IList interface /// </summary>
/// </summary> [Test]
[Test] public void TestThrowOnRemoveAtViaIList() {
public void TestThrowOnRemoveAtViaIList() { ReadOnlyList<int> testList = new ReadOnlyList<int>(new int[1]);
ReadOnlyList<int> testList = new ReadOnlyList<int>(new int[1]);
Assert.Throws<NotSupportedException>(
Assert.Throws<NotSupportedException>( delegate() { (testList as IList).RemoveAt(0); }
delegate() { (testList as IList).RemoveAt(0); } );
); }
}
/// <summary>
/// <summary> /// Checks whether the indexer method of the read only list will throw an exception
/// Checks whether the indexer method of the read only list will throw an exception /// if it is attempted to be used for replacing an item
/// if it is attempted to be used for replacing an item /// </summary>
/// </summary> [Test]
[Test] public void TestRetrieveByIndexerViaIList() {
public void TestRetrieveByIndexerViaIList() { int[] integers = new int[] { 12, 34, 67, 89 };
int[] integers = new int[] { 12, 34, 67, 89 }; ReadOnlyList<int> testList = new ReadOnlyList<int>(integers);
ReadOnlyList<int> testList = new ReadOnlyList<int>(integers);
Assert.AreEqual(12, (testList as IList)[0]);
Assert.AreEqual(12, (testList as IList)[0]); Assert.AreEqual(34, (testList as IList)[1]);
Assert.AreEqual(34, (testList as IList)[1]); Assert.AreEqual(67, (testList as IList)[2]);
Assert.AreEqual(67, (testList as IList)[2]); Assert.AreEqual(89, (testList as IList)[3]);
Assert.AreEqual(89, (testList as IList)[3]); }
}
/// <summary>
/// <summary> /// Checks whether the indexer method of the read only list will throw an exception
/// Checks whether the indexer method of the read only list will throw an exception /// if it is attempted to be used for replacing an item
/// if it is attempted to be used for replacing an item /// </summary>
/// </summary> [Test]
[Test] public void TestThrowOnReplaceByIndexerViaIList() {
public void TestThrowOnReplaceByIndexerViaIList() { ReadOnlyList<int> testList = new ReadOnlyList<int>(new int[1]);
ReadOnlyList<int> testList = new ReadOnlyList<int>(new int[1]);
Assert.Throws<NotSupportedException>(
Assert.Throws<NotSupportedException>( delegate() { (testList as IList)[0] = 12345; }
delegate() { (testList as IList)[0] = 12345; } );
); }
}
/// <summary>
/// <summary> /// Verifies that the CopyTo() of the read only list works if invoked via
/// Verifies that the CopyTo() of the read only list works if invoked via /// the ICollection interface
/// the ICollection interface /// </summary>
/// </summary> [Test]
[Test] public void TestCopyToArrayViaICollection() {
public void TestCopyToArrayViaICollection() { int[] inputIntegers = new int[] { 12, 34, 56, 78 };
int[] inputIntegers = new int[] { 12, 34, 56, 78 }; ReadOnlyList<int> testList = new ReadOnlyList<int>(inputIntegers);
ReadOnlyList<int> testList = new ReadOnlyList<int>(inputIntegers);
int[] outputIntegers = new int[testList.Count];
int[] outputIntegers = new int[testList.Count]; (testList as ICollection).CopyTo(outputIntegers, 0);
(testList as ICollection).CopyTo(outputIntegers, 0);
CollectionAssert.AreEqual(inputIntegers, outputIntegers);
CollectionAssert.AreEqual(inputIntegers, outputIntegers); }
}
/// <summary>
/// <summary> /// Verifies that the IsSynchronized property and the SyncRoot property are working
/// Verifies that the IsSynchronized property and the SyncRoot property are working /// </summary>
/// </summary> [Test]
[Test] public void TestSynchronization() {
public void TestSynchronization() { ReadOnlyList<int> testList = new ReadOnlyList<int>(new int[0]);
ReadOnlyList<int> testList = new ReadOnlyList<int>(new int[0]);
if(!(testList as ICollection).IsSynchronized) {
if(!(testList as ICollection).IsSynchronized) { lock((testList as ICollection).SyncRoot) {
lock((testList as ICollection).SyncRoot) { Assert.AreEqual(0, testList.Count);
Assert.AreEqual(0, testList.Count); }
} }
} }
}
}
}
} // namespace Nuclex.Support.Collections
} // namespace Nuclex.Support.Collections
#endif // UNITTEST
#endif // UNITTEST

View File

@ -1,258 +1,257 @@
#region CPL License #region Apache License 2.0
/* /*
Nuclex Framework Nuclex .NET Framework
Copyright (C) 2002-2017 Nuclex Development Labs Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
This library is free software; you can redistribute it and/or Licensed under the Apache License, Version 2.0 (the "License");
modify it under the terms of the IBM Common Public License as you may not use this file except in compliance with the License.
published by the IBM Corporation; either version 1.0 of the You may obtain a copy of the License at
License, or (at your option) any later version.
http://www.apache.org/licenses/LICENSE-2.0
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of Unless required by applicable law or agreed to in writing, software
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the distributed under the License is distributed on an "AS IS" BASIS,
IBM Common Public License for more details. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
You should have received a copy of the IBM Common Public limitations under the License.
License along with this library */
*/ #endregion // Apache License 2.0
#endregion
using System;
using System; using System.Collections;
using System.Collections; using System.Collections.Generic;
using System.Collections.Generic;
namespace Nuclex.Support.Collections {
namespace Nuclex.Support.Collections {
/// <summary>Wraps a list and prevents users from modifying it</summary>
/// <summary>Wraps a list and prevents users from modifying it</summary> /// <typeparam name="TItem">Type of items to manage in the set</typeparam>
/// <typeparam name="TItem">Type of items to manage in the set</typeparam> public class ReadOnlyList<TItem> : IList<TItem>, IList {
public class ReadOnlyList<TItem> : IList<TItem>, IList {
/// <summary>Initializes a new read-only List wrapper</summary>
/// <summary>Initializes a new read-only List wrapper</summary> /// <param name="list">List that will be wrapped</param>
/// <param name="list">List that will be wrapped</param> public ReadOnlyList(IList<TItem> list) {
public ReadOnlyList(IList<TItem> list) { this.typedList = list;
this.typedList = list; this.objectList = (list as IList);
this.objectList = (list as IList); }
}
/// <summary>Retrieves the index of an item within the List</summary>
/// <summary>Retrieves the index of an item within the List</summary> /// <param name="item">Item whose index will be returned</param>
/// <param name="item">Item whose index will be returned</param> /// <returns>The zero-based index of the specified item in the List</returns>
/// <returns>The zero-based index of the specified item in the List</returns> public int IndexOf(TItem item) {
public int IndexOf(TItem item) { return this.typedList.IndexOf(item);
return this.typedList.IndexOf(item); }
}
/// <summary>Accesses the List item with the specified index</summary>
/// <summary>Accesses the List item with the specified index</summary> /// <param name="index">Zero-based index of the List item that will be accessed</param>
/// <param name="index">Zero-based index of the List item that will be accessed</param> public TItem this[int index] {
public TItem this[int index] { get { return this.typedList[index]; }
get { return this.typedList[index]; } }
}
/// <summary>Determines whether the List contains the specified item</summary>
/// <summary>Determines whether the List contains the specified item</summary> /// <param name="item">Item that will be checked for</param>
/// <param name="item">Item that will be checked for</param> /// <returns>True if the specified item is contained in the List</returns>
/// <returns>True if the specified item is contained in the List</returns> public bool Contains(TItem item) {
public bool Contains(TItem item) { return this.typedList.Contains(item);
return this.typedList.Contains(item); }
}
/// <summary>Copies the contents of the List into an array</summary>
/// <summary>Copies the contents of the List into an array</summary> /// <param name="array">Array the List will be copied into</param>
/// <param name="array">Array the List will be copied into</param> /// <param name="arrayIndex">
/// <param name="arrayIndex"> /// Starting index at which to begin filling the destination array
/// Starting index at which to begin filling the destination array /// </param>
/// </param> public void CopyTo(TItem[] array, int arrayIndex) {
public void CopyTo(TItem[] array, int arrayIndex) { this.typedList.CopyTo(array, arrayIndex);
this.typedList.CopyTo(array, arrayIndex); }
}
/// <summary>The number of items current contained in the list</summary>
/// <summary>The number of items current contained in the list</summary> public int Count {
public int Count { get { return this.typedList.Count; }
get { return this.typedList.Count; } }
}
/// <summary>Whether the list is write-protected</summary>
/// <summary>Whether the list is write-protected</summary> public bool IsReadOnly {
public bool IsReadOnly { get { return true; }
get { return true; } }
}
/// <summary>Returns a new enumerator over the contents of the list</summary>
/// <summary>Returns a new enumerator over the contents of the list</summary> /// <returns>The new list content enumerator</returns>
/// <returns>The new list content enumerator</returns> public IEnumerator<TItem> GetEnumerator() {
public IEnumerator<TItem> GetEnumerator() { return this.typedList.GetEnumerator();
return this.typedList.GetEnumerator(); }
}
#region IList<> implementation
#region IList<> implementation
/// <summary>Inserts an item into the list</summary>
/// <summary>Inserts an item into the list</summary> /// <param name="index">Zero-based index before which the item will be inserted</param>
/// <param name="index">Zero-based index before which the item will be inserted</param> /// <param name="item">Item that will be inserted into the list</param>
/// <param name="item">Item that will be inserted into the list</param> void IList<TItem>.Insert(int index, TItem item) {
void IList<TItem>.Insert(int index, TItem item) { throw new NotSupportedException(
throw new NotSupportedException( "Inserting items is not supported by the read-only list"
"Inserting items is not supported by the read-only list" );
); }
}
/// <summary>Removes an item from the list</summary>
/// <summary>Removes an item from the list</summary> /// <param name="index">Zero-based index of the item that will be removed</param>
/// <param name="index">Zero-based index of the item that will be removed</param> void IList<TItem>.RemoveAt(int index) {
void IList<TItem>.RemoveAt(int index) { throw new NotSupportedException(
throw new NotSupportedException( "Removing items is not supported by the read-only list"
"Removing items is not supported by the read-only list" );
); }
}
/// <summary>Accesses the list item with the specified index</summary>
/// <summary>Accesses the list item with the specified index</summary> /// <param name="index">Zero-based index of the list item that will be accessed</param>
/// <param name="index">Zero-based index of the list item that will be accessed</param> TItem IList<TItem>.this[int index] {
TItem IList<TItem>.this[int index] { get { return this.typedList[index]; }
get { return this.typedList[index]; } set {
set { throw new NotSupportedException(
throw new NotSupportedException( "Assigning items is not supported by the read-only list"
"Assigning items is not supported by the read-only list" );
); }
} }
}
#endregion
#endregion
#region ICollection<> implementation
#region ICollection<> implementation
/// <summary>Adds an item to the end of the list</summary>
/// <summary>Adds an item to the end of the list</summary> /// <param name="item">Item that will be added to the list</param>
/// <param name="item">Item that will be added to the list</param> void ICollection<TItem>.Add(TItem item) {
void ICollection<TItem>.Add(TItem item) { throw new NotSupportedException(
throw new NotSupportedException( "Adding items is not supported by the read-only list"
"Adding items is not supported by the read-only list" );
); }
}
/// <summary>Removes all items from the List</summary>
/// <summary>Removes all items from the List</summary> void ICollection<TItem>.Clear() {
void ICollection<TItem>.Clear() { throw new NotSupportedException(
throw new NotSupportedException( "Clearing is not supported by the read-only list"
"Clearing is not supported by the read-only list" );
); }
}
/// <summary>Removes the specified item from the list</summary>
/// <summary>Removes the specified item from the list</summary> /// <param name="item">Item that will be removed from the list</param>
/// <param name="item">Item that will be removed from the list</param> /// <returns>True of the specified item was found in the list and removed</returns>
/// <returns>True of the specified item was found in the list and removed</returns> bool ICollection<TItem>.Remove(TItem item) {
bool ICollection<TItem>.Remove(TItem item) { throw new NotSupportedException(
throw new NotSupportedException( "Removing items is not supported by the read-only list"
"Removing items is not supported by the read-only list" );
); }
}
#endregion
#endregion
#region IEnumerable implementation
#region IEnumerable implementation
/// <summary>Returns a new enumerator over the contents of the list</summary>
/// <summary>Returns a new enumerator over the contents of the list</summary> /// <returns>The new list content enumerator</returns>
/// <returns>The new list content enumerator</returns> IEnumerator IEnumerable.GetEnumerator() {
IEnumerator IEnumerable.GetEnumerator() { return this.objectList.GetEnumerator();
return this.objectList.GetEnumerator(); }
}
#endregion
#endregion
#region IList implementation
#region IList implementation
/// <summary>Removes all items from the list</summary>
/// <summary>Removes all items from the list</summary> void IList.Clear() {
void IList.Clear() { throw new NotSupportedException(
throw new NotSupportedException( "Clearing is not supported by the read-only list"
"Clearing is not supported by the read-only list" );
); }
}
/// <summary>Adds an item to the end of the list</summary>
/// <summary>Adds an item to the end of the list</summary> /// <param name="value">Item that will be added to the list</param>
/// <param name="value">Item that will be added to the list</param> int IList.Add(object value) {
int IList.Add(object value) { throw new NotSupportedException(
throw new NotSupportedException( "Adding items is not supported by the read-only list"
"Adding items is not supported by the read-only list" );
); }
}
/// <summary>Determines whether the List contains the specified item</summary>
/// <summary>Determines whether the List contains the specified item</summary> /// <param name="value">Item that will be checked for</param>
/// <param name="value">Item that will be checked for</param> /// <returns>True if the specified item is contained in the list</returns>
/// <returns>True if the specified item is contained in the list</returns> bool IList.Contains(object value) {
bool IList.Contains(object value) { return this.objectList.Contains(value);
return this.objectList.Contains(value); }
}
/// <summary>Retrieves the index of an item within the list</summary>
/// <summary>Retrieves the index of an item within the list</summary> /// <param name="value">Item whose index will be returned</param>
/// <param name="value">Item whose index will be returned</param> /// <returns>The zero-based index of the specified item in the list</returns>
/// <returns>The zero-based index of the specified item in the list</returns> int IList.IndexOf(object value) {
int IList.IndexOf(object value) { return this.objectList.IndexOf(value);
return this.objectList.IndexOf(value); }
}
/// <summary>Inserts an item into the list</summary>
/// <summary>Inserts an item into the list</summary> /// <param name="index">Zero-based index before which the item will be inserted</param>
/// <param name="index">Zero-based index before which the item will be inserted</param> /// <param name="value">Item that will be inserted into the list</param>
/// <param name="value">Item that will be inserted into the list</param> void IList.Insert(int index, object value) {
void IList.Insert(int index, object value) { throw new NotSupportedException(
throw new NotSupportedException( "Inserting items is not supported by the read-only list"
"Inserting items is not supported by the read-only list" );
); }
}
/// <summary>Whether the size of the list is fixed</summary>
/// <summary>Whether the size of the list is fixed</summary> bool IList.IsFixedSize {
bool IList.IsFixedSize { get { return this.objectList.IsFixedSize; }
get { return this.objectList.IsFixedSize; } }
}
/// <summary>Removes the specified item from the list</summary>
/// <summary>Removes the specified item from the list</summary> /// <param name="value">Item that will be removed from the list</param>
/// <param name="value">Item that will be removed from the list</param> /// <returns>True of the specified item was found in the list and removed</returns>
/// <returns>True of the specified item was found in the list and removed</returns> void IList.Remove(object value) {
void IList.Remove(object value) { throw new NotSupportedException(
throw new NotSupportedException( "Removing items is not supported by the read-only list"
"Removing items is not supported by the read-only list" );
); }
}
/// <summary>Removes an item from the list</summary>
/// <summary>Removes an item from the list</summary> /// <param name="index">Zero-based index of the item that will be removed</param>
/// <param name="index">Zero-based index of the item that will be removed</param> void IList.RemoveAt(int index) {
void IList.RemoveAt(int index) { throw new NotSupportedException(
throw new NotSupportedException( "Removing items is not supported by the read-only list"
"Removing items is not supported by the read-only list" );
); }
}
/// <summary>Accesses the list item with the specified index</summary>
/// <summary>Accesses the list item with the specified index</summary> /// <param name="index">Zero-based index of the list item that will be accessed</param>
/// <param name="index">Zero-based index of the list item that will be accessed</param> object IList.this[int index] {
object IList.this[int index] { get { return this.objectList[index]; }
get { return this.objectList[index]; } set {
set { throw new NotSupportedException(
throw new NotSupportedException( "Assigning items is not supported by the read-only list"
"Assigning items is not supported by the read-only list" );
); }
} }
}
#endregion
#endregion
#region ICollection implementation
#region ICollection implementation
/// <summary>Copies the contents of the list into an array</summary>
/// <summary>Copies the contents of the list into an array</summary> /// <param name="array">Array the list will be copied into</param>
/// <param name="array">Array the list will be copied into</param> /// <param name="index">
/// <param name="index"> /// Starting index at which to begin filling the destination array
/// Starting index at which to begin filling the destination array /// </param>
/// </param> void ICollection.CopyTo(Array array, int index) {
void ICollection.CopyTo(Array array, int index) { this.objectList.CopyTo(array, index);
this.objectList.CopyTo(array, index); }
}
/// <summary>Whether the list is synchronized for multi-threaded usage</summary>
/// <summary>Whether the list is synchronized for multi-threaded usage</summary> bool ICollection.IsSynchronized {
bool ICollection.IsSynchronized { get { return this.objectList.IsSynchronized; }
get { return this.objectList.IsSynchronized; } }
}
/// <summary>Synchronization root on which the list locks</summary>
/// <summary>Synchronization root on which the list locks</summary> object ICollection.SyncRoot {
object ICollection.SyncRoot { get { return this.objectList.SyncRoot; }
get { return this.objectList.SyncRoot; } }
}
#endregion
#endregion
/// <summary>The wrapped list under its type-safe interface</summary>
/// <summary>The wrapped list under its type-safe interface</summary> private IList<TItem> typedList;
private IList<TItem> typedList; /// <summary>The wrapped list under its object interface</summary>
/// <summary>The wrapped list under its object interface</summary> private IList objectList;
private IList objectList;
}
}
} // namespace Nuclex.Support.Collections
} // namespace Nuclex.Support.Collections

View File

@ -1,208 +1,207 @@
#region CPL License #region Apache License 2.0
/* /*
Nuclex Framework Nuclex .NET Framework
Copyright (C) 2002-2017 Nuclex Development Labs Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
This library is free software; you can redistribute it and/or Licensed under the Apache License, Version 2.0 (the "License");
modify it under the terms of the IBM Common Public License as you may not use this file except in compliance with the License.
published by the IBM Corporation; either version 1.0 of the You may obtain a copy of the License at
License, or (at your option) any later version.
http://www.apache.org/licenses/LICENSE-2.0
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of Unless required by applicable law or agreed to in writing, software
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the distributed under the License is distributed on an "AS IS" BASIS,
IBM Common Public License for more details. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
You should have received a copy of the IBM Common Public limitations under the License.
License along with this library */
*/ #endregion // Apache License 2.0
#endregion
#if !NO_SETS
#if !NO_SETS
#if UNITTEST
#if UNITTEST
using System;
using System; using System.Collections;
using System.Collections; using System.Collections.Generic;
using System.Collections.Generic; using System.IO;
using System.IO; using System.Runtime.Serialization.Formatters.Binary;
using System.Runtime.Serialization.Formatters.Binary;
using NUnit.Framework;
using NUnit.Framework; using NMock;
using NMock;
namespace Nuclex.Support.Collections {
namespace Nuclex.Support.Collections {
/// <summary>Unit Test for the observable set wrapper</summary>
/// <summary>Unit Test for the observable set wrapper</summary> [TestFixture]
[TestFixture] internal class ReadOnlySetTest {
internal class ReadOnlySetTest {
/// <summary>Called before each test is run</summary>
/// <summary>Called before each test is run</summary> [SetUp]
[SetUp] public void Setup() {
public void Setup() { this.set = new HashSet<int>();
this.set = new HashSet<int>(); this.readOnlySet = new ReadOnlySet<int>(this.set);
this.readOnlySet = new ReadOnlySet<int>(this.set); }
}
/// <summary>
/// <summary> /// Verifies that the observable set has a default constructor
/// Verifies that the observable set has a default constructor /// </summary>
/// </summary> [Test]
[Test] public void HasDefaultConstructor() {
public void HasDefaultConstructor() { Assert.IsNotNull(new ReadOnlySet<int>(new HashSet<int>()));
Assert.IsNotNull(new ReadOnlySet<int>(new HashSet<int>())); }
}
/// <summary>
/// <summary> /// Verifies that an exception is thrown upon any attempt to add items
/// Verifies that an exception is thrown upon any attempt to add items /// to a read-only set
/// to a read-only set /// </summary>
/// </summary> [Test]
[Test] public void AddingThrowsException() {
public void AddingThrowsException() { Assert.Throws<NotSupportedException>(
Assert.Throws<NotSupportedException>( delegate() { ((ISet<int>)this.readOnlySet).Add(123); }
delegate() { ((ISet<int>)this.readOnlySet).Add(123); } );
); }
}
/// <summary>
/// <summary> /// Verifies that an exception is thrown upon any attempt to remove items
/// Verifies that an exception is thrown upon any attempt to remove items /// from a read-only set
/// from a read-only set /// </summary>
/// </summary> [Test]
[Test] public void RemovingThrowsException() {
public void RemovingThrowsException() { Assert.Throws<NotSupportedException>(
Assert.Throws<NotSupportedException>( delegate() { ((ISet<int>)this.readOnlySet).Remove(123); }
delegate() { ((ISet<int>)this.readOnlySet).Remove(123); } );
); }
}
/// <summary>
/// <summary> /// Verifies that an exception is thrown upon any attempt to except
/// Verifies that an exception is thrown upon any attempt to except /// the set with another set
/// the set with another set /// </summary>
/// </summary> [Test]
[Test] public void ExceptingThrowsException() {
public void ExceptingThrowsException() { Assert.Throws<NotSupportedException>(
Assert.Throws<NotSupportedException>( delegate() { ((ISet<int>)this.readOnlySet).ExceptWith(null); }
delegate() { ((ISet<int>)this.readOnlySet).ExceptWith(null); } );
); }
}
/// <summary>
/// <summary> /// Verifies that an exception is thrown upon any attempt to intersect
/// Verifies that an exception is thrown upon any attempt to intersect /// the set with another set
/// the set with another set /// </summary>
/// </summary> [Test]
[Test] public void InsersectThrowsException() {
public void InsersectThrowsException() { Assert.Throws<NotSupportedException>(
Assert.Throws<NotSupportedException>( delegate() { ((ISet<int>)this.readOnlySet).IntersectWith(null); }
delegate() { ((ISet<int>)this.readOnlySet).IntersectWith(null); } );
); }
}
/// <summary>
/// <summary> /// Verifies that it's possible to determine whether a set is a proper subset
/// Verifies that it's possible to determine whether a set is a proper subset /// or superset of another set
/// or superset of another set /// </summary>
/// </summary> [Test]
[Test] public void CanDetermineProperSubsetAndSuperset() {
public void CanDetermineProperSubsetAndSuperset() { this.set.Add(1);
this.set.Add(1); this.set.Add(2);
this.set.Add(2); this.set.Add(3);
this.set.Add(3);
var set2 = new HashSet<int>() { 1, 3 };
var set2 = new HashSet<int>() { 1, 3 };
Assert.IsTrue(this.readOnlySet.IsProperSupersetOf(set2));
Assert.IsTrue(this.readOnlySet.IsProperSupersetOf(set2)); Assert.IsTrue(set2.IsProperSubsetOf(this.readOnlySet));
Assert.IsTrue(set2.IsProperSubsetOf(this.readOnlySet));
set2.Add(2);
set2.Add(2);
Assert.IsFalse(this.readOnlySet.IsProperSupersetOf(set2));
Assert.IsFalse(this.readOnlySet.IsProperSupersetOf(set2)); Assert.IsFalse(set2.IsProperSubsetOf(this.readOnlySet));
Assert.IsFalse(set2.IsProperSubsetOf(this.readOnlySet)); }
}
/// <summary>
/// <summary> /// Verifies that it's possible to determine whether a set is a subset
/// Verifies that it's possible to determine whether a set is a subset /// or a superset of another set
/// or a superset of another set /// </summary>
/// </summary> [Test]
[Test] public void CanDetermineSubsetAndSuperset() {
public void CanDetermineSubsetAndSuperset() { this.set.Add(1);
this.set.Add(1); this.set.Add(2);
this.set.Add(2); this.set.Add(3);
this.set.Add(3);
var set2 = new HashSet<int>() { 1, 2, 3 };
var set2 = new HashSet<int>() { 1, 2, 3 };
Assert.IsTrue(this.readOnlySet.IsSupersetOf(set2));
Assert.IsTrue(this.readOnlySet.IsSupersetOf(set2)); Assert.IsTrue(set2.IsSubsetOf(this.readOnlySet));
Assert.IsTrue(set2.IsSubsetOf(this.readOnlySet));
set2.Add(4);
set2.Add(4);
Assert.IsFalse(this.readOnlySet.IsSupersetOf(set2));
Assert.IsFalse(this.readOnlySet.IsSupersetOf(set2)); Assert.IsFalse(set2.IsSubsetOf(this.readOnlySet));
Assert.IsFalse(set2.IsSubsetOf(this.readOnlySet)); }
}
/// <summary>
/// <summary> /// Verifies that a set can determine if another set overlaps with it
/// Verifies that a set can determine if another set overlaps with it /// </summary>
/// </summary> [Test]
[Test] public void CanDetermineOverlap() {
public void CanDetermineOverlap() { this.set.Add(1);
this.set.Add(1); this.set.Add(3);
this.set.Add(3); this.set.Add(5);
this.set.Add(5);
var set2 = new HashSet<int>() { 3 };
var set2 = new HashSet<int>() { 3 };
Assert.IsTrue(this.readOnlySet.Overlaps(set2));
Assert.IsTrue(this.readOnlySet.Overlaps(set2)); Assert.IsTrue(set2.Overlaps(this.readOnlySet));
Assert.IsTrue(set2.Overlaps(this.readOnlySet)); }
}
/// <summary>
/// <summary> /// Verifies that a set can determine if another set contains the same elements
/// Verifies that a set can determine if another set contains the same elements /// </summary>
/// </summary> [Test]
[Test] public void CanDetermineSetEquality() {
public void CanDetermineSetEquality() { this.set.Add(1);
this.set.Add(1); this.set.Add(3);
this.set.Add(3); this.set.Add(5);
this.set.Add(5);
var set2 = new HashSet<int>() { 3, 1, 5 };
var set2 = new HashSet<int>() { 3, 1, 5 };
Assert.IsTrue(this.readOnlySet.SetEquals(set2));
Assert.IsTrue(this.readOnlySet.SetEquals(set2)); Assert.IsTrue(set2.SetEquals(this.readOnlySet));
Assert.IsTrue(set2.SetEquals(this.readOnlySet));
this.set.Add(7);
this.set.Add(7);
Assert.IsFalse(this.readOnlySet.SetEquals(set2));
Assert.IsFalse(this.readOnlySet.SetEquals(set2)); Assert.IsFalse(set2.SetEquals(this.readOnlySet));
Assert.IsFalse(set2.SetEquals(this.readOnlySet)); }
}
/// <summary>
/// <summary> /// Verifies that any attempt to symmetrically except a read-only set
/// Verifies that any attempt to symmetrically except a read-only set /// causes an exception
/// causes an exception /// </summary>
/// </summary> [Test]
[Test] public void SymmetricallyExceptingThrowsException() {
public void SymmetricallyExceptingThrowsException() { Assert.Throws<NotSupportedException>(
Assert.Throws<NotSupportedException>( delegate() { ((ISet<int>)this.readOnlySet).SymmetricExceptWith(null); }
delegate() { ((ISet<int>)this.readOnlySet).SymmetricExceptWith(null); } );
); }
}
/// <summary>
/// <summary> /// Verifies that any attempt to union a read-only set causes an exception
/// Verifies that any attempt to union a read-only set causes an exception /// </summary>
/// </summary> [Test]
[Test] public void UnioningThrowsException() {
public void UnioningThrowsException() { Assert.Throws<NotSupportedException>(
Assert.Throws<NotSupportedException>( delegate() { ((ISet<int>)this.readOnlySet).UnionWith(null); }
delegate() { ((ISet<int>)this.readOnlySet).UnionWith(null); } );
); }
}
/// <summary>Set being wrapped in a read-only set</summary>
/// <summary>Set being wrapped in a read-only set</summary> private ISet<int> set;
private ISet<int> set; /// <summary>Read-only wrapper around the set</summary>
/// <summary>Read-only wrapper around the set</summary> private ReadOnlySet<int> readOnlySet;
private ReadOnlySet<int> readOnlySet;
}
}
} // namespace Nuclex.Support.Collections
} // namespace Nuclex.Support.Collections
#endif // UNITTEST
#endif // UNITTEST
#endif // !NO_SETS
#endif // !NO_SETS

View File

@ -1,216 +1,215 @@
#region CPL License #region Apache License 2.0
/* /*
Nuclex Framework Nuclex .NET Framework
Copyright (C) 2002-2017 Nuclex Development Labs Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
This library is free software; you can redistribute it and/or Licensed under the Apache License, Version 2.0 (the "License");
modify it under the terms of the IBM Common Public License as you may not use this file except in compliance with the License.
published by the IBM Corporation; either version 1.0 of the You may obtain a copy of the License at
License, or (at your option) any later version.
http://www.apache.org/licenses/LICENSE-2.0
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of Unless required by applicable law or agreed to in writing, software
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the distributed under the License is distributed on an "AS IS" BASIS,
IBM Common Public License for more details. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
You should have received a copy of the IBM Common Public limitations under the License.
License along with this library */
*/ #endregion // Apache License 2.0
#endregion
using System;
using System; using System.Collections;
using System.Collections; using System.Collections.Generic;
using System.Collections.Generic;
#if !NO_SETS
#if !NO_SETS
namespace Nuclex.Support.Collections {
namespace Nuclex.Support.Collections {
/// <summary>Wraps a set and prevents it from being modified</summary>
/// <summary>Wraps a set and prevents it from being modified</summary> /// <typeparam name="TItem">Type of items to manage in the set</typeparam>
/// <typeparam name="TItem">Type of items to manage in the set</typeparam> public class ReadOnlySet<TItem> : ISet<TItem>, ICollection<TItem> {
public class ReadOnlySet<TItem> : ISet<TItem>, ICollection<TItem> {
/// <summary>
/// <summary> /// Initializes a new observable set forwarding operations to the specified set
/// Initializes a new observable set forwarding operations to the specified set /// </summary>
/// </summary> /// <param name="set">Set operations will be forwarded to</param>
/// <param name="set">Set operations will be forwarded to</param> public ReadOnlySet(ISet<TItem> set) {
public ReadOnlySet(ISet<TItem> set) { this.set = set;
this.set = set; }
}
/// <summary>
/// <summary> /// Determines whether the current set is a proper (strict) subset of a collection
/// Determines whether the current set is a proper (strict) subset of a collection /// </summary>
/// </summary> /// <param name="other">Collection against which the set will be tested</param>
/// <param name="other">Collection against which the set will be tested</param> /// <returns>True if the set is a proper subset of the specified collection</returns>
/// <returns>True if the set is a proper subset of the specified collection</returns> public bool IsProperSubsetOf(IEnumerable<TItem> other) {
public bool IsProperSubsetOf(IEnumerable<TItem> other) { return this.set.IsProperSubsetOf(other);
return this.set.IsProperSubsetOf(other); }
}
/// <summary>
/// <summary> /// Determines whether the current set is a proper (strict) superset of a collection
/// Determines whether the current set is a proper (strict) superset of a collection /// </summary>
/// </summary> /// <param name="other">Collection against which the set will be tested</param>
/// <param name="other">Collection against which the set will be tested</param> /// <returns>True if the set is a proper superset of the specified collection</returns>
/// <returns>True if the set is a proper superset of the specified collection</returns> public bool IsProperSupersetOf(IEnumerable<TItem> other) {
public bool IsProperSupersetOf(IEnumerable<TItem> other) { return this.set.IsProperSupersetOf(other);
return this.set.IsProperSupersetOf(other); }
}
/// <summary>Determines whether the current set is a subset of a collection</summary>
/// <summary>Determines whether the current set is a subset of a collection</summary> /// <param name="other">Collection against which the set will be tested</param>
/// <param name="other">Collection against which the set will be tested</param> /// <returns>True if the set is a subset of the specified collection</returns>
/// <returns>True if the set is a subset of the specified collection</returns> public bool IsSubsetOf(IEnumerable<TItem> other) {
public bool IsSubsetOf(IEnumerable<TItem> other) { return this.set.IsSubsetOf(other);
return this.set.IsSubsetOf(other); }
}
/// <summary>Determines whether the current set is a superset of a collection</summary>
/// <summary>Determines whether the current set is a superset of a collection</summary> /// <param name="other">Collection against which the set will be tested</param>
/// <param name="other">Collection against which the set will be tested</param> /// <returns>True if the set is a superset of the specified collection</returns>
/// <returns>True if the set is a superset of the specified collection</returns> public bool IsSupersetOf(IEnumerable<TItem> other) {
public bool IsSupersetOf(IEnumerable<TItem> other) { return this.set.IsSupersetOf(other);
return this.set.IsSupersetOf(other); }
}
/// <summary>
/// <summary> /// Determines if the set shares at least one common element with the collection
/// Determines if the set shares at least one common element with the collection /// </summary>
/// </summary> /// <param name="other">Collection the set will be tested against</param>
/// <param name="other">Collection the set will be tested against</param> /// <returns>
/// <returns> /// True if the set shares at least one common element with the collection
/// True if the set shares at least one common element with the collection /// </returns>
/// </returns> public bool Overlaps(IEnumerable<TItem> other) {
public bool Overlaps(IEnumerable<TItem> other) { return this.set.Overlaps(other);
return this.set.Overlaps(other); }
}
/// <summary>
/// <summary> /// Determines whether the set contains the same elements as the specified collection
/// Determines whether the set contains the same elements as the specified collection /// </summary>
/// </summary> /// <param name="other">Collection the set will be tested against</param>
/// <param name="other">Collection the set will be tested against</param> /// <returns>True if the set contains the same elements as the collection</returns>
/// <returns>True if the set contains the same elements as the collection</returns> public bool SetEquals(IEnumerable<TItem> other) {
public bool SetEquals(IEnumerable<TItem> other) { return this.set.SetEquals(other);
return this.set.SetEquals(other); }
}
/// <summary>Determines whether the set contains the specified item</summary>
/// <summary>Determines whether the set contains the specified item</summary> /// <param name="item">Item the set will be tested for</param>
/// <param name="item">Item the set will be tested for</param> /// <returns>True if the set contains the specified item</returns>
/// <returns>True if the set contains the specified item</returns> public bool Contains(TItem item) {
public bool Contains(TItem item) { return this.set.Contains(item);
return this.set.Contains(item); }
}
/// <summary>Copies the contents of the set into an array</summary>
/// <summary>Copies the contents of the set into an array</summary> /// <param name="array">Array the set's contents will be copied to</param>
/// <param name="array">Array the set's contents will be copied to</param> /// <param name="arrayIndex">
/// <param name="arrayIndex"> /// Index in the array the first copied element will be written to
/// Index in the array the first copied element will be written to /// </param>
/// </param> public void CopyTo(TItem[] array, int arrayIndex) {
public void CopyTo(TItem[] array, int arrayIndex) { this.set.CopyTo(array, arrayIndex);
this.set.CopyTo(array, arrayIndex); }
}
/// <summary>Counts the number of items contained in the set</summary>
/// <summary>Counts the number of items contained in the set</summary> public int Count {
public int Count { get { return this.set.Count; }
get { return this.set.Count; } }
}
/// <summary>Determines whether the set is readonly</summary>
/// <summary>Determines whether the set is readonly</summary> public bool IsReadOnly {
public bool IsReadOnly { get { return true; }
get { return true; } }
}
/// <summary>Creates an enumerator for the set's contents</summary>
/// <summary>Creates an enumerator for the set's contents</summary> /// <returns>A new enumerator for the sets contents</returns>
/// <returns>A new enumerator for the sets contents</returns> public IEnumerator<TItem> GetEnumerator() {
public IEnumerator<TItem> GetEnumerator() { return this.set.GetEnumerator();
return this.set.GetEnumerator(); }
}
/// <summary>Creates an enumerator for the set's contents</summary>
/// <summary>Creates an enumerator for the set's contents</summary> /// <returns>A new enumerator for the sets contents</returns>
/// <returns>A new enumerator for the sets contents</returns> IEnumerator IEnumerable.GetEnumerator() {
IEnumerator IEnumerable.GetEnumerator() { return this.set.GetEnumerator();
return this.set.GetEnumerator(); }
}
/// <summary>
/// <summary> /// Modifies the current set so that it contains only elements that are present either
/// Modifies the current set so that it contains only elements that are present either /// in the current set or in the specified collection, but not both
/// in the current set or in the specified collection, but not both /// </summary>
/// </summary> /// <param name="other">Collection the set will be excepted with</param>
/// <param name="other">Collection the set will be excepted with</param> void ISet<TItem>.SymmetricExceptWith(IEnumerable<TItem> other) {
void ISet<TItem>.SymmetricExceptWith(IEnumerable<TItem> other) { throw new NotSupportedException(
throw new NotSupportedException( "Excepting is not supported by the read-only set"
"Excepting is not supported by the read-only set" );
); }
}
/// <summary>
/// <summary> /// Modifies the current set so that it contains all elements that are present in both
/// Modifies the current set so that it contains all elements that are present in both /// the current set and in the specified collection
/// the current set and in the specified collection /// </summary>
/// </summary> /// <param name="other">Collection an union will be built with</param>
/// <param name="other">Collection an union will be built with</param> void ISet<TItem>.UnionWith(IEnumerable<TItem> other) {
void ISet<TItem>.UnionWith(IEnumerable<TItem> other) { throw new NotSupportedException(
throw new NotSupportedException( "Unioning is not supported by the read-only set"
"Unioning is not supported by the read-only set" );
); }
}
/// <summary>Removes all items from the set</summary>
/// <summary>Removes all items from the set</summary> public void Clear() {
public void Clear() { throw new NotSupportedException(
throw new NotSupportedException( "Clearing is not supported by the read-only set"
"Clearing is not supported by the read-only set" );
); }
}
/// <summary>Removes an item from the set</summary>
/// <summary>Removes an item from the set</summary> /// <param name="item">Item that will be removed from the set</param>
/// <param name="item">Item that will be removed from the set</param> /// <returns>
/// <returns> /// True if the item was contained in the set and is now removed
/// True if the item was contained in the set and is now removed /// </returns>
/// </returns> bool ICollection<TItem>.Remove(TItem item) {
bool ICollection<TItem>.Remove(TItem item) { throw new NotSupportedException(
throw new NotSupportedException( "Removing items is not supported by the read-only set"
"Removing items is not supported by the read-only set" );
); }
}
/// <summary>Adds an item to the set</summary>
/// <summary>Adds an item to the set</summary> /// <param name="item">Item that will be added to the set</param>
/// <param name="item">Item that will be added to the set</param> /// <returns>
/// <returns> /// True if the element was added, false if it was already contained in the set
/// True if the element was added, false if it was already contained in the set /// </returns>
/// </returns> bool ISet<TItem>.Add(TItem item) {
bool ISet<TItem>.Add(TItem item) { throw new NotSupportedException(
throw new NotSupportedException( "Adding items is not supported by the read-only set"
"Adding items is not supported by the read-only set" );
); }
}
/// <summary>Removes all elements that are contained in the collection</summary>
/// <summary>Removes all elements that are contained in the collection</summary> /// <param name="other">Collection whose elements will be removed from this set</param>
/// <param name="other">Collection whose elements will be removed from this set</param> void ISet<TItem>.ExceptWith(IEnumerable<TItem> other) {
void ISet<TItem>.ExceptWith(IEnumerable<TItem> other) { throw new NotSupportedException(
throw new NotSupportedException( "Excepting items is not supported by the read-only set"
"Excepting items is not supported by the read-only set" );
); }
}
/// <summary>
/// <summary> /// Only keeps those elements in this set that are contained in the collection
/// Only keeps those elements in this set that are contained in the collection /// </summary>
/// </summary> /// <param name="other">Other set this set will be filtered by</param>
/// <param name="other">Other set this set will be filtered by</param> void ISet<TItem>.IntersectWith(IEnumerable<TItem> other) {
void ISet<TItem>.IntersectWith(IEnumerable<TItem> other) { throw new NotSupportedException(
throw new NotSupportedException( "Intersecting items is not supported by the read-only set"
"Intersecting items is not supported by the read-only set" );
); }
}
/// <summary>Adds an item to the set</summary>
/// <summary>Adds an item to the set</summary> /// <param name="item">Item that will be added to the set</param>
/// <param name="item">Item that will be added to the set</param> void ICollection<TItem>.Add(TItem item) {
void ICollection<TItem>.Add(TItem item) { throw new NotSupportedException(
throw new NotSupportedException( "Adding is not supported by the read-only set"
"Adding is not supported by the read-only set" );
); }
}
/// <summary>The set being wrapped</summary>
/// <summary>The set being wrapped</summary> private ISet<TItem> set;
private ISet<TItem> set;
}
}
} // namespace Nuclex.Support.Collections
} // namespace Nuclex.Support.Collections
#endif // !NO_SETS
#endif // !NO_SETS

View File

@ -1,117 +1,116 @@
#region CPL License #region Apache License 2.0
/* /*
Nuclex Framework Nuclex .NET Framework
Copyright (C) 2002-2017 Nuclex Development Labs Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
This library is free software; you can redistribute it and/or Licensed under the Apache License, Version 2.0 (the "License");
modify it under the terms of the IBM Common Public License as you may not use this file except in compliance with the License.
published by the IBM Corporation; either version 1.0 of the You may obtain a copy of the License at
License, or (at your option) any later version.
http://www.apache.org/licenses/LICENSE-2.0
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of Unless required by applicable law or agreed to in writing, software
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the distributed under the License is distributed on an "AS IS" BASIS,
IBM Common Public License for more details. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
You should have received a copy of the IBM Common Public limitations under the License.
License along with this library */
*/ #endregion // Apache License 2.0
#endregion
using System;
using System; using System.Collections.Generic;
using System.Collections.Generic;
#if UNITTEST
#if UNITTEST
using NUnit.Framework;
using NUnit.Framework;
namespace Nuclex.Support.Collections {
namespace Nuclex.Support.Collections {
/// <summary>Unit Test for the ReverseComparer helper class</summary>
/// <summary>Unit Test for the ReverseComparer helper class</summary> [TestFixture]
[TestFixture] internal class ReverseComparerTest {
internal class ReverseComparerTest {
#region class FortyTwoComparer
#region class FortyTwoComparer
/// <summary>Special comparer in which 42 is larger than everything</summary>
/// <summary>Special comparer in which 42 is larger than everything</summary> private class FortyTwoComparer : IComparer<int> {
private class FortyTwoComparer : IComparer<int> {
/// <summary>Compares the left value to the right value</summary>
/// <summary>Compares the left value to the right value</summary> /// <param name="left">Value on the left side</param>
/// <param name="left">Value on the left side</param> /// <param name="right">Value on the right side</param>
/// <param name="right">Value on the right side</param> /// <returns>The relationship of the two values</returns>
/// <returns>The relationship of the two values</returns> public int Compare(int left, int right) {
public int Compare(int left, int right) {
// Is there a 42 in the arguments?
// Is there a 42 in the arguments? if(left == 42) {
if(left == 42) { if(right == 42) {
if(right == 42) { return 0; // both are equal
return 0; // both are equal } else {
} else { return +1; // left is larger
return +1; // left is larger }
} } else if(right == 42) {
} else if(right == 42) { return -1; // right is larger
return -1; // right is larger }
}
// No 42 encountered, proceed as normal
// No 42 encountered, proceed as normal return Math.Sign(left - right);
return Math.Sign(left - right);
}
}
}
}
#endregion // class FortyTwoComparer
#endregion // class FortyTwoComparer
/// <summary>
/// <summary> /// Tests whether the default constructor of the reverse comparer works
/// Tests whether the default constructor of the reverse comparer works /// </summary>
/// </summary> [Test]
[Test] public void TestDefaultConstructor() {
public void TestDefaultConstructor() { new ReverseComparer<int>();
new ReverseComparer<int>(); }
}
/// <summary>
/// <summary> /// Tests whether the full constructor of the reverse comparer works
/// Tests whether the full constructor of the reverse comparer works /// </summary>
/// </summary> [Test]
[Test] public void TestFullConstructor() {
public void TestFullConstructor() { new ReverseComparer<int>(new FortyTwoComparer());
new ReverseComparer<int>(new FortyTwoComparer()); }
}
/// <summary>
/// <summary> /// Tests whether the full constructor of the reverse comparer works
/// Tests whether the full constructor of the reverse comparer works /// </summary>
/// </summary> [Test]
[Test] public void TestReversedDefaultComparer() {
public void TestReversedDefaultComparer() { Comparer<int> comparer = Comparer<int>.Default;
Comparer<int> comparer = Comparer<int>.Default; ReverseComparer<int> reverseComparer = new ReverseComparer<int>(comparer);
ReverseComparer<int> reverseComparer = new ReverseComparer<int>(comparer);
Assert.Greater(0, comparer.Compare(10, 20));
Assert.Greater(0, comparer.Compare(10, 20)); Assert.Less(0, comparer.Compare(20, 10));
Assert.Less(0, comparer.Compare(20, 10));
Assert.Less(0, reverseComparer.Compare(10, 20));
Assert.Less(0, reverseComparer.Compare(10, 20)); Assert.Greater(0, reverseComparer.Compare(20, 10));
Assert.Greater(0, reverseComparer.Compare(20, 10)); }
}
/// <summary>
/// <summary> /// Tests whether the full constructor of the reverse comparer works
/// Tests whether the full constructor of the reverse comparer works /// </summary>
/// </summary> [Test]
[Test] public void TestReversedCustomComparer() {
public void TestReversedCustomComparer() { FortyTwoComparer fortyTwoComparer = new FortyTwoComparer();
FortyTwoComparer fortyTwoComparer = new FortyTwoComparer(); ReverseComparer<int> reverseFortyTwoComparer = new ReverseComparer<int>(
ReverseComparer<int> reverseFortyTwoComparer = new ReverseComparer<int>( fortyTwoComparer
fortyTwoComparer );
);
Assert.Less(0, fortyTwoComparer.Compare(42, 84));
Assert.Less(0, fortyTwoComparer.Compare(42, 84)); Assert.Greater(0, fortyTwoComparer.Compare(84, 42));
Assert.Greater(0, fortyTwoComparer.Compare(84, 42));
Assert.Greater(0, reverseFortyTwoComparer.Compare(42, 84));
Assert.Greater(0, reverseFortyTwoComparer.Compare(42, 84)); Assert.Less(0, reverseFortyTwoComparer.Compare(84, 42));
Assert.Less(0, reverseFortyTwoComparer.Compare(84, 42)); }
}
}
}
} // namespace Nuclex.Support.Collections
} // namespace Nuclex.Support.Collections
#endif // UNITTEST
#endif // UNITTEST

View File

@ -1,56 +1,55 @@
#region CPL License #region Apache License 2.0
/* /*
Nuclex Framework Nuclex .NET Framework
Copyright (C) 2002-2017 Nuclex Development Labs Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
This library is free software; you can redistribute it and/or Licensed under the Apache License, Version 2.0 (the "License");
modify it under the terms of the IBM Common Public License as you may not use this file except in compliance with the License.
published by the IBM Corporation; either version 1.0 of the You may obtain a copy of the License at
License, or (at your option) any later version.
http://www.apache.org/licenses/LICENSE-2.0
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of Unless required by applicable law or agreed to in writing, software
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the distributed under the License is distributed on an "AS IS" BASIS,
IBM Common Public License for more details. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
You should have received a copy of the IBM Common Public limitations under the License.
License along with this library */
*/ #endregion // Apache License 2.0
#endregion
using System;
using System; using System.Collections.Generic;
using System.Collections.Generic;
namespace Nuclex.Support.Collections {
namespace Nuclex.Support.Collections {
/// <summary>
/// <summary> /// Compares two values in reverse or reverses the output of another comparer
/// Compares two values in reverse or reverses the output of another comparer /// </summary>
/// </summary> /// <typeparam name="TCompared">Type of values to be compared</typeparam>
/// <typeparam name="TCompared">Type of values to be compared</typeparam> public class ReverseComparer<TCompared> : IComparer<TCompared> {
public class ReverseComparer<TCompared> : IComparer<TCompared> {
/// <summary>Initializes a new reverse comparer</summary>
/// <summary>Initializes a new reverse comparer</summary> public ReverseComparer() : this(Comparer<TCompared>.Default) { }
public ReverseComparer() : this(Comparer<TCompared>.Default) { }
/// <summary>
/// <summary> /// Initializes the comparer to provide the inverse results of another comparer
/// Initializes the comparer to provide the inverse results of another comparer /// </summary>
/// </summary> /// <param name="comparerToReverse">Comparer whose results will be inversed</param>
/// <param name="comparerToReverse">Comparer whose results will be inversed</param> public ReverseComparer(IComparer<TCompared> comparerToReverse) {
public ReverseComparer(IComparer<TCompared> comparerToReverse) { this.comparer = comparerToReverse;
this.comparer = comparerToReverse; }
}
/// <summary>Compares the left value to the right value</summary>
/// <summary>Compares the left value to the right value</summary> /// <param name="left">Value on the left side</param>
/// <param name="left">Value on the left side</param> /// <param name="right">Value on the right side</param>
/// <param name="right">Value on the right side</param> /// <returns>The relationship of the two values</returns>
/// <returns>The relationship of the two values</returns> public int Compare(TCompared left, TCompared right) {
public int Compare(TCompared left, TCompared right) { return this.comparer.Compare(right, left); // intentionally reversed
return this.comparer.Compare(right, left); // intentionally reversed }
}
/// <summary>The default comparer from the .NET framework</summary>
/// <summary>The default comparer from the .NET framework</summary> private IComparer<TCompared> comparer;
private IComparer<TCompared> comparer;
}
}
} // namespace Nuclex.Support.Collections
} // namespace Nuclex.Support.Collections

View File

@ -1,120 +1,119 @@
#region CPL License #region Apache License 2.0
/* /*
Nuclex Framework Nuclex .NET Framework
Copyright (C) 2002-2017 Nuclex Development Labs Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
This library is free software; you can redistribute it and/or Licensed under the Apache License, Version 2.0 (the "License");
modify it under the terms of the IBM Common Public License as you may not use this file except in compliance with the License.
published by the IBM Corporation; either version 1.0 of the You may obtain a copy of the License at
License, or (at your option) any later version.
http://www.apache.org/licenses/LICENSE-2.0
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of Unless required by applicable law or agreed to in writing, software
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the distributed under the License is distributed on an "AS IS" BASIS,
IBM Common Public License for more details. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
You should have received a copy of the IBM Common Public limitations under the License.
License along with this library */
*/ #endregion // Apache License 2.0
#endregion
using System;
using System; using System.Collections.Generic;
using System.Collections.Generic; using System.ComponentModel;
using System.ComponentModel; using System.Reflection;
using System.Reflection;
#if UNITTEST
#if UNITTEST
using NUnit.Framework;
using NUnit.Framework;
namespace Nuclex.Support.Collections {
namespace Nuclex.Support.Collections {
/// <summary>Unit Test for the SortableBindingList class</summary>
/// <summary>Unit Test for the SortableBindingList class</summary> [TestFixture]
[TestFixture] internal class SortableBindingListTest {
internal class SortableBindingListTest {
#region class TestRecord
#region class TestRecord
/// <summary>Dummy record used to test the sortable binding list</summary>
/// <summary>Dummy record used to test the sortable binding list</summary> private class TestRecord {
private class TestRecord {
/// <summary>A property of type integer</summary>
/// <summary>A property of type integer</summary> public int IntegerValue { get; set; }
public int IntegerValue { get; set; }
/// <summary>A property of type string</summary>
/// <summary>A property of type string</summary> public string StringValue { get; set; }
public string StringValue { get; set; }
/// <summary>A property of type float</summary>
/// <summary>A property of type float</summary> public float FloatValue { get; set; }
public float FloatValue { get; set; }
}
}
#endregion // class TestRecord
#endregion // class TestRecord
/// <summary>Verifies that the sortable binding list is default constructible</summary>
/// <summary>Verifies that the sortable binding list is default constructible</summary> [Test]
[Test] public void HasDefaultConstructor() {
public void HasDefaultConstructor() { Assert.DoesNotThrow(
Assert.DoesNotThrow( delegate () { new SortableBindingList<TestRecord>(); }
delegate () { new SortableBindingList<TestRecord>(); } );
); }
}
/// <summary>
/// <summary> /// Tests whether the sortable binding list can copy an existing list
/// Tests whether the sortable binding list can copy an existing list /// when being constructed
/// when being constructed /// </summary>
/// </summary> [Test]
[Test] public void HasEnumerableConstructor() {
public void HasEnumerableConstructor() { var items = new List<TestRecord>() {
var items = new List<TestRecord>() { new TestRecord() { IntegerValue = 123 },
new TestRecord() { IntegerValue = 123 }, new TestRecord() { IntegerValue = 456 }
new TestRecord() { IntegerValue = 456 } };
};
var testList = new SortableBindingList<TestRecord>(items);
var testList = new SortableBindingList<TestRecord>(items);
Assert.AreEqual(2, testList.Count);
Assert.AreEqual(2, testList.Count); Assert.AreSame(items[0], testList[0]);
Assert.AreSame(items[0], testList[0]); Assert.AreSame(items[1], testList[1]);
Assert.AreSame(items[1], testList[1]); }
}
/// <summary>Verifies that the sortable binding list supports sorting</summary>
/// <summary>Verifies that the sortable binding list supports sorting</summary> [Test]
[Test] public void SupportsSorting() {
public void SupportsSorting() { var testList = new SortableBindingList<TestRecord>();
var testList = new SortableBindingList<TestRecord>(); IBindingList testListAsBindingList = testList;
IBindingList testListAsBindingList = testList;
Assert.IsTrue(testListAsBindingList.SupportsSorting);
Assert.IsTrue(testListAsBindingList.SupportsSorting); }
}
/// <summary>
/// <summary> /// Tests whether the sortable binding list can sort its elements by different properties
/// Tests whether the sortable binding list can sort its elements by different properties /// </summary>
/// </summary> [Test]
[Test] public void CanSortItems() {
public void CanSortItems() { var items = new List<TestRecord>() {
var items = new List<TestRecord>() { new TestRecord() { IntegerValue = 456 },
new TestRecord() { IntegerValue = 456 }, new TestRecord() { IntegerValue = 789 },
new TestRecord() { IntegerValue = 789 }, new TestRecord() { IntegerValue = 123 }
new TestRecord() { IntegerValue = 123 } };
};
var testList = new SortableBindingList<TestRecord>(items);
var testList = new SortableBindingList<TestRecord>(items); IBindingList testListAsBindingList = testList;
IBindingList testListAsBindingList = testList;
PropertyDescriptor integerValuePropertyDescriptor = (
PropertyDescriptor integerValuePropertyDescriptor = ( TypeDescriptor.GetProperties(typeof(TestRecord))[nameof(TestRecord.IntegerValue)]
TypeDescriptor.GetProperties(typeof(TestRecord))[nameof(TestRecord.IntegerValue)] );
); testListAsBindingList.ApplySort(
testListAsBindingList.ApplySort( integerValuePropertyDescriptor, ListSortDirection.Ascending
integerValuePropertyDescriptor, ListSortDirection.Ascending );
);
Assert.AreEqual(3, testList.Count);
Assert.AreEqual(3, testList.Count); Assert.AreEqual(123, testList[0].IntegerValue);
Assert.AreEqual(123, testList[0].IntegerValue); Assert.AreEqual(456, testList[1].IntegerValue);
Assert.AreEqual(456, testList[1].IntegerValue); Assert.AreEqual(789, testList[2].IntegerValue);
Assert.AreEqual(789, testList[2].IntegerValue); }
}
}
}
} // namespace Nuclex.Support.Collections
} // namespace Nuclex.Support.Collections
#endif // UNITTEST
#endif // UNITTEST

View File

@ -1,223 +1,222 @@
#region CPL License #region Apache License 2.0
/* /*
Nuclex Framework Nuclex .NET Framework
Copyright (C) 2002-2017 Nuclex Development Labs Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
This library is free software; you can redistribute it and/or Licensed under the Apache License, Version 2.0 (the "License");
modify it under the terms of the IBM Common Public License as you may not use this file except in compliance with the License.
published by the IBM Corporation; either version 1.0 of the You may obtain a copy of the License at
License, or (at your option) any later version.
http://www.apache.org/licenses/LICENSE-2.0
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of Unless required by applicable law or agreed to in writing, software
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the distributed under the License is distributed on an "AS IS" BASIS,
IBM Common Public License for more details. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
You should have received a copy of the IBM Common Public limitations under the License.
License along with this library */
*/ #endregion // Apache License 2.0
#endregion
using System;
using System; using System.Collections;
using System.Collections; using System.Collections.Generic;
using System.Collections.Generic; using System.ComponentModel;
using System.ComponentModel; using System.Reflection;
using System.Reflection;
namespace Nuclex.Support.Collections {
namespace Nuclex.Support.Collections {
/// <summary>Variant of BindingList that supports sorting</summary>
/// <summary>Variant of BindingList that supports sorting</summary> /// <typeparam name="TElement">Type of items the binding list will contain</typeparam>
/// <typeparam name="TElement">Type of items the binding list will contain</typeparam> public class SortableBindingList<TElement> : BindingList<TElement> {
public class SortableBindingList<TElement> : BindingList<TElement> {
#region class PropertyComparer
#region class PropertyComparer
/// <summary>Compares two elements based on a single preselected property</summary>
/// <summary>Compares two elements based on a single preselected property</summary> private class PropertyComparer : IComparer<TElement> {
private class PropertyComparer : IComparer<TElement> {
/// <summary>Initializes a new property comparer for the specified property</summary>
/// <summary>Initializes a new property comparer for the specified property</summary> /// <param name="property">Property based on which elements should be compared</param>
/// <param name="property">Property based on which elements should be compared</param> /// <param name="direction">Direction in which elements should be sorted</param>
/// <param name="direction">Direction in which elements should be sorted</param> public PropertyComparer(PropertyDescriptor property, ListSortDirection direction) {
public PropertyComparer(PropertyDescriptor property, ListSortDirection direction) { this.propertyDescriptor = property;
this.propertyDescriptor = property;
Type comparerForPropertyType = typeof(Comparer<>).MakeGenericType(property.PropertyType);
Type comparerForPropertyType = typeof(Comparer<>).MakeGenericType(property.PropertyType); this.comparer = (IComparer)comparerForPropertyType.InvokeMember(
this.comparer = (IComparer)comparerForPropertyType.InvokeMember( "Default",
"Default", BindingFlags.Static | BindingFlags.GetProperty | BindingFlags.Public,
BindingFlags.Static | BindingFlags.GetProperty | BindingFlags.Public, null, // binder
null, // binder null, // target (none since method is static)
null, // target (none since method is static) null // argument array
null // argument array );
);
SetListSortDirection(direction);
SetListSortDirection(direction); }
}
/// <summary>Compares two elements based on the comparer's chosen property</summary>
/// <summary>Compares two elements based on the comparer's chosen property</summary> /// <param name="first">First element for the comparison</param>
/// <param name="first">First element for the comparison</param> /// <param name="second">Second element for the comparison</param>
/// <param name="second">Second element for the comparison</param> /// <returns>The relationship of the two elements to each other</returns>
/// <returns>The relationship of the two elements to each other</returns> public int Compare(TElement first, TElement second) {
public int Compare(TElement first, TElement second) { return this.comparer.Compare(
return this.comparer.Compare( this.propertyDescriptor.GetValue(first),
this.propertyDescriptor.GetValue(first), this.propertyDescriptor.GetValue(second)
this.propertyDescriptor.GetValue(second) ) * this.reverse;
) * this.reverse; }
}
/// <summary>Selects the property based on which elements should be compared</summary>
/// <summary>Selects the property based on which elements should be compared</summary> /// <param name="descriptor">Descriptor for the property to use for comparison</param>
/// <param name="descriptor">Descriptor for the property to use for comparison</param> private void SetPropertyDescriptor(PropertyDescriptor descriptor) {
private void SetPropertyDescriptor(PropertyDescriptor descriptor) { this.propertyDescriptor = descriptor;
this.propertyDescriptor = descriptor; }
}
/// <summary>Changes the sort direction</summary>
/// <summary>Changes the sort direction</summary> /// <param name="direction">New sort direction</param>
/// <param name="direction">New sort direction</param> private void SetListSortDirection(ListSortDirection direction) {
private void SetListSortDirection(ListSortDirection direction) { this.reverse = direction == ListSortDirection.Ascending ? 1 : -1;
this.reverse = direction == ListSortDirection.Ascending ? 1 : -1; }
}
/// <summary>Updtes the sorted proeprty and the sort direction</summary>
/// <summary>Updtes the sorted proeprty and the sort direction</summary> /// <param name="descriptor">Property based on which elements will be sorted</param>
/// <param name="descriptor">Property based on which elements will be sorted</param> /// <param name="direction">Direction in which elements will be sorted</param>
/// <param name="direction">Direction in which elements will be sorted</param> public void SetPropertyAndDirection(
public void SetPropertyAndDirection( PropertyDescriptor descriptor, ListSortDirection direction
PropertyDescriptor descriptor, ListSortDirection direction ) {
) { SetPropertyDescriptor(descriptor);
SetPropertyDescriptor(descriptor); SetListSortDirection(direction);
SetListSortDirection(direction); }
}
/// <summary>The default comparer for the type of the chosen property</summary>
/// <summary>The default comparer for the type of the chosen property</summary> private readonly IComparer comparer;
private readonly IComparer comparer; /// <summary>Descriptor for the chosen property</summary>
/// <summary>Descriptor for the chosen property</summary> private PropertyDescriptor propertyDescriptor;
private PropertyDescriptor propertyDescriptor; /// <summary>
/// <summary> /// Either positive or negative 1 to change the sign of the comparison result
/// Either positive or negative 1 to change the sign of the comparison result /// </summary>
/// </summary> private int reverse;
private int reverse;
}
}
#endregion // class PropertyComparer
#endregion // class PropertyComparer
/// <summary>Initializes a new BindingList with support for sorting</summary>
/// <summary>Initializes a new BindingList with support for sorting</summary> public SortableBindingList() : base(new List<TElement>()) {
public SortableBindingList() : base(new List<TElement>()) { this.comparers = new Dictionary<Type, PropertyComparer>();
this.comparers = new Dictionary<Type, PropertyComparer>(); }
}
/// <summary>
/// <summary> /// Initializes a sortable BindingList, copying the contents of an existing list
/// Initializes a sortable BindingList, copying the contents of an existing list /// </summary>
/// </summary> /// <param name="enumeration">Existing list whose contents will be shallo-wcopied</param>
/// <param name="enumeration">Existing list whose contents will be shallo-wcopied</param> public SortableBindingList(IEnumerable<TElement> enumeration) :
public SortableBindingList(IEnumerable<TElement> enumeration) : base(new List<TElement>(enumeration)) {
base(new List<TElement>(enumeration)) { this.comparers = new Dictionary<Type, PropertyComparer>();
this.comparers = new Dictionary<Type, PropertyComparer>(); }
}
/// <summary>
/// <summary> /// Used by BindingList implementation to check whether sorting is supported
/// Used by BindingList implementation to check whether sorting is supported /// </summary>
/// </summary> protected override bool SupportsSortingCore {
protected override bool SupportsSortingCore { get { return true; }
get { return true; } }
}
/// <summary>
/// <summary> /// Used by BindingList implementation to check whether the list is currently sorted
/// Used by BindingList implementation to check whether the list is currently sorted /// </summary>
/// </summary> protected override bool IsSortedCore {
protected override bool IsSortedCore { get { return this.isSorted; }
get { return this.isSorted; } }
}
/// <summary>
/// <summary> /// Used by BindingList implementation to track the property the list is sorted by
/// Used by BindingList implementation to track the property the list is sorted by /// </summary>
/// </summary> protected override PropertyDescriptor SortPropertyCore {
protected override PropertyDescriptor SortPropertyCore { get { return this.propertyDescriptor; }
get { return this.propertyDescriptor; } }
}
/// <summary>
/// <summary> /// Used by BindingList implementation to track the direction in which the list is sortd
/// Used by BindingList implementation to track the direction in which the list is sortd /// </summary>
/// </summary> protected override ListSortDirection SortDirectionCore {
protected override ListSortDirection SortDirectionCore { get { return this.listSortDirection; }
get { return this.listSortDirection; } }
}
/// <summary>
/// <summary> /// Used by BindingList implementation to check whether the list supports searching
/// Used by BindingList implementation to check whether the list supports searching /// </summary>
/// </summary> protected override bool SupportsSearchingCore {
protected override bool SupportsSearchingCore { get { return true; }
get { return true; } }
}
/// <summary>
/// <summary> /// Used by BindingList implementation to sort the elements in the backing collection
/// Used by BindingList implementation to sort the elements in the backing collection /// </summary>
/// </summary> protected override void ApplySortCore(PropertyDescriptor property, ListSortDirection direction) {
protected override void ApplySortCore(PropertyDescriptor property, ListSortDirection direction) {
// Obtain a property comparer that sorts on the attributes the SortableBindingList
// Obtain a property comparer that sorts on the attributes the SortableBindingList // has been configured for its sort order
// has been configured for its sort order PropertyComparer comparer;
PropertyComparer comparer; {
{ Type propertyType = property.PropertyType;
Type propertyType = property.PropertyType;
if(!this.comparers.TryGetValue(propertyType, out comparer)) {
if(!this.comparers.TryGetValue(propertyType, out comparer)) { comparer = new PropertyComparer(property, direction);
comparer = new PropertyComparer(property, direction); this.comparers.Add(propertyType, comparer);
this.comparers.Add(propertyType, comparer); }
}
// Direction may need to be updated
// Direction may need to be updated comparer.SetPropertyAndDirection(property, direction);
comparer.SetPropertyAndDirection(property, direction); }
}
// Check to see if our base class is using a standard List<> in which case
// Check to see if our base class is using a standard List<> in which case // we'll sneakily use the downcast to call the List<>.Sort() method, otherwise
// we'll sneakily use the downcast to call the List<>.Sort() method, otherwise // there's still our own quicksort implementation for IList<>.
// there's still our own quicksort implementation for IList<>. List<TElement> itemsAsList = this.Items as List<TElement>;
List<TElement> itemsAsList = this.Items as List<TElement>; if(itemsAsList != null) {
if(itemsAsList != null) { itemsAsList.Sort(comparer);
itemsAsList.Sort(comparer); } else {
} else { this.Items.QuickSort(0, this.Items.Count, comparer); // from IListExtensions
this.Items.QuickSort(0, this.Items.Count, comparer); // from IListExtensions }
}
this.propertyDescriptor = property;
this.propertyDescriptor = property; this.listSortDirection = direction;
this.listSortDirection = direction; this.isSorted = true;
this.isSorted = true;
OnListChanged(new ListChangedEventArgs(ListChangedType.Reset, -1));
OnListChanged(new ListChangedEventArgs(ListChangedType.Reset, -1)); }
}
/// <summary>Used by BindingList implementation to undo any sorting that took place</summary>
/// <summary>Used by BindingList implementation to undo any sorting that took place</summary> protected override void RemoveSortCore() {
protected override void RemoveSortCore() { this.isSorted = false;
this.isSorted = false; this.propertyDescriptor = base.SortPropertyCore;
this.propertyDescriptor = base.SortPropertyCore; this.listSortDirection = base.SortDirectionCore;
this.listSortDirection = base.SortDirectionCore;
OnListChanged(new ListChangedEventArgs(ListChangedType.Reset, -1));
OnListChanged(new ListChangedEventArgs(ListChangedType.Reset, -1)); }
}
/// <summary>
/// <summary> /// Used by BindingList implementation to run a search on any of the element's properties
/// Used by BindingList implementation to run a search on any of the element's properties /// </summary>
/// </summary> protected override int FindCore(PropertyDescriptor property, object key) {
protected override int FindCore(PropertyDescriptor property, object key) { int count = this.Count;
int count = this.Count; for(int index = 0; index < count; ++index) {
for(int index = 0; index < count; ++index) { TElement element = this[index];
TElement element = this[index]; if(property.GetValue(element).Equals(key)) {
if(property.GetValue(element).Equals(key)) { return index;
return index; }
} }
}
return -1;
return -1; }
}
/// <summary>Cached property comparers, created for each element property as needed</summary>
/// <summary>Cached property comparers, created for each element property as needed</summary> private readonly Dictionary<Type, PropertyComparer> comparers;
private readonly Dictionary<Type, PropertyComparer> comparers; /// <summary>Whether the binding list is currently sorted</summary>
/// <summary>Whether the binding list is currently sorted</summary> private bool isSorted;
private bool isSorted; /// <summary>Direction in which the binding list is currently sorted</summary>
/// <summary>Direction in which the binding list is currently sorted</summary> private ListSortDirection listSortDirection;
private ListSortDirection listSortDirection; /// <summary>Descriptor for the property by which the binding list is currently sorted</summary>
/// <summary>Descriptor for the property by which the binding list is currently sorted</summary> private PropertyDescriptor propertyDescriptor;
private PropertyDescriptor propertyDescriptor;
}
}
} // namespace Nuclex.Support.Collections
} // namespace Nuclex.Support.Collections

View File

@ -1,343 +1,342 @@
#region CPL License #region Apache License 2.0
/* /*
Nuclex Framework Nuclex .NET Framework
Copyright (C) 2002-2017 Nuclex Development Labs Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
This library is free software; you can redistribute it and/or Licensed under the Apache License, Version 2.0 (the "License");
modify it under the terms of the IBM Common Public License as you may not use this file except in compliance with the License.
published by the IBM Corporation; either version 1.0 of the You may obtain a copy of the License at
License, or (at your option) any later version.
http://www.apache.org/licenses/LICENSE-2.0
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of Unless required by applicable law or agreed to in writing, software
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the distributed under the License is distributed on an "AS IS" BASIS,
IBM Common Public License for more details. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
You should have received a copy of the IBM Common Public limitations under the License.
License along with this library */
*/ #endregion // Apache License 2.0
#endregion
using System;
using System; using System.Threading;
using System.Threading; using System.Collections;
using System.Collections; using System.Collections.Generic;
using System.Collections.Generic;
namespace Nuclex.Support.Collections {
namespace Nuclex.Support.Collections {
// More than 400 lines of code just to implement the .NET collection interfaces.
// More than 400 lines of code just to implement the .NET collection interfaces. // Shows the niceties of having to support languages without generics and using
// Shows the niceties of having to support languages without generics and using // an inferior design to make collections "more convenient" for the user :/
// an inferior design to make collections "more convenient" for the user :/
partial class TransformingReadOnlyCollection<TContainedItem, TExposedItem> {
partial class TransformingReadOnlyCollection<TContainedItem, TExposedItem> {
#region IList<TExposedItem> Members
#region IList<TExposedItem> Members
/// <summary>
/// <summary> /// Inserts an item to the TransformingReadOnlyCollection at the specified index.
/// Inserts an item to the TransformingReadOnlyCollection at the specified index. /// </summary>
/// </summary> /// <param name="index">
/// <param name="index"> /// The zero-based index at which item should be inserted.
/// The zero-based index at which item should be inserted. /// </param>
/// </param> /// <param name="item">
/// <param name="item"> /// The object to insert into the TransformingReadOnlyCollection
/// The object to insert into the TransformingReadOnlyCollection /// </param>
/// </param> /// <exception cref="System.NotSupportedException">
/// <exception cref="System.NotSupportedException"> /// The TransformingReadOnlyCollection is read-only.
/// The TransformingReadOnlyCollection is read-only. /// </exception>
/// </exception> /// <exception cref="System.ArgumentOutOfRangeException">
/// <exception cref="System.ArgumentOutOfRangeException"> /// index is not a valid index in the TransformingReadOnlyCollection.
/// index is not a valid index in the TransformingReadOnlyCollection. /// </exception>
/// </exception> void IList<TExposedItem>.Insert(int index, TExposedItem item) {
void IList<TExposedItem>.Insert(int index, TExposedItem item) { throw new NotSupportedException("The collection is ready-only");
throw new NotSupportedException("The collection is ready-only"); }
}
/// <summary>
/// <summary> /// Removes the TransformingReadOnlyCollection item at the specified index.
/// Removes the TransformingReadOnlyCollection item at the specified index. /// </summary>
/// </summary> /// <param name="index">The zero-based index of the item to remove.</param>
/// <param name="index">The zero-based index of the item to remove.</param> /// <exception cref="System.NotSupportedException">
/// <exception cref="System.NotSupportedException"> /// The TransformingReadOnlyCollection is read-only.
/// The TransformingReadOnlyCollection is read-only. /// </exception>
/// </exception> /// <exception cref="System.ArgumentOutOfRangeException">
/// <exception cref="System.ArgumentOutOfRangeException"> /// Index is not a valid index in the TransformingReadOnlyCollection.
/// Index is not a valid index in the TransformingReadOnlyCollection. /// </exception>
/// </exception> void IList<TExposedItem>.RemoveAt(int index) {
void IList<TExposedItem>.RemoveAt(int index) { throw new NotSupportedException("The collection is ready-only");
throw new NotSupportedException("The collection is ready-only"); }
}
/// <summary>Gets or sets the element at the specified index.</summary>
/// <summary>Gets or sets the element at the specified index.</summary> /// <param name="index">The zero-based index of the element to get or set.</param>
/// <param name="index">The zero-based index of the element to get or set.</param> /// <returns>The element at the specified index.</returns>
/// <returns>The element at the specified index.</returns> /// <exception cref="System.ArgumentOutOfRangeException">
/// <exception cref="System.ArgumentOutOfRangeException"> /// Index is not a valid index in the TransformingReadOnlyCollection.
/// Index is not a valid index in the TransformingReadOnlyCollection. /// </exception>
/// </exception> /// <exception cref="System.NotSupportedException">
/// <exception cref="System.NotSupportedException"> /// The property is set and the TransformingReadOnlyCollection is read-only
/// The property is set and the TransformingReadOnlyCollection is read-only /// </exception>
/// </exception> TExposedItem IList<TExposedItem>.this[int index] {
TExposedItem IList<TExposedItem>.this[int index] { get {
get { return this[index];
return this[index]; }
} set {
set { throw new NotSupportedException("The collection is ready-only");
throw new NotSupportedException("The collection is ready-only"); }
} }
}
#endregion
#endregion
#region ICollection<TExposedItem> Members
#region ICollection<TExposedItem> Members
/// <summary>Adds an item to the TransformingReadOnlyCollection.</summary>
/// <summary>Adds an item to the TransformingReadOnlyCollection.</summary> /// <param name="item">The object to add to the TransformingReadOnlyCollection</param>
/// <param name="item">The object to add to the TransformingReadOnlyCollection</param> /// <exception cref="System.NotSupportedException">
/// <exception cref="System.NotSupportedException"> /// The TransformingReadOnlyCollection is read-only.
/// The TransformingReadOnlyCollection is read-only. /// </exception>
/// </exception> void ICollection<TExposedItem>.Add(TExposedItem item) {
void ICollection<TExposedItem>.Add(TExposedItem item) { throw new NotSupportedException("The collection is ready-only");
throw new NotSupportedException("The collection is ready-only"); }
}
/// <summary>Removes all items from the TransformingReadOnlyCollection</summary>
/// <summary>Removes all items from the TransformingReadOnlyCollection</summary> /// <exception cref="System.NotSupportedException">
/// <exception cref="System.NotSupportedException"> /// The TransformingReadOnlyCollection is read-only.
/// The TransformingReadOnlyCollection is read-only. /// </exception>
/// </exception> void ICollection<TExposedItem>.Clear() {
void ICollection<TExposedItem>.Clear() { throw new NotSupportedException("The collection is ready-only");
throw new NotSupportedException("The collection is ready-only"); }
}
/// <summary>
/// <summary> /// Removes the first occurrence of a specific object from the
/// Removes the first occurrence of a specific object from the /// TransformingReadOnlyCollection.
/// TransformingReadOnlyCollection. /// </summary>
/// </summary> /// <param name="item">
/// <param name="item"> /// The object to remove from the TransformingReadOnlyCollection
/// The object to remove from the TransformingReadOnlyCollection /// </param>
/// </param> /// <returns>
/// <returns> /// True if item was successfully removed from the TransformingReadOnlyCollection;
/// True if item was successfully removed from the TransformingReadOnlyCollection; /// otherwise, false. This method also returns false if item is not found in the
/// otherwise, false. This method also returns false if item is not found in the /// original TransformingReadOnlyCollection.
/// original TransformingReadOnlyCollection. /// </returns>
/// </returns> /// <exception cref="System.NotSupportedException">
/// <exception cref="System.NotSupportedException"> /// The TransformingReadOnlyCollection is read-only.
/// The TransformingReadOnlyCollection is read-only. /// </exception>
/// </exception> bool ICollection<TExposedItem>.Remove(TExposedItem item) {
bool ICollection<TExposedItem>.Remove(TExposedItem item) { throw new NotSupportedException("The collection is ready-only");
throw new NotSupportedException("The collection is ready-only"); }
}
#endregion
#endregion
#region IEnumerable Members
#region IEnumerable Members
/// <summary>Returns an enumerator that iterates through a collection.</summary>
/// <summary>Returns an enumerator that iterates through a collection.</summary> /// <returns>
/// <returns> /// A System.Collections.IEnumerator object that can be used to iterate through
/// A System.Collections.IEnumerator object that can be used to iterate through /// the collection.
/// the collection. /// </returns>
/// </returns> IEnumerator IEnumerable.GetEnumerator() {
IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator();
return GetEnumerator(); }
}
#endregion
#endregion
#region IList Members
#region IList Members
/// <summary>Adds an item to the TransformingReadOnlyCollection.</summary>
/// <summary>Adds an item to the TransformingReadOnlyCollection.</summary> /// <param name="value">
/// <param name="value"> /// The System.Object to add to the TransformingReadOnlyCollection.
/// The System.Object to add to the TransformingReadOnlyCollection. /// </param>
/// </param> /// <returns>The position into which the new element was inserted.</returns>
/// <returns>The position into which the new element was inserted.</returns> /// <exception cref="System.NotSupportedException">
/// <exception cref="System.NotSupportedException"> /// The System.Collections.IList is read-only or the TransformingReadOnlyCollection
/// The System.Collections.IList is read-only or the TransformingReadOnlyCollection /// has a fixed size.
/// has a fixed size. /// </exception>
/// </exception> int IList.Add(object value) {
int IList.Add(object value) { throw new NotSupportedException("The collection is ready-only");
throw new NotSupportedException("The collection is ready-only"); }
}
/// <summary>Removes all items from the TransformingReadOnlyCollection.</summary>
/// <summary>Removes all items from the TransformingReadOnlyCollection.</summary> /// <exception cref="System.NotSupportedException">
/// <exception cref="System.NotSupportedException"> /// The TransformingReadOnlyCollection is read-only.
/// The TransformingReadOnlyCollection is read-only. /// </exception>
/// </exception> void IList.Clear() {
void IList.Clear() { throw new NotSupportedException("The collection is ready-only");
throw new NotSupportedException("The collection is ready-only"); }
}
/// <summary>
/// <summary> /// Determines whether the TransformingReadOnlyCollection contains a specific value.
/// Determines whether the TransformingReadOnlyCollection contains a specific value. /// </summary>
/// </summary> /// <param name="value">
/// <param name="value"> /// The System.Object to locate in the TransformingReadOnlyCollection.
/// The System.Object to locate in the TransformingReadOnlyCollection. /// </param>
/// </param> /// <returns>
/// <returns> /// True if the System.Object is found in the TransformingReadOnlyCollection;
/// True if the System.Object is found in the TransformingReadOnlyCollection; /// otherwise, false.
/// otherwise, false. /// </returns>
/// </returns> bool IList.Contains(object value) {
bool IList.Contains(object value) { return Contains((TExposedItem)value);
return Contains((TExposedItem)value); }
}
/// <summary>
/// <summary> /// Determines the index of a specific item in the TransformingReadOnlyCollection.
/// Determines the index of a specific item in the TransformingReadOnlyCollection. /// </summary>
/// </summary> /// <param name="value">
/// <param name="value"> /// The System.Object to locate in the TransformingReadOnlyCollection.
/// The System.Object to locate in the TransformingReadOnlyCollection. /// </param>
/// </param> /// <returns>
/// <returns> /// The index of value if found in the list; otherwise, -1.
/// The index of value if found in the list; otherwise, -1. /// </returns>
/// </returns> int IList.IndexOf(object value) {
int IList.IndexOf(object value) { return IndexOf((TExposedItem)value);
return IndexOf((TExposedItem)value); }
}
/// <summary>
/// <summary> /// Inserts an item to the TransformingReadOnlyCollection at the specified index.
/// Inserts an item to the TransformingReadOnlyCollection at the specified index. /// </summary>
/// </summary> /// <param name="index">
/// <param name="index"> /// The zero-based index at which value should be inserted.
/// The zero-based index at which value should be inserted. /// </param>
/// </param> /// <param name="value">
/// <param name="value"> /// The System.Object to insert into the TransformingReadOnlyCollection.
/// The System.Object to insert into the TransformingReadOnlyCollection. /// </param>
/// </param> /// <exception cref="System.ArgumentOutOfRangeException">
/// <exception cref="System.ArgumentOutOfRangeException"> /// Index is not a valid index in the TransformingReadOnlyCollection.
/// Index is not a valid index in the TransformingReadOnlyCollection. /// </exception>
/// </exception> /// <exception cref="System.NotSupportedException">
/// <exception cref="System.NotSupportedException"> /// The System.Collections.IList is read-only or the TransformingReadOnlyCollection
/// The System.Collections.IList is read-only or the TransformingReadOnlyCollection /// has a fixed size.
/// has a fixed size. /// </exception>
/// </exception> /// <exception cref="System.NullReferenceException">
/// <exception cref="System.NullReferenceException"> /// Value is null reference in the TransformingReadOnlyCollection.
/// Value is null reference in the TransformingReadOnlyCollection. /// </exception>
/// </exception> void IList.Insert(int index, object value) {
void IList.Insert(int index, object value) { throw new NotSupportedException("The collection is ready-only");
throw new NotSupportedException("The collection is ready-only"); }
}
/// <summary>
/// <summary> /// A value indicating whether the TransformingReadOnlyCollection has a fixed
/// A value indicating whether the TransformingReadOnlyCollection has a fixed /// size.
/// size. /// </summary>
/// </summary> bool IList.IsFixedSize {
bool IList.IsFixedSize { get { return true; }
get { return true; } }
}
/// <summary>
/// <summary> /// Removes the first occurrence of a specific object from the
/// Removes the first occurrence of a specific object from the /// TransformingReadOnlyCollection.
/// TransformingReadOnlyCollection. /// </summary>
/// </summary> /// <param name="value">
/// <param name="value"> /// The System.Object to remove from the TransformingReadOnlyCollection.
/// The System.Object to remove from the TransformingReadOnlyCollection. /// </param>
/// </param> /// <exception cref="System.NotSupportedException">
/// <exception cref="System.NotSupportedException"> /// The TransformingReadOnlyCollection is read-only or the
/// The TransformingReadOnlyCollection is read-only or the /// TransformingReadOnlyCollection has a fixed size.
/// TransformingReadOnlyCollection has a fixed size. /// </exception>
/// </exception> void IList.Remove(object value) {
void IList.Remove(object value) { throw new NotSupportedException("The collection is ready-only");
throw new NotSupportedException("The collection is ready-only"); }
}
/// <summary>
/// <summary> /// Removes the TransformingReadOnlyCollection item at the specified index.
/// Removes the TransformingReadOnlyCollection item at the specified index. /// </summary>
/// </summary> /// <param name="index">The zero-based index of the item to remove.</param>
/// <param name="index">The zero-based index of the item to remove.</param> /// <exception cref="System.ArgumentOutOfRangeException">
/// <exception cref="System.ArgumentOutOfRangeException"> /// Index is not a valid index in the TransformingReadOnlyCollection.
/// Index is not a valid index in the TransformingReadOnlyCollection. /// </exception>
/// </exception> /// <exception cref="System.NotSupportedException">
/// <exception cref="System.NotSupportedException"> /// The TransformingReadOnlyCollection is read-only or the
/// The TransformingReadOnlyCollection is read-only or the /// TransformingReadOnlyCollection has a fixed size.
/// TransformingReadOnlyCollection has a fixed size. /// </exception>
/// </exception> void IList.RemoveAt(int index) {
void IList.RemoveAt(int index) { throw new NotSupportedException("The collection is ready-only");
throw new NotSupportedException("The collection is ready-only"); }
}
/// <summary>Gets or sets the element at the specified index.</summary>
/// <summary>Gets or sets the element at the specified index.</summary> /// <param name="index">The zero-based index of the element to get or set.</param>
/// <param name="index">The zero-based index of the element to get or set.</param> /// <returns>The element at the specified index</returns>
/// <returns>The element at the specified index</returns> /// <exception cref="System.ArgumentOutOfRangeException">
/// <exception cref="System.ArgumentOutOfRangeException"> /// Index is not a valid index in the TransformingReadOnlyCollection
/// Index is not a valid index in the TransformingReadOnlyCollection /// </exception>
/// </exception> /// <exception cref="System.NotSupportedException">
/// <exception cref="System.NotSupportedException"> /// The property is set and the TransformingReadOnlyCollection is read-only.
/// The property is set and the TransformingReadOnlyCollection is read-only. /// </exception>
/// </exception> object IList.this[int index] {
object IList.this[int index] { get {
get { return this[index];
return this[index]; }
} set {
set { throw new NotSupportedException("The collection is ready-only");
throw new NotSupportedException("The collection is ready-only"); }
} }
}
#endregion
#endregion
#region ICollection Members
#region ICollection Members
/// <summary>
/// <summary> /// Copies the elements of the TransformingReadOnlyCollection to an System.Array,
/// Copies the elements of the TransformingReadOnlyCollection to an System.Array, /// starting at a particular System.Array index.
/// starting at a particular System.Array index. /// </summary>
/// </summary> /// <param name="array">
/// <param name="array"> /// The one-dimensional System.Array that is the destination of the elements
/// The one-dimensional System.Array that is the destination of the elements /// copied from TransformingReadOnlyCollection. The System.Array must have zero-based
/// copied from TransformingReadOnlyCollection. The System.Array must have zero-based /// indexing.
/// indexing. /// </param>
/// </param> /// <param name="index">The zero-based index in array at which copying begins.</param>
/// <param name="index">The zero-based index in array at which copying begins.</param> /// <exception cref="System.ArgumentNullException">
/// <exception cref="System.ArgumentNullException"> /// Array is null.
/// Array is null. /// </exception>
/// </exception> /// <exception cref="System.ArgumentOutOfRangeException">
/// <exception cref="System.ArgumentOutOfRangeException"> /// Index is less than zero.
/// Index is less than zero. /// </exception>
/// </exception> /// <exception cref="System.ArgumentException">
/// <exception cref="System.ArgumentException"> /// Array is multidimensional or index is equal to or greater than the length
/// Array is multidimensional or index is equal to or greater than the length /// of array or the number of elements in the source TransformingReadOnlyCollection
/// of array or the number of elements in the source TransformingReadOnlyCollection /// is greater than the available space from index to the end of the destination
/// is greater than the available space from index to the end of the destination /// array.
/// array. /// </exception>
/// </exception> /// <exception cref="System.InvalidCastException">
/// <exception cref="System.InvalidCastException"> /// The type of the source TransformingReadOnlyCollection cannot be cast
/// The type of the source TransformingReadOnlyCollection cannot be cast /// automatically to the type of the destination array.
/// automatically to the type of the destination array. /// </exception>
/// </exception> void ICollection.CopyTo(Array array, int index) {
void ICollection.CopyTo(Array array, int index) { CopyTo((TExposedItem[])array, index);
CopyTo((TExposedItem[])array, index); }
}
/// <summary>
/// <summary> /// The number of elements contained in the TransformingReadOnlyCollection.
/// The number of elements contained in the TransformingReadOnlyCollection. /// </summary>
/// </summary> int ICollection.Count {
int ICollection.Count { get { return Count; }
get { return Count; } }
}
/// <summary>
/// <summary> /// A value indicating whether access to the TransformingReadOnlyCollection
/// A value indicating whether access to the TransformingReadOnlyCollection /// is synchronized (thread safe).
/// is synchronized (thread safe). /// </summary>
/// </summary> bool ICollection.IsSynchronized {
bool ICollection.IsSynchronized { get { return false; }
get { return false; } }
}
/// <summary>
/// <summary> /// An object that can be used to synchronize access to the
/// An object that can be used to synchronize access to the /// TransformingReadOnlyCollection.
/// TransformingReadOnlyCollection. /// </summary>
/// </summary> object ICollection.SyncRoot {
object ICollection.SyncRoot { get {
get { if(this.syncRoot == null) {
if(this.syncRoot == null) { ICollection is2 = this.items as ICollection;
ICollection is2 = this.items as ICollection; if(is2 != null) {
if(is2 != null) { this.syncRoot = is2.SyncRoot;
this.syncRoot = is2.SyncRoot; } else {
} else { Interlocked.CompareExchange(ref this.syncRoot, new object(), null);
Interlocked.CompareExchange(ref this.syncRoot, new object(), null); }
} }
}
return this.syncRoot;
return this.syncRoot; }
} }
}
#endregion
#endregion
}
}
} // namespace Nuclex.Support.Collections
} // namespace Nuclex.Support.Collections

File diff suppressed because it is too large Load Diff

View File

@ -1,289 +1,288 @@
#region CPL License #region Apache License 2.0
/* /*
Nuclex Framework Nuclex .NET Framework
Copyright (C) 2002-2017 Nuclex Development Labs Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
This library is free software; you can redistribute it and/or Licensed under the Apache License, Version 2.0 (the "License");
modify it under the terms of the IBM Common Public License as you may not use this file except in compliance with the License.
published by the IBM Corporation; either version 1.0 of the You may obtain a copy of the License at
License, or (at your option) any later version.
http://www.apache.org/licenses/LICENSE-2.0
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of Unless required by applicable law or agreed to in writing, software
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the distributed under the License is distributed on an "AS IS" BASIS,
IBM Common Public License for more details. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
You should have received a copy of the IBM Common Public limitations under the License.
License along with this library */
*/ #endregion // Apache License 2.0
#endregion
using System;
using System; using System.Collections;
using System.Collections; using System.Collections.Generic;
using System.Collections.Generic;
namespace Nuclex.Support.Collections {
namespace Nuclex.Support.Collections {
/// <summary>Collection that transforms the contents of another collection.</summary>
/// <summary>Collection that transforms the contents of another collection.</summary> /// <typeparam name="TContainedItem">
/// <typeparam name="TContainedItem"> /// Type of the items contained in the wrapped collection.
/// Type of the items contained in the wrapped collection. /// </typeparam>
/// </typeparam> /// <typeparam name="TExposedItem">
/// <typeparam name="TExposedItem"> /// Type this collection exposes its items as.
/// Type this collection exposes its items as. /// </typeparam>
/// </typeparam> /// <remarks>
/// <remarks> /// <para>
/// <para> /// This collection is useful if you want to expose the objects of an arbitrary
/// This collection is useful if you want to expose the objects of an arbitrary /// collection under a different type. It can be used, for example, to construct
/// collection under a different type. It can be used, for example, to construct /// wrappers for the items in a collection on-the-fly, eliminating the need to
/// wrappers for the items in a collection on-the-fly, eliminating the need to /// manage the wrappers in parallel to the real items and improving performance
/// manage the wrappers in parallel to the real items and improving performance /// by only constructing a wrapper when an item is actually requested.
/// by only constructing a wrapper when an item is actually requested. /// </para>
/// </para> /// <para>
/// <para> /// Another common use would be if you have a private collection of a non-public
/// Another common use would be if you have a private collection of a non-public /// type that's derived from some publicly visible type. By using this collection,
/// type that's derived from some publicly visible type. By using this collection, /// you can return the items under the publicly visible type while still having
/// you can return the items under the publicly visible type while still having /// your private collection under the non-public type, eliminating the need to
/// your private collection under the non-public type, eliminating the need to /// downcast each time you need to access elements of the non-public type.
/// downcast each time you need to access elements of the non-public type. /// </para>
/// </para> /// </remarks>
/// </remarks> public abstract partial class TransformingReadOnlyCollection<
public abstract partial class TransformingReadOnlyCollection< TContainedItem, TExposedItem
TContainedItem, TExposedItem > : IList<TExposedItem>, IList {
> : IList<TExposedItem>, IList {
#region class TransformingEnumerator
#region class TransformingEnumerator
/// <summary>
/// <summary> /// An enumerator that transforms the items returned by an enumerator of the
/// An enumerator that transforms the items returned by an enumerator of the /// wrapped collection into the exposed type on-the-fly.
/// wrapped collection into the exposed type on-the-fly. /// </summary>
/// </summary> private class TransformingEnumerator : IEnumerator<TExposedItem> {
private class TransformingEnumerator : IEnumerator<TExposedItem> {
/// <summary>Initializes a new transforming enumerator</summary>
/// <summary>Initializes a new transforming enumerator</summary> /// <param name="transformer">Owner; used to invoke the Transform() method</param>
/// <param name="transformer">Owner; used to invoke the Transform() method</param> /// <param name="containedTypeEnumerator">Enumerator of the wrapped collection</param>
/// <param name="containedTypeEnumerator">Enumerator of the wrapped collection</param> public TransformingEnumerator(
public TransformingEnumerator( TransformingReadOnlyCollection<TContainedItem, TExposedItem> transformer,
TransformingReadOnlyCollection<TContainedItem, TExposedItem> transformer, IEnumerator<TContainedItem> containedTypeEnumerator
IEnumerator<TContainedItem> containedTypeEnumerator ) {
) { this.transformer = transformer;
this.transformer = transformer; this.containedTypeEnumerator = containedTypeEnumerator;
this.containedTypeEnumerator = containedTypeEnumerator; }
}
/// <summary>Immediately releases all resources used by the instance</summary>
/// <summary>Immediately releases all resources used by the instance</summary> public void Dispose() {
public void Dispose() { this.containedTypeEnumerator.Dispose();
this.containedTypeEnumerator.Dispose(); }
}
/// <summary>
/// <summary> /// The element in the collection at the current position of the enumerator.
/// The element in the collection at the current position of the enumerator. /// </summary>
/// </summary> public TExposedItem Current {
public TExposedItem Current { get {
get { return this.transformer.Transform(this.containedTypeEnumerator.Current);
return this.transformer.Transform(this.containedTypeEnumerator.Current); }
} }
}
/// <summary>Gets the current element in the collection.</summary>
/// <summary>Gets the current element in the collection.</summary> /// <returns>The current element in the collection.</returns>
/// <returns>The current element in the collection.</returns> /// <exception cref="System.InvalidOperationException">
/// <exception cref="System.InvalidOperationException"> /// The enumerator is positioned before the first element of the collection
/// The enumerator is positioned before the first element of the collection /// or after the last element.
/// or after the last element. /// </exception>
/// </exception> public bool MoveNext() {
public bool MoveNext() { return this.containedTypeEnumerator.MoveNext();
return this.containedTypeEnumerator.MoveNext(); }
}
/// <summary>
/// <summary> /// Sets the enumerator to its initial position, which is before the first element
/// Sets the enumerator to its initial position, which is before the first element /// in the collection.
/// in the collection. /// </summary>
/// </summary> /// <exception cref="System.InvalidOperationException">
/// <exception cref="System.InvalidOperationException"> /// The collection was modified after the enumerator was created.
/// The collection was modified after the enumerator was created. /// </exception>
/// </exception> public void Reset() {
public void Reset() { this.containedTypeEnumerator.Reset();
this.containedTypeEnumerator.Reset(); }
}
/// <summary>The current element in the collection.</summary>
/// <summary>The current element in the collection.</summary> /// <exception cref="System.InvalidOperationException">
/// <exception cref="System.InvalidOperationException"> /// The enumerator is positioned before the first element of the collection
/// The enumerator is positioned before the first element of the collection /// or after the last element.
/// or after the last element. /// </exception>
/// </exception> object IEnumerator.Current {
object IEnumerator.Current { get { return Current; }
get { return Current; } }
}
/// <summary>
/// <summary> /// Collection that owns this enumerator; required to invoke the item
/// Collection that owns this enumerator; required to invoke the item /// transformation method.
/// transformation method. /// </summary>
/// </summary> private TransformingReadOnlyCollection<TContainedItem, TExposedItem> transformer;
private TransformingReadOnlyCollection<TContainedItem, TExposedItem> transformer; /// <summary>An enumerator from the wrapped collection</summary>
/// <summary>An enumerator from the wrapped collection</summary> private IEnumerator<TContainedItem> containedTypeEnumerator;
private IEnumerator<TContainedItem> containedTypeEnumerator;
}
}
#endregion // class TransformingEnumerator
#endregion // class TransformingEnumerator
/// <summary>Initializes a new transforming collection wrapper</summary>
/// <summary>Initializes a new transforming collection wrapper</summary> /// <param name="items">
/// <param name="items"> /// Internal list of items that are transformed into the exposed type when
/// Internal list of items that are transformed into the exposed type when /// accessed through the TransformingReadOnlyCollection.
/// accessed through the TransformingReadOnlyCollection. /// </param>
/// </param> public TransformingReadOnlyCollection(IList<TContainedItem> items) {
public TransformingReadOnlyCollection(IList<TContainedItem> items) { this.items = items;
this.items = items; }
}
/// <summary>
/// <summary> /// Determines whether an element is in the TransformingReadOnlyCollection
/// Determines whether an element is in the TransformingReadOnlyCollection /// </summary>
/// </summary> /// <param name="item">
/// <param name="item"> /// The object to locate in the TransformingReadOnlyCollection.
/// The object to locate in the TransformingReadOnlyCollection. /// The value can be null for reference types.
/// The value can be null for reference types. /// </param>
/// </param> /// <returns>
/// <returns> /// True if value is found in the TransformingReadOnlyCollection; otherwise, false.
/// True if value is found in the TransformingReadOnlyCollection; otherwise, false. /// </returns>
/// </returns> /// <remarks>
/// <remarks> /// The default implementation of this method is very unoptimized and will
/// The default implementation of this method is very unoptimized and will /// enumerate all the items in the collection, transforming one after another
/// enumerate all the items in the collection, transforming one after another /// to check whether the transformed item matches the item the user was
/// to check whether the transformed item matches the item the user was /// looking for. It is recommended to provide a custom implementation of
/// looking for. It is recommended to provide a custom implementation of /// this method, if possible.
/// this method, if possible. /// </remarks>
/// </remarks> public virtual bool Contains(TExposedItem item) {
public virtual bool Contains(TExposedItem item) { return (IndexOf(item) != -1);
return (IndexOf(item) != -1); }
}
/// <summary>
/// <summary> /// Copies the entire TransformingReadOnlyCollection to a compatible one-dimensional
/// Copies the entire TransformingReadOnlyCollection to a compatible one-dimensional /// System.Array, starting at the specified index of the target array.
/// System.Array, starting at the specified index of the target array. /// </summary>
/// </summary> /// <param name="array">
/// <param name="array"> /// The one-dimensional System.Array that is the destination of the elements copied
/// The one-dimensional System.Array that is the destination of the elements copied /// from the TransformingReadOnlyCollection. The System.Array must have
/// from the TransformingReadOnlyCollection. The System.Array must have /// zero-based indexing.
/// zero-based indexing. /// </param>
/// </param> /// <param name="index">
/// <param name="index"> /// The zero-based index in array at which copying begins.
/// The zero-based index in array at which copying begins. /// </param>
/// </param> /// <exception cref="System.ArgumentException">
/// <exception cref="System.ArgumentException"> /// Index is equal to or greater than the length of array or the number of elements
/// Index is equal to or greater than the length of array or the number of elements /// in the source TransformingReadOnlyCollection is greater than the available space
/// in the source TransformingReadOnlyCollection is greater than the available space /// from index to the end of the destination array.
/// from index to the end of the destination array. /// </exception>
/// </exception> /// <exception cref="System.ArgumentOutOfRangeException">
/// <exception cref="System.ArgumentOutOfRangeException"> /// Index is less than zero.
/// Index is less than zero. /// </exception>
/// </exception> /// <exception cref="System.ArgumentNullException">
/// <exception cref="System.ArgumentNullException"> /// Array is null.
/// Array is null. /// </exception>
/// </exception> public void CopyTo(TExposedItem[] array, int index) {
public void CopyTo(TExposedItem[] array, int index) { if(this.items.Count > (array.Length - index)) {
if(this.items.Count > (array.Length - index)) { throw new ArgumentException(
throw new ArgumentException( "Array too small to fit the collection items starting at the specified index"
"Array too small to fit the collection items starting at the specified index" );
); }
}
for(int itemIndex = 0; itemIndex < this.items.Count; ++itemIndex) {
for(int itemIndex = 0; itemIndex < this.items.Count; ++itemIndex) { array[itemIndex + index] = Transform(this.items[itemIndex]);
array[itemIndex + index] = Transform(this.items[itemIndex]); }
} }
}
/// <summary>
/// <summary> /// Returns an enumerator that iterates through the TransformingReadOnlyCollection.
/// Returns an enumerator that iterates through the TransformingReadOnlyCollection. /// </summary>
/// </summary> /// <returns>
/// <returns> /// An enumerator or the TransformingReadOnlyCollection.
/// An enumerator or the TransformingReadOnlyCollection. /// </returns>
/// </returns> public IEnumerator<TExposedItem> GetEnumerator() {
public IEnumerator<TExposedItem> GetEnumerator() { return new TransformingEnumerator(this, this.items.GetEnumerator());
return new TransformingEnumerator(this, this.items.GetEnumerator()); }
}
/// <summary>
/// <summary> /// Searches for the specified object and returns the zero-based index of the
/// Searches for the specified object and returns the zero-based index of the /// first occurrence within the entire TransformingReadOnlyCollection.
/// first occurrence within the entire TransformingReadOnlyCollection. /// </summary>
/// </summary> /// <param name="item">
/// <param name="item"> /// The object to locate in the TransformingReadOnlyCollection. The value can
/// The object to locate in the TransformingReadOnlyCollection. The value can /// be null for reference types.
/// be null for reference types. /// </param>
/// </param> /// <returns>
/// <returns> /// The zero-based index of the first occurrence of item within the entire
/// The zero-based index of the first occurrence of item within the entire /// TransformingReadOnlyCollection, if found; otherwise, -1.
/// TransformingReadOnlyCollection, if found; otherwise, -1. /// </returns>
/// </returns> /// <remarks>
/// <remarks> /// The default implementation of this method is very unoptimized and will
/// The default implementation of this method is very unoptimized and will /// enumerate all the items in the collection, transforming one after another
/// enumerate all the items in the collection, transforming one after another /// to check whether the transformed item matches the item the user was
/// to check whether the transformed item matches the item the user was /// looking for. It is recommended to provide a custom implementation of
/// looking for. It is recommended to provide a custom implementation of /// this method, if possible.
/// this method, if possible. /// </remarks>
/// </remarks> public int IndexOf(TExposedItem item) {
public int IndexOf(TExposedItem item) {
if(item == null) {
if(item == null) {
for(int index = 0; index < this.items.Count; ++index) {
for(int index = 0; index < this.items.Count; ++index) { if(Transform(this.items[index]) == null) {
if(Transform(this.items[index]) == null) { return index;
return index; }
} }
}
} else {
} else {
var comparer = EqualityComparer<TExposedItem>.Default;
var comparer = EqualityComparer<TExposedItem>.Default; for(int index = 0; index < this.items.Count; ++index) {
for(int index = 0; index < this.items.Count; ++index) { if(comparer.Equals(Transform(this.items[index]), item)) {
if(comparer.Equals(Transform(this.items[index]), item)) { return index;
return index; }
} }
}
}
}
return -1;
return -1;
}
}
/// <summary>
/// <summary> /// The number of elements contained in the TransformingReadOnlyCollection instance
/// The number of elements contained in the TransformingReadOnlyCollection instance /// </summary>
/// </summary> public int Count {
public int Count { get { return this.items.Count; }
get { return this.items.Count; } }
}
/// <summary>Gets the element at the specified index.</summary>
/// <summary>Gets the element at the specified index.</summary> /// <param name="index">The zero-based index of the element to get.</param>
/// <param name="index">The zero-based index of the element to get.</param> /// <returns>The element at the specified index.</returns>
/// <returns>The element at the specified index.</returns> /// <exception cref="System.ArgumentOutOfRangeException">
/// <exception cref="System.ArgumentOutOfRangeException"> /// Index is less than zero or index is equal to or greater than
/// Index is less than zero or index is equal to or greater than /// TransformingReadOnlyCollection.Count.
/// TransformingReadOnlyCollection.Count. /// </exception>
/// </exception> public TExposedItem this[int index] {
public TExposedItem this[int index] { get { return Transform(this.items[index]); }
get { return Transform(this.items[index]); } }
}
/// <summary>Whether the List is write-protected</summary>
/// <summary>Whether the List is write-protected</summary> public bool IsReadOnly {
public bool IsReadOnly { get { return true; }
get { return true; } }
}
/// <summary>Transforms an item into the exposed type</summary>
/// <summary>Transforms an item into the exposed type</summary> /// <param name="item">Item to be transformed</param>
/// <param name="item">Item to be transformed</param> /// <returns>The transformed item</returns>
/// <returns>The transformed item</returns> /// <remarks>
/// <remarks> /// This method is used to transform an item in the wrapped collection into
/// This method is used to transform an item in the wrapped collection into /// the exposed item type whenever the user accesses an item. Expect it to
/// the exposed item type whenever the user accesses an item. Expect it to /// be called frequently, because the TransformingReadOnlyCollection does
/// be called frequently, because the TransformingReadOnlyCollection does /// not cache or otherwise store the transformed items.
/// not cache or otherwise store the transformed items. /// </remarks>
/// </remarks> protected abstract TExposedItem Transform(TContainedItem item);
protected abstract TExposedItem Transform(TContainedItem item);
/// <summary>Items being transformed upon exposure by this collection</summary>
/// <summary>Items being transformed upon exposure by this collection</summary> private IList<TContainedItem> items;
private IList<TContainedItem> items; /// <summary>Synchronization root for threaded accesses to this collection</summary>
/// <summary>Synchronization root for threaded accesses to this collection</summary> private object syncRoot;
private object syncRoot;
}
}
} // namespace Nuclex.Support.Collections
} // namespace Nuclex.Support.Collections

View File

@ -1,88 +1,87 @@
#region CPL License #region Apache License 2.0
/* /*
Nuclex Framework Nuclex .NET Framework
Copyright (C) 2002-2017 Nuclex Development Labs Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
This library is free software; you can redistribute it and/or Licensed under the Apache License, Version 2.0 (the "License");
modify it under the terms of the IBM Common Public License as you may not use this file except in compliance with the License.
published by the IBM Corporation; either version 1.0 of the You may obtain a copy of the License at
License, or (at your option) any later version.
http://www.apache.org/licenses/LICENSE-2.0
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of Unless required by applicable law or agreed to in writing, software
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the distributed under the License is distributed on an "AS IS" BASIS,
IBM Common Public License for more details. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
You should have received a copy of the IBM Common Public limitations under the License.
License along with this library */
*/ #endregion // Apache License 2.0
#endregion
#if !NO_SETS
#if !NO_SETS
using System;
using System; using System.Collections.Generic;
using System.Collections.Generic;
#if UNITTEST
#if UNITTEST
using NUnit.Framework;
using NUnit.Framework;
namespace Nuclex.Support.Collections {
namespace Nuclex.Support.Collections {
/// <summary>Unit Test for the Variegator multi dictionary</summary>
/// <summary>Unit Test for the Variegator multi dictionary</summary> [TestFixture]
[TestFixture] internal class VariegatorTest {
internal class VariegatorTest {
/// <summary>
/// <summary> /// Tests whether the default constructor of the reverse comparer works
/// Tests whether the default constructor of the reverse comparer works /// </summary>
/// </summary> [Test]
[Test] public void InstancesCanBeCreated() {
public void InstancesCanBeCreated() { new Variegator<int, string>();
new Variegator<int, string>(); }
}
/// <summary>
/// <summary> /// Verifies that querying for a missing value leads to an exception being thrown
/// Verifies that querying for a missing value leads to an exception being thrown /// </summary>
/// </summary> [Test]
[Test] public void QueryingMissingValueThrowsException() {
public void QueryingMissingValueThrowsException() { var variegator = new Variegator<int, string>();
var variegator = new Variegator<int, string>(); Assert.Throws<KeyNotFoundException>(
Assert.Throws<KeyNotFoundException>( () => {
() => { variegator.Get(123);
variegator.Get(123); }
} );
); }
}
/// <summary>
/// <summary> /// Verifies that the variegator resolves ambiguous matches according to its design
/// Verifies that the variegator resolves ambiguous matches according to its design /// </summary>
/// </summary> [Test]
[Test] public void AmbiguityResolvesToLeastRecentValue() {
public void AmbiguityResolvesToLeastRecentValue() { var variegator = new Variegator<int, string>();
var variegator = new Variegator<int, string>(); variegator.Add(1, "one");
variegator.Add(1, "one"); variegator.Add(1, "eins");
variegator.Add(1, "eins");
string first = variegator.Get(1);
string first = variegator.Get(1); string second = variegator.Get(1);
string second = variegator.Get(1);
// The variegator should have selected the first value by random and then
// The variegator should have selected the first value by random and then // returned the other value on the second query
// returned the other value on the second query Assert.AreNotEqual(first, second);
Assert.AreNotEqual(first, second);
// Now the variegator should return the first value again because it is
// Now the variegator should return the first value again because it is // the least recently used value
// the least recently used value Assert.AreEqual(first, variegator.Get(1));
Assert.AreEqual(first, variegator.Get(1));
// Repeating the query, the second should be returned again because now
// Repeating the query, the second should be returned again because now // it has become the least recently used value
// it has become the least recently used value Assert.AreEqual(second, variegator.Get(1));
Assert.AreEqual(second, variegator.Get(1)); }
}
}
}
} // namespace Nuclex.Support.Collections
} // namespace Nuclex.Support.Collections
#endif // UNITTEST
#endif // UNITTEST
#endif // !NO_SETS
#endif // !NO_SETS

View File

@ -1,287 +1,286 @@
#region CPL License #region Apache License 2.0
/* /*
Nuclex Native Framework Nuclex .NET Framework
Copyright (C) 2002-2017 Nuclex Development Labs Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
This library is free software; you can redistribute it and/or Licensed under the Apache License, Version 2.0 (the "License");
modify it under the terms of the IBM Common Public License as you may not use this file except in compliance with the License.
published by the IBM Corporation; either version 1.0 of the You may obtain a copy of the License at
License, or (at your option) any later version.
http://www.apache.org/licenses/LICENSE-2.0
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of Unless required by applicable law or agreed to in writing, software
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the distributed under the License is distributed on an "AS IS" BASIS,
IBM Common Public License for more details. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
You should have received a copy of the IBM Common Public limitations under the License.
License along with this library */
*/ #endregion // Apache License 2.0
#endregion // CPL License
using System;
using System; using System.Collections.Generic;
using System.Collections.Generic;
#if !NO_SETS
#if !NO_SETS
namespace Nuclex.Support.Collections {
namespace Nuclex.Support.Collections {
/// <summary>Randomly selects between different options, trying to avoid repetition</summary>
/// <summary>Randomly selects between different options, trying to avoid repetition</summary> /// <typeparam name="TKey">Type of keys through which values can be looked up</typeparam>
/// <typeparam name="TKey">Type of keys through which values can be looked up</typeparam> /// <typeparam name="TValue">Type of values provided by the variegator</typeparam>
/// <typeparam name="TValue">Type of values provided by the variegator</typeparam> /// <remarks>
/// <remarks> /// <para>
/// <para> /// This class is useful wherever randomness is involved in a game: picking random
/// This class is useful wherever randomness is involved in a game: picking random /// actions for an NPC to execute, selecting different songs to play, displaying
/// actions for an NPC to execute, selecting different songs to play, displaying /// different dialogue and more.
/// different dialogue and more. /// </para>
/// </para> /// <para>
/// <para> /// In principle, it works like a multimap, associating keys with a number of values
/// In principle, it works like a multimap, associating keys with a number of values /// and allowing you to look up values by their keys. Unlike a multimap, it will try
/// and allowing you to look up values by their keys. Unlike a multimap, it will try /// to avoid handing out a previously provided value again as long as possible.
/// to avoid handing out a previously provided value again as long as possible. /// </para>
/// </para> /// <para>
/// <para> /// A typical usage would be to set up a mapping between situations and dialogue lines.
/// A typical usage would be to set up a mapping between situations and dialogue lines. /// Upon calling <see cref="Get(TKey)" /> with the situation 'detected-player-stealing',
/// Upon calling <see cref="Get(TKey)" /> with the situation 'detected-player-stealing', /// the variegator would return a random (but not recently used) value which in this case
/// the variegator would return a random (but not recently used) value which in this case /// might contain a commentary an NPC might make upon encountering that situation.
/// might contain a commentary an NPC might make upon encountering that situation. /// Other NPCs requesting dialogue lines for the same situation would receive different
/// Other NPCs requesting dialogue lines for the same situation would receive different /// random commentary for as long as long as available data allows.
/// random commentary for as long as long as available data allows. /// </para>
/// </para> /// </remarks>
/// </remarks> public class Variegator<TKey, TValue> {
public class Variegator<TKey, TValue> {
/// <summary>Initializes a new variegator using the default history length</summary>
/// <summary>Initializes a new variegator using the default history length</summary> public Variegator() : this(64) {}
public Variegator() : this(64) {}
/// <summary>Initializes a new variegator</summary>
/// <summary>Initializes a new variegator</summary> /// <param name="historyLength">
/// <param name="historyLength"> /// How far into the past the variegator will look to avoid repetition
/// How far into the past the variegator will look to avoid repetition /// </param>
/// </param> public Variegator(int historyLength) {
public Variegator(int historyLength) { this.historyLength = historyLength;
this.historyLength = historyLength; this.history = new TValue[historyLength];
this.history = new TValue[historyLength]; this.values = new MultiDictionary<TKey, TValue>();
this.values = new MultiDictionary<TKey, TValue>();
this.randomNumberGenerator = new Random();
this.randomNumberGenerator = new Random(); }
}
/// <summary>Removes all entries from the variegator</summary>
/// <summary>Removes all entries from the variegator</summary> /// <remarks>
/// <remarks> /// This is mainly useful if you are storing smart pointers to values of substantial
/// This is mainly useful if you are storing smart pointers to values of substantial /// size (eg. audio clips instead of just resource proxies or paths) and need to
/// size (eg. audio clips instead of just resource proxies or paths) and need to /// reclaim memory.
/// reclaim memory. /// </remarks>
/// </remarks> public void Clear() {
public void Clear() { freeHistory();
freeHistory(); this.historyFull = false;
this.historyFull = false; this.historyTailIndex = 0;
this.historyTailIndex = 0; }
}
/// <summary>Checks whether the variegator is empty</summary>
/// <summary>Checks whether the variegator is empty</summary> /// <returns>True if there are no entries in the variegator</returns>
/// <returns>True if there are no entries in the variegator</returns> public bool IsEmpty {
public bool IsEmpty { get { return (Count == 0); }
get { return (Count == 0); } }
}
/// <summary>Returns the number of values in the variegator</summary>
/// <summary>Returns the number of values in the variegator</summary> /// <returns>The number of values stored in the variegator</returns>
/// <returns>The number of values stored in the variegator</returns> /// <remarks>
/// <remarks> /// If the same value is added with different keys (a situation that doesn't make
/// If the same value is added with different keys (a situation that doesn't make /// sense because such reuse should be covered by specifying multiple keys in
/// sense because such reuse should be covered by specifying multiple keys in /// a query), it will be counted multiple times.
/// a query), it will be counted multiple times. /// </remarks>
/// </remarks> public int Count {
public int Count { get { return ((System.Collections.ICollection)this.values).Count; }
get { return ((System.Collections.ICollection)this.values).Count; } }
}
/// <summary>
/// <summary> /// Insert a new value that can be returned when requesting the specified key
/// Insert a new value that can be returned when requesting the specified key /// </summary>
/// </summary> /// <param name="key">Key of the value that will be inserted</param>
/// <param name="key">Key of the value that will be inserted</param> /// <param name="value">Value that will be inserted under the provided key</param>
/// <param name="value">Value that will be inserted under the provided key</param> public void Add(TKey key, TValue value) {
public void Add(TKey key, TValue value) { this.values.Add(key, value);
this.values.Add(key, value); }
}
/// <summary>Retrieves a random value associated with the specified key</summary>
/// <summary>Retrieves a random value associated with the specified key</summary> /// <param name="key">For for which a value will be looked up</param>
/// <param name="key">For for which a value will be looked up</param> /// <returns>A random value associated with the specified key</returns>
/// <returns>A random value associated with the specified key</returns> public TValue Get(TKey key) {
public TValue Get(TKey key) { ISet<TValue> candidates = new HashSet<TValue>();
ISet<TValue> candidates = new HashSet<TValue>(); {
{ ICollection<TValue> valueRange = this.values[key];
ICollection<TValue> valueRange = this.values[key];
// If possible access the values by index because it's faster and produces less
// If possible access the values by index because it's faster and produces less // garbage, otherwise fall back to using an enumerator
// garbage, otherwise fall back to using an enumerator var indexableValueRange = valueRange as IList<TValue>;
var indexableValueRange = valueRange as IList<TValue>; if(indexableValueRange == null) {
if(indexableValueRange == null) { foreach(TValue value in valueRange) {
foreach(TValue value in valueRange) { candidates.Add(value);
candidates.Add(value); }
} } else {
} else { for(int valueIndex = 0; valueIndex < indexableValueRange.Count; ++valueIndex) {
for(int valueIndex = 0; valueIndex < indexableValueRange.Count; ++valueIndex) { candidates.Add(indexableValueRange[valueIndex]);
candidates.Add(indexableValueRange[valueIndex]); }
} }
} }
}
TValue result = destructivePickCandidateValue(candidates);
TValue result = destructivePickCandidateValue(candidates); addRecentlyUsedValue(result);
addRecentlyUsedValue(result); return result;
return result; }
}
/// <summary>Retrieves a random value associated with one of the specified keys</summary>
/// <summary>Retrieves a random value associated with one of the specified keys</summary> /// <param name="keys">Keys that will be considered</param>
/// <param name="keys">Keys that will be considered</param> /// <remarks>
/// <remarks> /// In many cases, you have generic situations (such as 'detected-player-stealing',
/// In many cases, you have generic situations (such as 'detected-player-stealing', /// 'observed-hostile-action') and specified situations (such as
/// 'observed-hostile-action') and specified situations (such as /// 'detected-player-stealing-from-beggar', 'observed-hostile-action-on-cop')
/// 'detected-player-stealing-from-beggar', 'observed-hostile-action-on-cop') /// where a values from both pools should be considered. This method allows you
/// where a values from both pools should be considered. This method allows you /// to specify any number of keys, creating a greater set of values the variegator
/// to specify any number of keys, creating a greater set of values the variegator /// can pick between.
/// can pick between. /// </remarks>
/// </remarks> public TValue Get(params TKey[] keys) {
public TValue Get(params TKey[] keys) { ISet<TValue> candidates = new HashSet<TValue>();
ISet<TValue> candidates = new HashSet<TValue>();
for(int index = 0; index < keys.Length; ++index) {
for(int index = 0; index < keys.Length; ++index) { ICollection<TValue> valueRange = this.values[keys[index]];
ICollection<TValue> valueRange = this.values[keys[index]];
// If possible access the values by index because it's faster and produces less
// If possible access the values by index because it's faster and produces less // garbage, otherwise fall back to using an enumerator
// garbage, otherwise fall back to using an enumerator var indexableValueRange = valueRange as IList<TValue>;
var indexableValueRange = valueRange as IList<TValue>; if(indexableValueRange == null) {
if(indexableValueRange == null) { foreach(TValue value in valueRange) {
foreach(TValue value in valueRange) { candidates.Add(value);
candidates.Add(value); }
} } else {
} else { for(int valueIndex = 0; valueIndex < indexableValueRange.Count; ++valueIndex) {
for(int valueIndex = 0; valueIndex < indexableValueRange.Count; ++valueIndex) { candidates.Add(indexableValueRange[valueIndex]);
candidates.Add(indexableValueRange[valueIndex]); }
} }
} }
}
TValue result = destructivePickCandidateValue(candidates);
TValue result = destructivePickCandidateValue(candidates); addRecentlyUsedValue(result);
addRecentlyUsedValue(result); return result;
return result; }
}
/// <summary>Picks amongst the values in a set</summary>
/// <summary>Picks amongst the values in a set</summary> /// <param name="candidates">
/// <param name="candidates"> /// Set containing the candidats values to consider. Will be destroyed.
/// Set containing the candidats values to consider. Will be destroyed. /// </param>
/// </param> /// <returns>The least recently used candidate value or a random one</returns>
/// <returns>The least recently used candidate value or a random one</returns> private TValue destructivePickCandidateValue(ISet<TValue> candidates) {
private TValue destructivePickCandidateValue(ISet<TValue> candidates) { removeRecentlyUsedValues(candidates);
removeRecentlyUsedValues(candidates);
switch(candidates.Count) {
switch(candidates.Count) { case 0: {
case 0: { throw new InvalidOperationException("No values mapped to this key");
throw new InvalidOperationException("No values mapped to this key"); }
} case 1: {
case 1: { using(IEnumerator<TValue> enumerator = candidates.GetEnumerator()) {
using(IEnumerator<TValue> enumerator = candidates.GetEnumerator()) { enumerator.MoveNext(); // We can be sure this one returns true
enumerator.MoveNext(); // We can be sure this one returns true return enumerator.Current;
return enumerator.Current; }
} }
} default: {
default: { int index = this.randomNumberGenerator.Next(candidates.Count);
int index = this.randomNumberGenerator.Next(candidates.Count); using(IEnumerator<TValue> enumerator = candidates.GetEnumerator()) {
using(IEnumerator<TValue> enumerator = candidates.GetEnumerator()) { do {
do { --index;
--index; enumerator.MoveNext(); // We can be sure this one returns true
enumerator.MoveNext(); // We can be sure this one returns true } while(index >= 0);
} while(index >= 0);
return enumerator.Current;
return enumerator.Current; }
}
throw new InvalidOperationException(
throw new InvalidOperationException( "ISet.Count was off or random number generator malfunctioned"
"ISet.Count was off or random number generator malfunctioned" );
); }
} }
} }
}
/// <summary>Adds a recently used value to the history</summary>
/// <summary>Adds a recently used value to the history</summary> /// <param name="value">Value that will be added to the history</param>
/// <param name="value">Value that will be added to the history</param> private void addRecentlyUsedValue(TValue value) {
private void addRecentlyUsedValue(TValue value) { if(this.historyTailIndex == this.historyLength) {
if(this.historyTailIndex == this.historyLength) { this.historyFull = true;
this.historyFull = true; this.history[0] = value;
this.history[0] = value; this.historyTailIndex = 1;
this.historyTailIndex = 1; } else {
} else { this.history[this.historyTailIndex] = value;
this.history[this.historyTailIndex] = value; ++this.historyTailIndex;
++this.historyTailIndex; }
} }
}
/// <summary>Removes all values that are in the recent use list from a set</summary>
/// <summary>Removes all values that are in the recent use list from a set</summary> /// <param name="candidates">Set from which recently used values are removed</param>
/// <param name="candidates">Set from which recently used values are removed</param> /// <remarks>
/// <remarks> /// Stops removing values when there's only 1 value left in the set
/// Stops removing values when there's only 1 value left in the set /// </remarks>
/// </remarks> private void removeRecentlyUsedValues(ISet<TValue> candidates) {
private void removeRecentlyUsedValues(ISet<TValue> candidates) { if(candidates.Count <= 1) {
if(candidates.Count <= 1) { return;
return; }
}
if(this.historyFull) { // History buffer has wrapped around
if(this.historyFull) { // History buffer has wrapped around int index = this.historyTailIndex;
int index = this.historyTailIndex; while(index > 0) {
while(index > 0) { --index;
--index; if(candidates.Remove(this.history[index])) {
if(candidates.Remove(this.history[index])) { if(candidates.Count <= 1) {
if(candidates.Count <= 1) { return;
return; }
} }
} }
} index = this.historyLength;
index = this.historyLength; while(index > this.historyTailIndex) {
while(index > this.historyTailIndex) { --index;
--index; if(candidates.Remove(this.history[index])) {
if(candidates.Remove(this.history[index])) { if(candidates.Count <= 1) {
if(candidates.Count <= 1) { return;
return; }
} }
} }
} } else { // History buffer was not full yet
} else { // History buffer was not full yet int index = this.historyTailIndex;
int index = this.historyTailIndex; while(index > 0) {
while(index > 0) { --index;
--index; if(candidates.Remove(this.history[index])) {
if(candidates.Remove(this.history[index])) { if(candidates.Count <= 1) {
if(candidates.Count <= 1) { return;
return; }
} }
} }
} }
} }
}
/// <summary>Frees all memory used by the individual history entries</summary>
/// <summary>Frees all memory used by the individual history entries</summary> /// <remarks>
/// <remarks> /// The history array itself is kept alive and the tail index + full flag will
/// The history array itself is kept alive and the tail index + full flag will /// not be reset.
/// not be reset. /// </remarks>
/// </remarks> private void freeHistory() {
private void freeHistory() { Array.Clear(this.history, 0, this.historyLength);
Array.Clear(this.history, 0, this.historyLength); }
}
/// <summary>Stores the entries the variegator can select from by their keys</summary>
/// <summary>Stores the entries the variegator can select from by their keys</summary> private IMultiDictionary<TKey, TValue> values;
private IMultiDictionary<TKey, TValue> values;
/// <summary>Random number generator that will be used to pick random values</summary>
/// <summary>Random number generator that will be used to pick random values</summary> private Random randomNumberGenerator;
private Random randomNumberGenerator; /// <summary>Number of entries in the recently used list</summary>
/// <summary>Number of entries in the recently used list</summary> private int historyLength;
private int historyLength;
/// <summary>Array containing the most recently provided values</summary>
/// <summary>Array containing the most recently provided values</summary> private TValue[] history;
private TValue[] history; /// <summary>Index of the tail in the recently used value array</summary>
/// <summary>Index of the tail in the recently used value array</summary> private int historyTailIndex;
private int historyTailIndex; /// <summary>Whether the recently used value history is at capacity</summary>
/// <summary>Whether the recently used value history is at capacity</summary> private bool historyFull;
private bool historyFull;
}
}
} // namespace Nuclex.Support.Collections
} // namespace Nuclex.Support.Collections
#endif // !NO_SETS
#endif // !NO_SETS

View File

@ -1,211 +1,210 @@
#region CPL License #region Apache License 2.0
/* /*
Nuclex Framework Nuclex .NET Framework
Copyright (C) 2002-2017 Nuclex Development Labs Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
This library is free software; you can redistribute it and/or Licensed under the Apache License, Version 2.0 (the "License");
modify it under the terms of the IBM Common Public License as you may not use this file except in compliance with the License.
published by the IBM Corporation; either version 1.0 of the You may obtain a copy of the License at
License, or (at your option) any later version.
http://www.apache.org/licenses/LICENSE-2.0
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of Unless required by applicable law or agreed to in writing, software
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the distributed under the License is distributed on an "AS IS" BASIS,
IBM Common Public License for more details. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
You should have received a copy of the IBM Common Public limitations under the License.
License along with this library */
*/ #endregion // Apache License 2.0
#endregion
using System;
using System; using System.Threading;
using System.Threading; using System.Collections;
using System.Collections; using System.Collections.Generic;
using System.Collections.Generic;
namespace Nuclex.Support.Collections {
namespace Nuclex.Support.Collections {
partial class WeakCollection<TItem> {
partial class WeakCollection<TItem> {
#region IEnumerable Members
#region IEnumerable Members
/// <summary>Returns an enumerator that iterates through a collection.</summary>
/// <summary>Returns an enumerator that iterates through a collection.</summary> /// <returns>
/// <returns> /// A System.Collections.IEnumerator object that can be used to iterate through
/// A System.Collections.IEnumerator object that can be used to iterate through /// the collection.
/// the collection. /// </returns>
/// </returns> IEnumerator IEnumerable.GetEnumerator() {
IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator();
return GetEnumerator(); }
}
#endregion
#endregion
#region IList Members
#region IList Members
/// <summary>Adds an item to the WeakCollection.</summary>
/// <summary>Adds an item to the WeakCollection.</summary> /// <param name="value">The System.Object to add to the WeakCollection.</param>
/// <param name="value">The System.Object to add to the WeakCollection.</param> /// <returns>The position into which the new element was inserted.</returns>
/// <returns>The position into which the new element was inserted.</returns> /// <exception cref="System.NotSupportedException">
/// <exception cref="System.NotSupportedException"> /// The System.Collections.IList is read-only or the WeakCollection has a fixed size.
/// The System.Collections.IList is read-only or the WeakCollection has a fixed size. /// </exception>
/// </exception> int IList.Add(object value) {
int IList.Add(object value) { TItem valueAsItemType = downcastToItemType(value);
TItem valueAsItemType = downcastToItemType(value); return (this.items as IList).Add(new WeakReference<TItem>(valueAsItemType));
return (this.items as IList).Add(new WeakReference<TItem>(valueAsItemType)); }
}
/// <summary>
/// <summary> /// Determines whether the WeakCollection contains a specific value.
/// Determines whether the WeakCollection contains a specific value. /// </summary>
/// </summary> /// <param name="value">The System.Object to locate in the WeakCollection.</param>
/// <param name="value">The System.Object to locate in the WeakCollection.</param> /// <returns>
/// <returns> /// True if the System.Object is found in the WeakCollection; otherwise, false.
/// True if the System.Object is found in the WeakCollection; otherwise, false. /// </returns>
/// </returns> bool IList.Contains(object value) {
bool IList.Contains(object value) { TItem valueAsItemType = downcastToItemType(value);
TItem valueAsItemType = downcastToItemType(value); return Contains(valueAsItemType);
return Contains(valueAsItemType); }
}
/// <summary>Determines the index of a specific item in the WeakCollection.</summary>
/// <summary>Determines the index of a specific item in the WeakCollection.</summary> /// <param name="value">The System.Object to locate in the WeakCollection.</param>
/// <param name="value">The System.Object to locate in the WeakCollection.</param> /// <returns>
/// <returns> /// The index of value if found in the list; otherwise, -1.
/// The index of value if found in the list; otherwise, -1. /// </returns>
/// </returns> int IList.IndexOf(object value) {
int IList.IndexOf(object value) { TItem valueAsItemType = downcastToItemType(value);
TItem valueAsItemType = downcastToItemType(value); return IndexOf(valueAsItemType);
return IndexOf(valueAsItemType); }
}
/// <summary>
/// <summary> /// Inserts an item to the WeakCollection at the specified index.
/// Inserts an item to the WeakCollection at the specified index. /// </summary>
/// </summary> /// <param name="index">
/// <param name="index"> /// The zero-based index at which value should be inserted.
/// The zero-based index at which value should be inserted. /// </param>
/// </param> /// <param name="value">The System.Object to insert into the WeakCollection.</param>
/// <param name="value">The System.Object to insert into the WeakCollection.</param> /// <exception cref="System.ArgumentOutOfRangeException">
/// <exception cref="System.ArgumentOutOfRangeException"> /// Index is not a valid index in the TransformingReadOnlyCollection.
/// Index is not a valid index in the TransformingReadOnlyCollection. /// </exception>
/// </exception> /// <exception cref="System.NotSupportedException">
/// <exception cref="System.NotSupportedException"> /// The System.Collections.IList is read-only or the WeakCollection has a fixed size.
/// The System.Collections.IList is read-only or the WeakCollection has a fixed size. /// </exception>
/// </exception> /// <exception cref="System.NullReferenceException">
/// <exception cref="System.NullReferenceException"> /// Value is null reference in the WeakCollection.
/// Value is null reference in the WeakCollection. /// </exception>
/// </exception> void IList.Insert(int index, object value) {
void IList.Insert(int index, object value) { TItem valueAsItemType = downcastToItemType(value);
TItem valueAsItemType = downcastToItemType(value); Insert(index, valueAsItemType);
Insert(index, valueAsItemType); }
}
/// <summary>
/// <summary> /// A value indicating whether the WeakCollection has a fixed size.
/// A value indicating whether the WeakCollection has a fixed size. /// </summary>
/// </summary> bool IList.IsFixedSize {
bool IList.IsFixedSize { get { return (this.items as IList).IsFixedSize; }
get { return (this.items as IList).IsFixedSize; } }
}
/// <summary>
/// <summary> /// Removes the first occurrence of a specific object from the WeakCollection.
/// Removes the first occurrence of a specific object from the WeakCollection. /// </summary>
/// </summary> /// <param name="value">The System.Object to remove from the WeakCollection.</param>
/// <param name="value">The System.Object to remove from the WeakCollection.</param> /// <exception cref="System.NotSupportedException">
/// <exception cref="System.NotSupportedException"> /// The WeakCollection is read-only or the WeakCollection has a fixed size.
/// The WeakCollection is read-only or the WeakCollection has a fixed size. /// </exception>
/// </exception> void IList.Remove(object value) {
void IList.Remove(object value) { TItem valueAsItemType = downcastToItemType(value);
TItem valueAsItemType = downcastToItemType(value); Remove(valueAsItemType);
Remove(valueAsItemType); }
}
/// <summary>Gets or sets the element at the specified index.</summary>
/// <summary>Gets or sets the element at the specified index.</summary> /// <param name="index">The zero-based index of the element to get or set.</param>
/// <param name="index">The zero-based index of the element to get or set.</param> /// <returns>The element at the specified index</returns>
/// <returns>The element at the specified index</returns> /// <exception cref="System.ArgumentOutOfRangeException">
/// <exception cref="System.ArgumentOutOfRangeException"> /// Index is not a valid index in the WeakCollection
/// Index is not a valid index in the WeakCollection /// </exception>
/// </exception> object IList.this[int index] {
object IList.this[int index] { get { return this[index]; }
get { return this[index]; } set {
set { TItem valueAsItemType = downcastToItemType(value);
TItem valueAsItemType = downcastToItemType(value); this[index] = valueAsItemType;
this[index] = valueAsItemType; }
} }
}
#endregion
#endregion
#region ICollection Members
#region ICollection Members
/// <summary>
/// <summary> /// Copies the elements of the WeakCollection to an System.Array, starting at
/// Copies the elements of the WeakCollection to an System.Array, starting at /// a particular System.Array index.
/// a particular System.Array index. /// </summary>
/// </summary> /// <param name="array">
/// <param name="array"> /// The one-dimensional System.Array that is the destination of the elements
/// The one-dimensional System.Array that is the destination of the elements /// copied from WeakCollection. The System.Array must have zero-based indexing.
/// copied from WeakCollection. The System.Array must have zero-based indexing. /// </param>
/// </param> /// <param name="index">The zero-based index in array at which copying begins.</param>
/// <param name="index">The zero-based index in array at which copying begins.</param> /// <exception cref="System.ArgumentNullException">
/// <exception cref="System.ArgumentNullException"> /// Array is null.
/// Array is null. /// </exception>
/// </exception> /// <exception cref="System.ArgumentOutOfRangeException">
/// <exception cref="System.ArgumentOutOfRangeException"> /// Index is less than zero.
/// Index is less than zero. /// </exception>
/// </exception> /// <exception cref="System.ArgumentException">
/// <exception cref="System.ArgumentException"> /// Array is multidimensional or index is equal to or greater than the length
/// 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
/// 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 available space from index to the end of the destination array. /// </exception>
/// </exception> /// <exception cref="System.InvalidCastException">
/// <exception cref="System.InvalidCastException"> /// The type of the source WeakCollection cannot be cast automatically to the type of
/// The type of the source WeakCollection cannot be cast automatically to the type of /// the destination array.
/// the destination array. /// </exception>
/// </exception> void ICollection.CopyTo(Array array, int index) {
void ICollection.CopyTo(Array array, int index) { CopyTo((TItem[])array, index);
CopyTo((TItem[])array, index); }
}
/// <summary>
/// <summary> /// A value indicating whether access to the WeakCollection is
/// A value indicating whether access to the WeakCollection is /// synchronized (thread safe).
/// synchronized (thread safe). /// </summary>
/// </summary> bool ICollection.IsSynchronized {
bool ICollection.IsSynchronized { get { return false; }
get { return false; } }
}
/// <summary>
/// <summary> /// An object that can be used to synchronize access to the WeakCollection.
/// An object that can be used to synchronize access to the WeakCollection. /// </summary>
/// </summary> object ICollection.SyncRoot {
object ICollection.SyncRoot { get {
get { if(this.syncRoot == null) {
if(this.syncRoot == null) { ICollection is2 = this.items as ICollection;
ICollection is2 = this.items as ICollection; if(is2 != null) {
if(is2 != null) { this.syncRoot = is2.SyncRoot;
this.syncRoot = is2.SyncRoot; } else {
} else { Interlocked.CompareExchange(ref this.syncRoot, new object(), null);
Interlocked.CompareExchange(ref this.syncRoot, new object(), null); }
} }
}
return this.syncRoot;
return this.syncRoot; }
} }
}
#endregion
#endregion
/// <summary>
/// <summary> /// Downcasts an object reference to a reference to the collection's item type
/// Downcasts an object reference to a reference to the collection's item type /// </summary>
/// </summary> /// <param name="value">Object reference that will be downcast</param>
/// <param name="value">Object reference that will be downcast</param> /// <returns>
/// <returns> /// The specified object referecne as a reference to the collection's item type
/// The specified object referecne as a reference to the collection's item type /// </returns>
/// </returns> private static TItem downcastToItemType(object value) {
private static TItem downcastToItemType(object value) { TItem valueAsItemType = value as TItem;
TItem valueAsItemType = value as TItem; if(!ReferenceEquals(value, null)) {
if(!ReferenceEquals(value, null)) { if(valueAsItemType == null) {
if(valueAsItemType == null) { throw new ArgumentException("Object is not of a compatible type", "value");
throw new ArgumentException("Object is not of a compatible type", "value"); }
} }
} return valueAsItemType;
return valueAsItemType; }
}
}
}
} // namespace Nuclex.Support.Collections
} // namespace Nuclex.Support.Collections

File diff suppressed because it is too large Load Diff

View File

@ -1,339 +1,338 @@
#region CPL License #region Apache License 2.0
/* /*
Nuclex Framework Nuclex .NET Framework
Copyright (C) 2002-2017 Nuclex Development Labs Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
This library is free software; you can redistribute it and/or Licensed under the Apache License, Version 2.0 (the "License");
modify it under the terms of the IBM Common Public License as you may not use this file except in compliance with the License.
published by the IBM Corporation; either version 1.0 of the You may obtain a copy of the License at
License, or (at your option) any later version.
http://www.apache.org/licenses/LICENSE-2.0
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of Unless required by applicable law or agreed to in writing, software
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the distributed under the License is distributed on an "AS IS" BASIS,
IBM Common Public License for more details. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
You should have received a copy of the IBM Common Public limitations under the License.
License along with this library */
*/ #endregion // Apache License 2.0
#endregion
using System;
using System; using System.Collections;
using System.Collections; using System.Collections.Generic;
using System.Collections.Generic;
namespace Nuclex.Support.Collections {
namespace Nuclex.Support.Collections {
/// <summary>Collection of weakly referenced objects</summary>
/// <summary>Collection of weakly referenced objects</summary> /// <remarks>
/// <remarks> /// This collection tries to expose the interface of a normal collection, but stores
/// 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.
/// 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
/// when the collection detects that one of its items was garbage collected, it /// will silently remove that item.
/// will silently remove that item. /// </remarks>
/// </remarks> public partial class WeakCollection<TItem> : IList<TItem>, IList
public partial class WeakCollection<TItem> : IList<TItem>, IList where TItem : class {
where TItem : class {
#region class UnpackingEnumerator
#region class UnpackingEnumerator
/// <summary>
/// <summary> /// An enumerator that unpacks the items returned by an enumerator of the
/// An enumerator that unpacks the items returned by an enumerator of the /// weak reference collection into the actual item type on-the-fly.
/// weak reference collection into the actual item type on-the-fly. /// </summary>
/// </summary> private class UnpackingEnumerator : IEnumerator<TItem> {
private class UnpackingEnumerator : IEnumerator<TItem> {
/// <summary>Initializes a new unpacking enumerator</summary>
/// <summary>Initializes a new unpacking enumerator</summary> /// <param name="containedTypeEnumerator">
/// <param name="containedTypeEnumerator"> /// Enumerator of the weak reference collection
/// Enumerator of the weak reference collection /// </param>
/// </param> public UnpackingEnumerator(
public UnpackingEnumerator( IEnumerator<WeakReference<TItem>> containedTypeEnumerator
IEnumerator<WeakReference<TItem>> containedTypeEnumerator ) {
) { this.containedTypeEnumerator = containedTypeEnumerator;
this.containedTypeEnumerator = containedTypeEnumerator; }
}
/// <summary>Immediately releases all resources used by the instance</summary>
/// <summary>Immediately releases all resources used by the instance</summary> public void Dispose() {
public void Dispose() { this.containedTypeEnumerator.Dispose();
this.containedTypeEnumerator.Dispose(); }
}
/// <summary>
/// <summary> /// The element in the collection at the current position of the enumerator.
/// The element in the collection at the current position of the enumerator. /// </summary>
/// </summary> public TItem Current {
public TItem Current { get { return this.containedTypeEnumerator.Current.Target; }
get { return this.containedTypeEnumerator.Current.Target; } }
}
/// <summary>Gets the current element in the collection.</summary>
/// <summary>Gets the current element in the collection.</summary> /// <returns>The current element in the collection.</returns>
/// <returns>The current element in the collection.</returns> /// <exception cref="System.InvalidOperationException">
/// <exception cref="System.InvalidOperationException"> /// The enumerator is positioned before the first element of the collection
/// The enumerator is positioned before the first element of the collection /// or after the last element.
/// or after the last element. /// </exception>
/// </exception> public bool MoveNext() {
public bool MoveNext() { return this.containedTypeEnumerator.MoveNext();
return this.containedTypeEnumerator.MoveNext(); }
}
/// <summary>
/// <summary> /// Sets the enumerator to its initial position, which is before the first element
/// Sets the enumerator to its initial position, which is before the first element /// in the collection.
/// in the collection. /// </summary>
/// </summary> /// <exception cref="System.InvalidOperationException">
/// <exception cref="System.InvalidOperationException"> /// The collection was modified after the enumerator was created.
/// The collection was modified after the enumerator was created. /// </exception>
/// </exception> public void Reset() {
public void Reset() { this.containedTypeEnumerator.Reset();
this.containedTypeEnumerator.Reset(); }
}
/// <summary>The current element in the collection.</summary>
/// <summary>The current element in the collection.</summary> /// <exception cref="System.InvalidOperationException">
/// <exception cref="System.InvalidOperationException"> /// The enumerator is positioned before the first element of the collection
/// The enumerator is positioned before the first element of the collection /// or after the last element.
/// or after the last element. /// </exception>
/// </exception> object IEnumerator.Current {
object IEnumerator.Current { get { return Current; }
get { return Current; } }
}
/// <summary>An enumerator from the wrapped collection</summary>
/// <summary>An enumerator from the wrapped collection</summary> private IEnumerator<WeakReference<TItem>> containedTypeEnumerator;
private IEnumerator<WeakReference<TItem>> containedTypeEnumerator;
}
}
#endregion // class UnpackingEnumerator
#endregion // class UnpackingEnumerator
/// <summary>Initializes a new weak reference collection</summary>
/// <summary>Initializes a new weak reference collection</summary> /// <param name="items">
/// <param name="items"> /// Internal list of weak references that are unpacking when accessed through
/// Internal list of weak references that are unpacking when accessed through /// the WeakCollection's interface.
/// the WeakCollection's interface. /// </param>
/// </param> public WeakCollection(IList<WeakReference<TItem>> items) :
public WeakCollection(IList<WeakReference<TItem>> items) : this(items, EqualityComparer<TItem>.Default) { }
this(items, EqualityComparer<TItem>.Default) { }
/// <summary>Initializes a new weak reference collection</summary>
/// <summary>Initializes a new weak reference collection</summary> /// <param name="items">
/// <param name="items"> /// Internal list of weak references that are unpacking when accessed through
/// Internal list of weak references that are unpacking when accessed through /// the WeakCollection's interface.
/// the WeakCollection's interface. /// </param>
/// </param> /// <param name="comparer">
/// <param name="comparer"> /// Comparer used to identify and compare items to each other
/// Comparer used to identify and compare items to each other /// </param>
/// </param> public WeakCollection(
public WeakCollection( IList<WeakReference<TItem>> items, IEqualityComparer<TItem> comparer
IList<WeakReference<TItem>> items, IEqualityComparer<TItem> comparer ) {
) { this.items = items;
this.items = items; this.comparer = comparer;
this.comparer = comparer; }
}
/// <summary>
/// <summary> /// Determines whether an element is in the WeakCollection
/// Determines whether an element is in the WeakCollection /// </summary>
/// </summary> /// <param name="item">
/// <param name="item"> /// The object to locate in the WeakCollection. The value can be null.
/// The object to locate in the WeakCollection. The value can be null. /// </param>
/// </param> /// <returns>
/// <returns> /// True if value is found in the WeakCollection; otherwise, false.
/// True if value is found in the WeakCollection; otherwise, false. /// </returns>
/// </returns> /// <remarks>
/// <remarks> /// The default implementation of this method is very unoptimized and will
/// The default implementation of this method is very unoptimized and will /// enumerate all the items in the collection, transforming one after another
/// enumerate all the items in the collection, transforming one after another /// to check whether the transformed item matches the item the user was
/// to check whether the transformed item matches the item the user was /// looking for. It is recommended to provide a custom implementation of
/// looking for. It is recommended to provide a custom implementation of /// this method, if possible.
/// this method, if possible. /// </remarks>
/// </remarks> public virtual bool Contains(TItem item) {
public virtual bool Contains(TItem item) { return (IndexOf(item) != -1);
return (IndexOf(item) != -1); }
}
/// <summary>
/// <summary> /// Copies the entire WeakCollection to a compatible one-dimensional
/// Copies the entire WeakCollection to a compatible one-dimensional /// System.Array, starting at the specified index of the target array.
/// System.Array, starting at the specified index of the target array. /// </summary>
/// </summary> /// <param name="array">
/// <param name="array"> /// The one-dimensional System.Array that is the destination of the elements copied
/// The one-dimensional System.Array that is the destination of the elements copied /// from the WeakCollection. The System.Array must have zero-based indexing.
/// from the WeakCollection. The System.Array must have zero-based indexing. /// </param>
/// </param> /// <param name="index">
/// <param name="index"> /// The zero-based index in array at which copying begins.
/// The zero-based index in array at which copying begins. /// </param>
/// </param> /// <exception cref="System.ArgumentException">
/// <exception cref="System.ArgumentException"> /// Index is equal to or greater than the length of array or the number of elements
/// 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
/// in the source WeakCollection is greater than the available space from index to /// the end of the destination array.
/// the end of the destination array. /// </exception>
/// </exception> /// <exception cref="System.ArgumentOutOfRangeException">
/// <exception cref="System.ArgumentOutOfRangeException"> /// Index is less than zero.
/// Index is less than zero. /// </exception>
/// </exception> /// <exception cref="System.ArgumentNullException">
/// <exception cref="System.ArgumentNullException"> /// Array is null.
/// Array is null. /// </exception>
/// </exception> public void CopyTo(TItem[] array, int index) {
public void CopyTo(TItem[] array, int index) { if(this.items.Count > (array.Length - index)) {
if(this.items.Count > (array.Length - index)) { throw new ArgumentException(
throw new ArgumentException( "Array too small to fit the collection items starting at the specified index"
"Array too small to fit the collection items starting at the specified index" );
); }
}
for(int itemIndex = 0; itemIndex < this.items.Count; ++itemIndex) {
for(int itemIndex = 0; itemIndex < this.items.Count; ++itemIndex) { array[itemIndex + index] = this.items[itemIndex].Target;
array[itemIndex + index] = this.items[itemIndex].Target; }
} }
}
/// <summary>Removes all items from the WeakCollection</summary>
/// <summary>Removes all items from the WeakCollection</summary> public void Clear() {
public void Clear() { this.items.Clear();
this.items.Clear(); }
}
/// <summary>
/// <summary> /// Returns an enumerator that iterates through the WeakCollection.
/// Returns an enumerator that iterates through the WeakCollection. /// </summary>
/// </summary> /// <returns>An enumerator or the WeakCollection.</returns>
/// <returns>An enumerator or the WeakCollection.</returns> public IEnumerator<TItem> GetEnumerator() {
public IEnumerator<TItem> GetEnumerator() { return new UnpackingEnumerator(this.items.GetEnumerator());
return new UnpackingEnumerator(this.items.GetEnumerator()); }
}
/// <summary>
/// <summary> /// Searches for the specified object and returns the zero-based index of the
/// Searches for the specified object and returns the zero-based index of the /// first occurrence within the entire WeakCollection.
/// first occurrence within the entire WeakCollection. /// </summary>
/// </summary> /// <param name="item">
/// <param name="item"> /// The object to locate in the WeakCollection. The value can
/// The object to locate in the WeakCollection. The value can /// be null for reference types.
/// be null for reference types. /// </param>
/// </param> /// <returns>
/// <returns> /// The zero-based index of the first occurrence of item within the entire
/// The zero-based index of the first occurrence of item within the entire /// WeakCollection, if found; otherwise, -1.
/// WeakCollection, if found; otherwise, -1. /// </returns>
/// </returns> /// <remarks>
/// <remarks> /// The default implementation of this method is very unoptimized and will
/// The default implementation of this method is very unoptimized and will /// enumerate all the items in the collection, transforming one after another
/// enumerate all the items in the collection, transforming one after another /// to check whether the transformed item matches the item the user was
/// to check whether the transformed item matches the item the user was /// looking for. It is recommended to provide a custom implementation of
/// looking for. It is recommended to provide a custom implementation of /// this method, if possible.
/// this method, if possible. /// </remarks>
/// </remarks> public int IndexOf(TItem item) {
public int IndexOf(TItem item) { for(int index = 0; index < this.items.Count; ++index) {
for(int index = 0; index < this.items.Count; ++index) { TItem itemAtIndex = this.items[index].Target;
TItem itemAtIndex = this.items[index].Target; if((itemAtIndex == null) || (item == null)) {
if((itemAtIndex == null) || (item == null)) { if(ReferenceEquals(item, itemAtIndex)) {
if(ReferenceEquals(item, itemAtIndex)) { return index;
return index; }
} } else {
} else { if(this.comparer.Equals(itemAtIndex, item)) {
if(this.comparer.Equals(itemAtIndex, item)) { return index;
return index; }
} }
} }
}
return -1;
return -1; }
}
/// <summary>
/// <summary> /// The number of elements contained in the WeakCollection instance
/// The number of elements contained in the WeakCollection instance /// </summary>
/// </summary> public int Count {
public int Count { get { return this.items.Count; }
get { return this.items.Count; } }
}
/// <summary>Gets the element at the specified index.</summary>
/// <summary>Gets the element at the specified index.</summary> /// <param name="index">The zero-based index of the element to get.</param>
/// <param name="index">The zero-based index of the element to get.</param> /// <returns>The element at the specified index.</returns>
/// <returns>The element at the specified index.</returns> /// <exception cref="System.ArgumentOutOfRangeException">
/// <exception cref="System.ArgumentOutOfRangeException"> /// Index is less than zero or index is equal to or greater than
/// Index is less than zero or index is equal to or greater than /// WeakCollection.Count.
/// WeakCollection.Count. /// </exception>
/// </exception> public TItem this[int index] {
public TItem this[int index] { get { return this.items[index].Target; }
get { return this.items[index].Target; } set { this.items[index] = new WeakReference<TItem>(value); }
set { this.items[index] = new WeakReference<TItem>(value); } }
}
/// <summary>
/// <summary> /// Removes the first occurrence of a specific object from the WeakCollection.
/// Removes the first occurrence of a specific object from the WeakCollection. /// </summary>
/// </summary> /// <param name="item">The object to remove from the WeakCollection</param>
/// <param name="item">The object to remove from the WeakCollection</param> /// <returns>
/// <returns> /// True if item was successfully removed from the WeakCollection; otherwise, false.
/// True if item was successfully removed from the WeakCollection; otherwise, false. /// </returns>
/// </returns> public bool Remove(TItem item) {
public bool Remove(TItem item) { for(int index = 0; index < this.items.Count; ++index) {
for(int index = 0; index < this.items.Count; ++index) { TItem itemAtIndex = this.items[index].Target;
TItem itemAtIndex = this.items[index].Target; if((itemAtIndex == null) || (item == null)) {
if((itemAtIndex == null) || (item == null)) { if(ReferenceEquals(item, itemAtIndex)) {
if(ReferenceEquals(item, itemAtIndex)) { this.items.RemoveAt(index);
this.items.RemoveAt(index); return true;
return true; }
} } else {
} else { if(this.comparer.Equals(item, itemAtIndex)) {
if(this.comparer.Equals(item, itemAtIndex)) { this.items.RemoveAt(index);
this.items.RemoveAt(index); return true;
return true; }
} }
} }
}
return false;
return false; }
}
/// <summary>Adds an item to the WeakCollection.</summary>
/// <summary>Adds an item to the WeakCollection.</summary> /// <param name="item">The object to add to the WeakCollection</param>
/// <param name="item">The object to add to the WeakCollection</param> public void Add(TItem item) {
public void Add(TItem item) { this.items.Add(new WeakReference<TItem>(item));
this.items.Add(new WeakReference<TItem>(item)); }
}
/// <summary>Inserts an item to the WeakCollection at the specified index.</summary>
/// <summary>Inserts an item to the WeakCollection at the specified index.</summary> /// <param name="index">
/// <param name="index"> /// The zero-based index at which item should be inserted.
/// The zero-based index at which item should be inserted. /// </param>
/// </param> /// <param name="item">The object to insert into the WeakCollection</param>
/// <param name="item">The object to insert into the WeakCollection</param> /// <exception cref="System.ArgumentOutOfRangeException">
/// <exception cref="System.ArgumentOutOfRangeException"> /// index is not a valid index in the WeakCollection.
/// index is not a valid index in the WeakCollection. /// </exception>
/// </exception> public void Insert(int index, TItem item) {
public void Insert(int index, TItem item) { this.items.Insert(index, new WeakReference<TItem>(item));
this.items.Insert(index, new WeakReference<TItem>(item)); }
}
/// <summary>
/// <summary> /// Removes the WeakCollection item at the specified index.
/// Removes the WeakCollection item at the specified index. /// </summary>
/// </summary> /// <param name="index">The zero-based index of the item to remove.</param>
/// <param name="index">The zero-based index of the item to remove.</param> /// <exception cref="System.ArgumentOutOfRangeException">
/// <exception cref="System.ArgumentOutOfRangeException"> /// Index is not a valid index in the WeakCollection.
/// Index is not a valid index in the WeakCollection. /// </exception>
/// </exception> public void RemoveAt(int index) {
public void RemoveAt(int index) { this.items.RemoveAt(index);
this.items.RemoveAt(index); }
}
/// <summary>Whether the List is write-protected</summary>
/// <summary>Whether the List is write-protected</summary> public bool IsReadOnly {
public bool IsReadOnly { get { return this.items.IsReadOnly; }
get { return this.items.IsReadOnly; } }
}
/// <summary>
/// <summary> /// Removes the items that have been garbage collected from the collection
/// Removes the items that have been garbage collected from the collection /// </summary>
/// </summary> public void RemoveDeadItems() {
public void RemoveDeadItems() { int newCount = 0;
int newCount = 0;
// Eliminate all items that have been garbage collected by shifting
// Eliminate all items that have been garbage collected by shifting for(int index = 0; index < this.items.Count; ++index) {
for(int index = 0; index < this.items.Count; ++index) { if(this.items[index].IsAlive) {
if(this.items[index].IsAlive) { this.items[newCount] = this.items[index];
this.items[newCount] = this.items[index]; ++newCount;
++newCount; }
} }
}
// If any garbage collected items were found, resize the collection so
// If any garbage collected items were found, resize the collection so // the space that became empty in the previous shifting process will be freed
// the space that became empty in the previous shifting process will be freed while(this.items.Count > newCount) {
while(this.items.Count > newCount) { this.items.RemoveAt(this.items.Count - 1);
this.items.RemoveAt(this.items.Count - 1); }
} }
}
/// <summary>Weak references to the items contained in the collection</summary>
/// <summary>Weak references to the items contained in the collection</summary> private IList<WeakReference<TItem>> items;
private IList<WeakReference<TItem>> items; /// <summary>Used to identify and compare items in the collection</summary>
/// <summary>Used to identify and compare items in the collection</summary> private IEqualityComparer<TItem> comparer;
private IEqualityComparer<TItem> comparer; /// <summary>Synchronization root for threaded accesses to this collection</summary>
/// <summary>Synchronization root for threaded accesses to this collection</summary> private object syncRoot;
private object syncRoot;
}
}
} // namespace Nuclex.Support.Collections
} // namespace Nuclex.Support.Collections

View File

@ -1,124 +1,123 @@
#region CPL License #region Apache License 2.0
/* /*
Nuclex Framework Nuclex .NET Framework
Copyright (C) 2002-2017 Nuclex Development Labs Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
This library is free software; you can redistribute it and/or Licensed under the Apache License, Version 2.0 (the "License");
modify it under the terms of the IBM Common Public License as you may not use this file except in compliance with the License.
published by the IBM Corporation; either version 1.0 of the You may obtain a copy of the License at
License, or (at your option) any later version.
http://www.apache.org/licenses/LICENSE-2.0
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of Unless required by applicable law or agreed to in writing, software
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the distributed under the License is distributed on an "AS IS" BASIS,
IBM Common Public License for more details. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
You should have received a copy of the IBM Common Public limitations under the License.
License along with this library */
*/ #endregion // Apache License 2.0
#endregion
#if UNITTEST
#if UNITTEST
using System;
using System; using System.IO;
using System.IO;
using NUnit.Framework;
using NUnit.Framework;
namespace Nuclex.Support {
namespace Nuclex.Support {
/// <summary>Unit Test for the enumeration helper class</summary>
/// <summary>Unit Test for the enumeration helper class</summary> [TestFixture]
[TestFixture] internal class EnumHelperTest {
internal class EnumHelperTest {
#region enum TestEnumeration
#region enum TestEnumeration
/// <summary>An enumeration used for unit testing</summary>
/// <summary>An enumeration used for unit testing</summary> internal enum TestEnumeration {
internal enum TestEnumeration { /// <summary>First arbitrary enumeration value</summary>
/// <summary>First arbitrary enumeration value</summary> One = -2,
One = -2, /// <summary>Third arbitrary enumeration value</summary>
/// <summary>Third arbitrary enumeration value</summary> Three = 33,
Three = 33, /// <summary>Second arbitrary enumeration value</summary>
/// <summary>Second arbitrary enumeration value</summary> Two = 23
Two = 23 }
}
#endregion // enum TestEnumeration
#endregion // enum TestEnumeration
#region enum EmptyEnumeration
#region enum EmptyEnumeration
internal enum EmptyEnumeration { }
internal enum EmptyEnumeration { }
#endregion // enum EmptyEnumeration
#endregion // enum EmptyEnumeration
/// <summary>
/// <summary> /// Verifies that the enum helper can list the members of an enumeration
/// Verifies that the enum helper can list the members of an enumeration /// </summary>
/// </summary> [Test]
[Test] public void TestGetValues() {
public void TestGetValues() { CollectionAssert.AreEquivalent(
CollectionAssert.AreEquivalent( new TestEnumeration[] {
new TestEnumeration[] { TestEnumeration.One, TestEnumeration.Two, TestEnumeration.Three
TestEnumeration.One, TestEnumeration.Two, TestEnumeration.Three },
}, EnumHelper.GetValues<TestEnumeration>()
EnumHelper.GetValues<TestEnumeration>() );
); }
}
/// <summary>
/// <summary> /// Verifies that the enum helper can locate the highest value in an enumeration
/// Verifies that the enum helper can locate the highest value in an enumeration /// </summary>
/// </summary> [Test]
[Test] public void TestGetHighestValue() {
public void TestGetHighestValue() { Assert.AreEqual(
Assert.AreEqual( TestEnumeration.Three, EnumHelper.GetHighestValue<TestEnumeration>()
TestEnumeration.Three, EnumHelper.GetHighestValue<TestEnumeration>() );
); }
}
/// <summary>
/// <summary> /// Verifies that the enum helper can locate the lowest value in an enumeration
/// Verifies that the enum helper can locate the lowest value in an enumeration /// </summary>
/// </summary> [Test]
[Test] public void TestGetLowestValue() {
public void TestGetLowestValue() { Assert.AreEqual(
Assert.AreEqual( TestEnumeration.One, EnumHelper.GetLowestValue<TestEnumeration>()
TestEnumeration.One, EnumHelper.GetLowestValue<TestEnumeration>() );
); }
}
/// <summary>
/// <summary> /// Tests whether an exception is thrown if the GetValues() method is used on
/// Tests whether an exception is thrown if the GetValues() method is used on /// a non-enumeration type
/// a non-enumeration type /// </summary>
/// </summary> [Test]
[Test] public void TestThrowOnNonEnumType() {
public void TestThrowOnNonEnumType() { Assert.Throws<ArgumentException>(
Assert.Throws<ArgumentException>( delegate() { EnumHelper.GetValues<int>(); }
delegate() { EnumHelper.GetValues<int>(); } );
); }
}
/// <summary>
/// <summary> /// Verifies that the default value for an enumeration is returned if
/// Verifies that the default value for an enumeration is returned if /// the GetLowestValue() method is used on an empty enumeration
/// the GetLowestValue() method is used on an empty enumeration /// </summary>
/// </summary> [Test]
[Test] public void TestLowestValueInEmptyEnumeration() {
public void TestLowestValueInEmptyEnumeration() { Assert.AreEqual(
Assert.AreEqual( default(EmptyEnumeration), EnumHelper.GetLowestValue<EmptyEnumeration>()
default(EmptyEnumeration), EnumHelper.GetLowestValue<EmptyEnumeration>() );
); }
}
/// <summary>
/// <summary> /// Verifies that the default value for an enumeration is returned if
/// Verifies that the default value for an enumeration is returned if /// the GetHighestValue() method is used on an empty enumeration
/// the GetHighestValue() method is used on an empty enumeration /// </summary>
/// </summary> [Test]
[Test] public void TestHighestValueInEmptyEnumeration() {
public void TestHighestValueInEmptyEnumeration() { Assert.AreEqual(
Assert.AreEqual( default(EmptyEnumeration), EnumHelper.GetHighestValue<EmptyEnumeration>()
default(EmptyEnumeration), EnumHelper.GetHighestValue<EmptyEnumeration>() );
); }
}
}
}
} // namespace Nuclex.Support
} // namespace Nuclex.Support
#endif // UNITTEST
#endif // UNITTEST

View File

@ -1,97 +1,96 @@
#region CPL License #region Apache License 2.0
/* /*
Nuclex Framework Nuclex .NET Framework
Copyright (C) 2002-2017 Nuclex Development Labs Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
This library is free software; you can redistribute it and/or Licensed under the Apache License, Version 2.0 (the "License");
modify it under the terms of the IBM Common Public License as you may not use this file except in compliance with the License.
published by the IBM Corporation; either version 1.0 of the You may obtain a copy of the License at
License, or (at your option) any later version.
http://www.apache.org/licenses/LICENSE-2.0
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of Unless required by applicable law or agreed to in writing, software
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the distributed under the License is distributed on an "AS IS" BASIS,
IBM Common Public License for more details. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
You should have received a copy of the IBM Common Public limitations under the License.
License along with this library */
*/ #endregion // Apache License 2.0
#endregion
using System;
using System;
namespace Nuclex.Support {
namespace Nuclex.Support {
/// <summary>Helper methods for enumerations</summary>
/// <summary>Helper methods for enumerations</summary> public static class EnumHelper {
public static class EnumHelper {
/// <summary>Returns the highest value encountered in an enumeration</summary>
/// <summary>Returns the highest value encountered in an enumeration</summary> /// <typeparam name="TEnumeration">
/// <typeparam name="TEnumeration"> /// Enumeration of which the highest value will be returned
/// Enumeration of which the highest value will be returned /// </typeparam>
/// </typeparam> /// <returns>The highest value in the enumeration</returns>
/// <returns>The highest value in the enumeration</returns> public static TEnumeration GetHighestValue<TEnumeration>()
public static TEnumeration GetHighestValue<TEnumeration>() where TEnumeration : IComparable {
where TEnumeration : IComparable { TEnumeration[] values = GetValues<TEnumeration>();
TEnumeration[] values = GetValues<TEnumeration>();
// If the enumeration is empty, return nothing
// If the enumeration is empty, return nothing if(values.Length == 0) {
if(values.Length == 0) { return default(TEnumeration);
return default(TEnumeration); }
}
// Look for the highest value in the enumeration. We initialize the highest value
// Look for the highest value in the enumeration. We initialize the highest value // to the first enumeration value so we don't have to use some arbitrary starting
// to the first enumeration value so we don't have to use some arbitrary starting // value which might actually appear in the enumeration.
// value which might actually appear in the enumeration. TEnumeration highestValue = values[0];
TEnumeration highestValue = values[0]; for(int index = 1; index < values.Length; ++index) {
for(int index = 1; index < values.Length; ++index) { if(values[index].CompareTo(highestValue) > 0) {
if(values[index].CompareTo(highestValue) > 0) { highestValue = values[index];
highestValue = values[index]; }
} }
}
return highestValue;
return highestValue; }
}
/// <summary>Returns the lowest value encountered in an enumeration</summary>
/// <summary>Returns the lowest value encountered in an enumeration</summary> /// <typeparam name="TEnumeration">
/// <typeparam name="TEnumeration"> /// Enumeration of which the lowest value will be returned
/// Enumeration of which the lowest value will be returned /// </typeparam>
/// </typeparam> /// <returns>The lowest value in the enumeration</returns>
/// <returns>The lowest value in the enumeration</returns> public static TEnumeration GetLowestValue<TEnumeration>()
public static TEnumeration GetLowestValue<TEnumeration>() where TEnumeration : IComparable {
where TEnumeration : IComparable { TEnumeration[] values = GetValues<TEnumeration>();
TEnumeration[] values = GetValues<TEnumeration>();
// If the enumeration is empty, return nothing
// If the enumeration is empty, return nothing if(values.Length == 0) {
if(values.Length == 0) { return default(TEnumeration);
return default(TEnumeration); }
}
// Look for the lowest value in the enumeration. We initialize the lowest value
// Look for the lowest value in the enumeration. We initialize the lowest value // to the first enumeration value so we don't have to use some arbitrary starting
// to the first enumeration value so we don't have to use some arbitrary starting // value which might actually appear in the enumeration.
// value which might actually appear in the enumeration. TEnumeration lowestValue = values[0];
TEnumeration lowestValue = values[0]; for(int index = 1; index < values.Length; ++index) {
for(int index = 1; index < values.Length; ++index) { if(values[index].CompareTo(lowestValue) < 0) {
if(values[index].CompareTo(lowestValue) < 0) { lowestValue = values[index];
lowestValue = values[index]; }
} }
}
return lowestValue;
return lowestValue; }
}
/// <summary>Retrieves a list of all values contained in an enumeration</summary>
/// <summary>Retrieves a list of all values contained in an enumeration</summary> /// <typeparam name="TEnum">
/// <typeparam name="TEnum"> /// Type of the enumeration whose values will be returned
/// Type of the enumeration whose values will be returned /// </typeparam>
/// </typeparam> /// <returns>All values contained in the specified enumeration</returns>
/// <returns>All values contained in the specified enumeration</returns> /// <remarks>
/// <remarks> /// This method produces collectable garbage so it's best to only call it once
/// This method produces collectable garbage so it's best to only call it once /// and cache the result.
/// and cache the result. /// </remarks>
/// </remarks> public static TEnum[] GetValues<TEnum>() {
public static TEnum[] GetValues<TEnum>() { return (TEnum[])Enum.GetValues(typeof(TEnum));
return (TEnum[])Enum.GetValues(typeof(TEnum)); }
}
}
}
} // namespace Nuclex.Support } // namespace Nuclex.Support

View File

@ -1,266 +1,265 @@
#region CPL License #region Apache License 2.0
/* /*
Nuclex Framework Nuclex .NET Framework
Copyright (C) 2002-2017 Nuclex Development Labs Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
This library is free software; you can redistribute it and/or Licensed under the Apache License, Version 2.0 (the "License");
modify it under the terms of the IBM Common Public License as you may not use this file except in compliance with the License.
published by the IBM Corporation; either version 1.0 of the You may obtain a copy of the License at
License, or (at your option) any later version.
http://www.apache.org/licenses/LICENSE-2.0
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of Unless required by applicable law or agreed to in writing, software
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the distributed under the License is distributed on an "AS IS" BASIS,
IBM Common Public License for more details. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
You should have received a copy of the IBM Common Public limitations under the License.
License along with this library */
*/ #endregion // Apache License 2.0
#endregion
#if UNITTEST
#if UNITTEST
using System;
using System; using System.Collections.Generic;
using System.Collections.Generic;
using NUnit.Framework;
using NUnit.Framework;
namespace Nuclex.Support {
namespace Nuclex.Support {
/// <summary>Unit Test for the FloatHelper class</summary>
/// <summary>Unit Test for the FloatHelper class</summary> [TestFixture]
[TestFixture] internal class FloatHelperTest {
internal class FloatHelperTest {
/// <summary>Tests the floating point value comparison helper</summary>
/// <summary>Tests the floating point value comparison helper</summary> [Test]
[Test] public void UlpDistancesOnFloatsCompareAsEqual() {
public void UlpDistancesOnFloatsCompareAsEqual() { Assert.IsTrue(
Assert.IsTrue( FloatHelper.AreAlmostEqual(0.00000001f, 0.0000000100000008f, 1),
FloatHelper.AreAlmostEqual(0.00000001f, 0.0000000100000008f, 1), "Minimal difference between very small floating point numbers is considered equal"
"Minimal difference between very small floating point numbers is considered equal" );
); Assert.IsFalse(
Assert.IsFalse( FloatHelper.AreAlmostEqual(0.00000001f, 0.0000000100000017f, 1),
FloatHelper.AreAlmostEqual(0.00000001f, 0.0000000100000017f, 1), "Larger difference between very small floating point numbers is not considered equal"
"Larger difference between very small floating point numbers is not considered equal" );
);
Assert.IsTrue(
Assert.IsTrue( FloatHelper.AreAlmostEqual(1000000.00f, 1000000.06f, 1),
FloatHelper.AreAlmostEqual(1000000.00f, 1000000.06f, 1), "Minimal difference between very large floating point numbers is considered equal"
"Minimal difference between very large floating point numbers is considered equal" );
); Assert.IsFalse(
Assert.IsFalse( FloatHelper.AreAlmostEqual(1000000.00f, 1000000.13f, 1),
FloatHelper.AreAlmostEqual(1000000.00f, 1000000.13f, 1), "Larger difference between very large floating point numbers is not considered equal"
"Larger difference between very large floating point numbers is not considered equal" );
); }
}
/// <summary>Tests the double precision floating point value comparison helper</summary>
/// <summary>Tests the double precision floating point value comparison helper</summary> [Test]
[Test] public void UlpDistancesOnDoublesCompareAsEqual() {
public void UlpDistancesOnDoublesCompareAsEqual() { Assert.IsTrue(
Assert.IsTrue( FloatHelper.AreAlmostEqual(0.00000001, 0.000000010000000000000002, 1),
FloatHelper.AreAlmostEqual(0.00000001, 0.000000010000000000000002, 1), "Minimal difference between very small double precision floating point " +
"Minimal difference between very small double precision floating point " + "numbers is considered equal"
"numbers is considered equal" );
); Assert.IsFalse(
Assert.IsFalse( FloatHelper.AreAlmostEqual(0.00000001, 0.000000010000000000000004, 1),
FloatHelper.AreAlmostEqual(0.00000001, 0.000000010000000000000004, 1), "Larger difference between very small double precision floating point " +
"Larger difference between very small double precision floating point " + "numbers is not considered equal"
"numbers is not considered equal" );
);
Assert.IsTrue(
Assert.IsTrue( FloatHelper.AreAlmostEqual(1000000.00, 1000000.0000000001, 1),
FloatHelper.AreAlmostEqual(1000000.00, 1000000.0000000001, 1), "Minimal difference between very large double precision floating point " +
"Minimal difference between very large double precision floating point " + "numbers is considered equal"
"numbers is considered equal" );
); Assert.IsFalse(
Assert.IsFalse( FloatHelper.AreAlmostEqual(1000000.00, 1000000.0000000002, 1),
FloatHelper.AreAlmostEqual(1000000.00, 1000000.0000000002, 1), "Larger difference between very large double precision floating point " +
"Larger difference between very large double precision floating point " + "numbers is not considered equal"
"numbers is not considered equal" );
); }
}
/// <summary>Tests the integer reinterpretation functions</summary>
/// <summary>Tests the integer reinterpretation functions</summary> [Test]
[Test] public void IntegersCanBeReinterpretedAsFloats() {
public void IntegersCanBeReinterpretedAsFloats() { Assert.AreEqual(
Assert.AreEqual( 12345.0f,
12345.0f, FloatHelper.ReinterpretAsFloat(FloatHelper.ReinterpretAsInt(12345.0f)),
FloatHelper.ReinterpretAsFloat(FloatHelper.ReinterpretAsInt(12345.0f)), "Number hasn't changed after mirrored reinterpretation"
"Number hasn't changed after mirrored reinterpretation" );
); }
}
/// <summary>Tests the long reinterpretation functions</summary>
/// <summary>Tests the long reinterpretation functions</summary> [Test]
[Test] public void LongsCanBeReinterpretedAsDoubles() {
public void LongsCanBeReinterpretedAsDoubles() { Assert.AreEqual(
Assert.AreEqual( 12345.67890,
12345.67890, FloatHelper.ReinterpretAsDouble(FloatHelper.ReinterpretAsLong(12345.67890)),
FloatHelper.ReinterpretAsDouble(FloatHelper.ReinterpretAsLong(12345.67890)), "Number hasn't changed after mirrored reinterpretation"
"Number hasn't changed after mirrored reinterpretation" );
); }
}
/// <summary>Tests the floating point reinterpretation functions</summary>
/// <summary>Tests the floating point reinterpretation functions</summary> [Test]
[Test] public void FloatsCanBeReinterpretedAsIntegers() {
public void FloatsCanBeReinterpretedAsIntegers() { Assert.AreEqual(
Assert.AreEqual( 12345,
12345, FloatHelper.ReinterpretAsInt(FloatHelper.ReinterpretAsFloat(12345)),
FloatHelper.ReinterpretAsInt(FloatHelper.ReinterpretAsFloat(12345)), "Number hasn't changed after mirrored reinterpretation"
"Number hasn't changed after mirrored reinterpretation" );
); }
}
/// <summary>
/// <summary> /// Verifies that the IsZero() method can distinguish zero from very small values
/// Verifies that the IsZero() method can distinguish zero from very small values /// </summary>
/// </summary> [Test]
[Test] public void CanDetermineIfFloatIsZero() {
public void CanDetermineIfFloatIsZero() { Assert.IsTrue(FloatHelper.IsZero(FloatHelper.PositiveZeroFloat));
Assert.IsTrue(FloatHelper.IsZero(FloatHelper.PositiveZeroFloat)); Assert.IsTrue(FloatHelper.IsZero(FloatHelper.NegativeZeroFloat));
Assert.IsTrue(FloatHelper.IsZero(FloatHelper.NegativeZeroFloat)); Assert.IsFalse(FloatHelper.IsZero(1.401298E-45f));
Assert.IsFalse(FloatHelper.IsZero(1.401298E-45f)); Assert.IsFalse(FloatHelper.IsZero(-1.401298E-45f));
Assert.IsFalse(FloatHelper.IsZero(-1.401298E-45f)); }
}
/// <summary>
/// <summary> /// Verifies that the IsZero() method can distinguish zero from very small values
/// Verifies that the IsZero() method can distinguish zero from very small values /// </summary>
/// </summary> [Test]
[Test] public void CanDetermineIfDoubleIsZero() {
public void CanDetermineIfDoubleIsZero() { Assert.IsTrue(FloatHelper.IsZero(FloatHelper.PositiveZeroDouble));
Assert.IsTrue(FloatHelper.IsZero(FloatHelper.PositiveZeroDouble)); Assert.IsTrue(FloatHelper.IsZero(FloatHelper.NegativeZeroDouble));
Assert.IsTrue(FloatHelper.IsZero(FloatHelper.NegativeZeroDouble)); Assert.IsFalse(FloatHelper.IsZero(4.94065645841247E-324));
Assert.IsFalse(FloatHelper.IsZero(4.94065645841247E-324)); Assert.IsFalse(FloatHelper.IsZero(-4.94065645841247E-324));
Assert.IsFalse(FloatHelper.IsZero(-4.94065645841247E-324)); }
}
/// <summary>
/// <summary> /// Tests the double prevision floating point reinterpretation functions
/// Tests the double prevision floating point reinterpretation functions /// </summary>
/// </summary> [Test]
[Test] public void DoublesCanBeReinterpretedAsLongs() {
public void DoublesCanBeReinterpretedAsLongs() { Assert.AreEqual(
Assert.AreEqual( 1234567890,
1234567890, FloatHelper.ReinterpretAsLong(FloatHelper.ReinterpretAsDouble(1234567890)),
FloatHelper.ReinterpretAsLong(FloatHelper.ReinterpretAsDouble(1234567890)), "Number hasn't changed after mirrored reinterpretation"
"Number hasn't changed after mirrored reinterpretation" );
); }
}
/// <summary>
/// <summary> /// Verifies that two denormalized floats can be compared in ulps
/// Verifies that two denormalized floats can be compared in ulps /// </summary>
/// </summary> [Test]
[Test] public void DenormalizedFloatsCanBeCompared() {
public void DenormalizedFloatsCanBeCompared() { float zero = 0.0f;
float zero = 0.0f; float zeroPlusOneUlp = FloatHelper.ReinterpretAsFloat(
float zeroPlusOneUlp = FloatHelper.ReinterpretAsFloat( FloatHelper.ReinterpretAsInt(zero) + 1
FloatHelper.ReinterpretAsInt(zero) + 1 );
); float zeroMinusOneUlp = -zeroPlusOneUlp;
float zeroMinusOneUlp = -zeroPlusOneUlp;
// Across zero
// Across zero Assert.IsFalse(FloatHelper.AreAlmostEqual(zeroMinusOneUlp, zeroPlusOneUlp, 1));
Assert.IsFalse(FloatHelper.AreAlmostEqual(zeroMinusOneUlp, zeroPlusOneUlp, 1)); Assert.IsTrue(FloatHelper.AreAlmostEqual(zeroPlusOneUlp, zeroMinusOneUlp, 2));
Assert.IsTrue(FloatHelper.AreAlmostEqual(zeroPlusOneUlp, zeroMinusOneUlp, 2));
// Against zero
// Against zero Assert.IsFalse(FloatHelper.AreAlmostEqual(zero, zeroPlusOneUlp, 0));
Assert.IsFalse(FloatHelper.AreAlmostEqual(zero, zeroPlusOneUlp, 0)); Assert.IsTrue(FloatHelper.AreAlmostEqual(zero, zeroPlusOneUlp, 1));
Assert.IsTrue(FloatHelper.AreAlmostEqual(zero, zeroPlusOneUlp, 1)); Assert.IsFalse(FloatHelper.AreAlmostEqual(zero, zeroMinusOneUlp, 0));
Assert.IsFalse(FloatHelper.AreAlmostEqual(zero, zeroMinusOneUlp, 0)); Assert.IsTrue(FloatHelper.AreAlmostEqual(zero, zeroMinusOneUlp, 1));
Assert.IsTrue(FloatHelper.AreAlmostEqual(zero, zeroMinusOneUlp, 1)); }
}
/// <summary>
/// <summary> /// Verifies that the negative floating point zero is within one ulp of the positive
/// Verifies that the negative floating point zero is within one ulp of the positive /// floating point zero and vice versa
/// floating point zero and vice versa /// </summary>
/// </summary> [Test]
[Test] public void NegativeZeroFloatEqualsPositiveZero() {
public void NegativeZeroFloatEqualsPositiveZero() { Assert.IsTrue(
Assert.IsTrue( FloatHelper.AreAlmostEqual(
FloatHelper.AreAlmostEqual( FloatHelper.NegativeZeroFloat, FloatHelper.PositiveZeroFloat, 0
FloatHelper.NegativeZeroFloat, FloatHelper.PositiveZeroFloat, 0 )
) );
); Assert.IsTrue(
Assert.IsTrue( FloatHelper.AreAlmostEqual(
FloatHelper.AreAlmostEqual( FloatHelper.PositiveZeroFloat, FloatHelper.NegativeZeroFloat, 0
FloatHelper.PositiveZeroFloat, FloatHelper.NegativeZeroFloat, 0 )
) );
); }
}
/// <summary>Verifies that floats can be compared across the zero boundary</summary>
/// <summary>Verifies that floats can be compared across the zero boundary</summary> [Test]
[Test] public void FloatsCanBeComparedAcrossZeroInUlps() {
public void FloatsCanBeComparedAcrossZeroInUlps() { float tenUlps = float.Epsilon * 10.0f;
float tenUlps = float.Epsilon * 10.0f;
Assert.IsTrue(FloatHelper.AreAlmostEqual(-tenUlps, tenUlps, 20));
Assert.IsTrue(FloatHelper.AreAlmostEqual(-tenUlps, tenUlps, 20)); Assert.IsTrue(FloatHelper.AreAlmostEqual(tenUlps, -tenUlps, 20));
Assert.IsTrue(FloatHelper.AreAlmostEqual(tenUlps, -tenUlps, 20)); Assert.IsFalse(FloatHelper.AreAlmostEqual(-tenUlps, tenUlps, 19));
Assert.IsFalse(FloatHelper.AreAlmostEqual(-tenUlps, tenUlps, 19));
Assert.IsTrue(FloatHelper.AreAlmostEqual(-tenUlps, 0, 10));
Assert.IsTrue(FloatHelper.AreAlmostEqual(-tenUlps, 0, 10)); Assert.IsTrue(FloatHelper.AreAlmostEqual(0, -tenUlps, 10));
Assert.IsTrue(FloatHelper.AreAlmostEqual(0, -tenUlps, 10)); Assert.IsFalse(FloatHelper.AreAlmostEqual(-tenUlps, 0, 9));
Assert.IsFalse(FloatHelper.AreAlmostEqual(-tenUlps, 0, 9));
Assert.IsTrue(FloatHelper.AreAlmostEqual(0, tenUlps, 10));
Assert.IsTrue(FloatHelper.AreAlmostEqual(0, tenUlps, 10)); Assert.IsTrue(FloatHelper.AreAlmostEqual(tenUlps, 0, 10));
Assert.IsTrue(FloatHelper.AreAlmostEqual(tenUlps, 0, 10)); Assert.IsFalse(FloatHelper.AreAlmostEqual(0, tenUlps, 9));
Assert.IsFalse(FloatHelper.AreAlmostEqual(0, tenUlps, 9)); }
}
/// <summary>
/// <summary> /// Verifies that two denormalized doubles can be compared in ulps
/// Verifies that two denormalized doubles can be compared in ulps /// </summary>
/// </summary> [Test]
[Test] public void DenormalizedDoublesCanBeCompared() {
public void DenormalizedDoublesCanBeCompared() { double zero = 0.0;
double zero = 0.0; double zeroPlusOneUlp = FloatHelper.ReinterpretAsDouble(
double zeroPlusOneUlp = FloatHelper.ReinterpretAsDouble( FloatHelper.ReinterpretAsLong(zero) + 1
FloatHelper.ReinterpretAsLong(zero) + 1 );
); double zeroMinusOneUlp = -zeroPlusOneUlp;
double zeroMinusOneUlp = -zeroPlusOneUlp;
// Across zero
// Across zero Assert.IsFalse(FloatHelper.AreAlmostEqual(zeroMinusOneUlp, zeroPlusOneUlp, 1));
Assert.IsFalse(FloatHelper.AreAlmostEqual(zeroMinusOneUlp, zeroPlusOneUlp, 1)); Assert.IsTrue(FloatHelper.AreAlmostEqual(zeroPlusOneUlp, zeroMinusOneUlp, 2));
Assert.IsTrue(FloatHelper.AreAlmostEqual(zeroPlusOneUlp, zeroMinusOneUlp, 2));
// Against zero
// Against zero Assert.IsFalse(FloatHelper.AreAlmostEqual(zero, zeroPlusOneUlp, 0));
Assert.IsFalse(FloatHelper.AreAlmostEqual(zero, zeroPlusOneUlp, 0)); Assert.IsTrue(FloatHelper.AreAlmostEqual(zero, zeroPlusOneUlp, 1));
Assert.IsTrue(FloatHelper.AreAlmostEqual(zero, zeroPlusOneUlp, 1)); Assert.IsFalse(FloatHelper.AreAlmostEqual(zero, zeroMinusOneUlp, 0));
Assert.IsFalse(FloatHelper.AreAlmostEqual(zero, zeroMinusOneUlp, 0)); Assert.IsTrue(FloatHelper.AreAlmostEqual(zero, zeroMinusOneUlp, 1));
Assert.IsTrue(FloatHelper.AreAlmostEqual(zero, zeroMinusOneUlp, 1)); }
}
/// <summary>
/// <summary> /// Verifies that the negative double precision floating point zero is within one ulp
/// Verifies that the negative double precision floating point zero is within one ulp /// of the positive double precision floating point zero and vice versa
/// of the positive double precision floating point zero and vice versa /// </summary>
/// </summary> [Test]
[Test] public void NegativeZeroDoubleEqualsPositiveZero() {
public void NegativeZeroDoubleEqualsPositiveZero() { Assert.IsTrue(
Assert.IsTrue( FloatHelper.AreAlmostEqual(
FloatHelper.AreAlmostEqual( FloatHelper.NegativeZeroDouble, FloatHelper.NegativeZeroDouble, 0
FloatHelper.NegativeZeroDouble, FloatHelper.NegativeZeroDouble, 0 )
) );
); Assert.IsTrue(
Assert.IsTrue( FloatHelper.AreAlmostEqual(
FloatHelper.AreAlmostEqual( FloatHelper.NegativeZeroDouble, FloatHelper.NegativeZeroDouble, 0
FloatHelper.NegativeZeroDouble, FloatHelper.NegativeZeroDouble, 0 )
) );
); }
}
/// <summary>Verifies that doubles can be compared across the zero boundary</summary>
/// <summary>Verifies that doubles can be compared across the zero boundary</summary> [Test]
[Test] public void DoublesCanBeComparedAcrossZeroInUlps() {
public void DoublesCanBeComparedAcrossZeroInUlps() { double tenUlps = double.Epsilon * 10.0;
double tenUlps = double.Epsilon * 10.0;
Assert.IsTrue(FloatHelper.AreAlmostEqual(-tenUlps, tenUlps, 20));
Assert.IsTrue(FloatHelper.AreAlmostEqual(-tenUlps, tenUlps, 20)); Assert.IsTrue(FloatHelper.AreAlmostEqual(tenUlps, -tenUlps, 20));
Assert.IsTrue(FloatHelper.AreAlmostEqual(tenUlps, -tenUlps, 20)); Assert.IsFalse(FloatHelper.AreAlmostEqual(-tenUlps, tenUlps, 19));
Assert.IsFalse(FloatHelper.AreAlmostEqual(-tenUlps, tenUlps, 19));
Assert.IsTrue(FloatHelper.AreAlmostEqual(-tenUlps, 0, 10));
Assert.IsTrue(FloatHelper.AreAlmostEqual(-tenUlps, 0, 10)); Assert.IsTrue(FloatHelper.AreAlmostEqual(0, -tenUlps, 10));
Assert.IsTrue(FloatHelper.AreAlmostEqual(0, -tenUlps, 10)); Assert.IsFalse(FloatHelper.AreAlmostEqual(-tenUlps, 0, 9));
Assert.IsFalse(FloatHelper.AreAlmostEqual(-tenUlps, 0, 9));
Assert.IsTrue(FloatHelper.AreAlmostEqual(0, tenUlps, 10));
Assert.IsTrue(FloatHelper.AreAlmostEqual(0, tenUlps, 10)); Assert.IsTrue(FloatHelper.AreAlmostEqual(tenUlps, 0, 10));
Assert.IsTrue(FloatHelper.AreAlmostEqual(tenUlps, 0, 10)); Assert.IsFalse(FloatHelper.AreAlmostEqual(0, tenUlps, 9));
Assert.IsFalse(FloatHelper.AreAlmostEqual(0, tenUlps, 9)); }
}
}
}
} // namespace Nuclex.Support
} // namespace Nuclex.Support
#endif // UNITTEST
#endif // UNITTEST

View File

@ -1,314 +1,313 @@
#region CPL License #region Apache License 2.0
/* /*
Nuclex Framework Nuclex .NET Framework
Copyright (C) 2002-2017 Nuclex Development Labs Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
This library is free software; you can redistribute it and/or Licensed under the Apache License, Version 2.0 (the "License");
modify it under the terms of the IBM Common Public License as you may not use this file except in compliance with the License.
published by the IBM Corporation; either version 1.0 of the You may obtain a copy of the License at
License, or (at your option) any later version.
http://www.apache.org/licenses/LICENSE-2.0
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of Unless required by applicable law or agreed to in writing, software
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the distributed under the License is distributed on an "AS IS" BASIS,
IBM Common Public License for more details. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
You should have received a copy of the IBM Common Public limitations under the License.
License along with this library */
*/ #endregion // Apache License 2.0
#endregion
using System;
using System; using System.Runtime.InteropServices;
using System.Runtime.InteropServices;
namespace Nuclex.Support {
namespace Nuclex.Support {
/// <summary>Helper routines for working with floating point numbers</summary>
/// <summary>Helper routines for working with floating point numbers</summary> /// <remarks>
/// <remarks> /// <para>
/// <para> /// The floating point comparison code is based on this excellent article:
/// The floating point comparison code is based on this excellent article: /// http://www.cygnus-software.com/papers/comparingfloats/comparingfloats.htm
/// http://www.cygnus-software.com/papers/comparingfloats/comparingfloats.htm /// </para>
/// </para> /// <para>
/// <para> /// "ULP" means Unit in the Last Place and in the context of this library refers to
/// "ULP" means Unit in the Last Place and in the context of this library refers to /// the distance between two adjacent floating point numbers. IEEE floating point
/// the distance between two adjacent floating point numbers. IEEE floating point /// numbers can only represent a finite subset of natural numbers, with greater
/// numbers can only represent a finite subset of natural numbers, with greater /// accuracy for smaller numbers and lower accuracy for very large numbers.
/// accuracy for smaller numbers and lower accuracy for very large numbers. /// </para>
/// </para> /// <para>
/// <para> /// If a comparison is allowed "2 ulps" of deviation, that means the values are
/// If a comparison is allowed "2 ulps" of deviation, that means the values are /// allowed to deviate by up to 2 adjacent floating point values, which might be
/// allowed to deviate by up to 2 adjacent floating point values, which might be /// as low as 0.0000001 for small numbers or as high as 10.0 for large numbers.
/// as low as 0.0000001 for small numbers or as high as 10.0 for large numbers. /// </para>
/// </para> /// </remarks>
/// </remarks> public static class FloatHelper {
public static class FloatHelper {
#region struct FloatIntUnion
#region struct FloatIntUnion
/// <summary>Union of a floating point variable and an integer</summary>
/// <summary>Union of a floating point variable and an integer</summary> [StructLayout(LayoutKind.Explicit)]
[StructLayout(LayoutKind.Explicit)] private struct FloatIntUnion {
private struct FloatIntUnion {
/// <summary>The union's value as a floating point variable</summary>
/// <summary>The union's value as a floating point variable</summary> [FieldOffset(0)]
[FieldOffset(0)] public float Float;
public float Float;
/// <summary>The union's value as an integer</summary>
/// <summary>The union's value as an integer</summary> [FieldOffset(0)]
[FieldOffset(0)] public int Int;
public int Int;
/// <summary>The union's value as an unsigned integer</summary>
/// <summary>The union's value as an unsigned integer</summary> [FieldOffset(0)]
[FieldOffset(0)] public uint UInt;
public uint UInt;
}
}
#endregion // struct FloatIntUnion
#endregion // struct FloatIntUnion
#region struct DoubleLongUnion
#region struct DoubleLongUnion
/// <summary>Union of a double precision floating point variable and a long</summary>
/// <summary>Union of a double precision floating point variable and a long</summary> [StructLayout(LayoutKind.Explicit)]
[StructLayout(LayoutKind.Explicit)] private struct DoubleLongUnion {
private struct DoubleLongUnion {
/// <summary>The union's value as a double precision floating point variable</summary>
/// <summary>The union's value as a double precision floating point variable</summary> [FieldOffset(0)]
[FieldOffset(0)] public double Double;
public double Double;
/// <summary>The union's value as a long</summary>
/// <summary>The union's value as a long</summary> [FieldOffset(0)]
[FieldOffset(0)] public long Long;
public long Long;
/// <summary>The union's value as an unsigned long</summary>
/// <summary>The union's value as an unsigned long</summary> [FieldOffset(0)]
[FieldOffset(0)] public ulong ULong;
public ulong ULong;
}
}
#endregion // struct DoubleLongUnion
#endregion // struct DoubleLongUnion
/// <summary>A floating point value that holds a positive zero</summary>
/// <summary>A floating point value that holds a positive zero</summary> public const float PositiveZeroFloat = +0.0f;
public const float PositiveZeroFloat = +0.0f;
/// <summary>A floating point value that holds a negative zero</summary>
/// <summary>A floating point value that holds a negative zero</summary> /// <remarks>
/// <remarks> /// Negative zeros have a special representation in IEEE 752 floating point math
/// Negative zeros have a special representation in IEEE 752 floating point math /// </remarks>
/// </remarks> public const float NegativeZeroFloat = -0.0f;
public const float NegativeZeroFloat = -0.0f;
/// <summary>A double precision floating point value that holds a positive zero</summary>
/// <summary>A double precision floating point value that holds a positive zero</summary> public const double PositiveZeroDouble = +0.0;
public const double PositiveZeroDouble = +0.0;
/// <summary>A doublep precision floating point value that holds a negative zero</summary>
/// <summary>A doublep precision floating point value that holds a negative zero</summary> /// <remarks>
/// <remarks> /// Negative zeros have a special representation in IEEE 752 floating point math
/// Negative zeros have a special representation in IEEE 752 floating point math /// </remarks>
/// </remarks> public const double NegativeZeroDouble = -0.0;
public const double NegativeZeroDouble = -0.0;
/// <summary>Checks whether the floating point value is exactly zero</summary>
/// <summary>Checks whether the floating point value is exactly zero</summary> /// <param name="value">Value that will be checked for being zero</param>
/// <param name="value">Value that will be checked for being zero</param> /// <returns>True if the value is zero, false otherwise</returns>
/// <returns>True if the value is zero, false otherwise</returns> public static bool IsZero(float value) {
public static bool IsZero(float value) { return (value == PositiveZeroFloat) || (value == NegativeZeroFloat);
return (value == PositiveZeroFloat) || (value == NegativeZeroFloat); }
}
/// <summary>
/// <summary> /// Checks whether the double precision floating point value is exactly zero
/// Checks whether the double precision floating point value is exactly zero /// </summary>
/// </summary> /// <param name="value">Value that will be checked for being zero</param>
/// <param name="value">Value that will be checked for being zero</param> /// <returns>True if the value is zero, false otherwise</returns>
/// <returns>True if the value is zero, false otherwise</returns> public static bool IsZero(double value) {
public static bool IsZero(double value) { return (value == PositiveZeroDouble) || (value == NegativeZeroDouble);
return (value == PositiveZeroDouble) || (value == NegativeZeroDouble); }
}
/// <summary>Compares two floating point values for equality</summary>
/// <summary>Compares two floating point values for equality</summary> /// <param name="left">First floating point value to be compared</param>
/// <param name="left">First floating point value to be compared</param> /// <param name="right">Second floating point value t be compared</param>
/// <param name="right">Second floating point value t be compared</param> /// <param name="maxUlps">
/// <param name="maxUlps"> /// Maximum number of representable floating point values that are allowed to
/// Maximum number of representable floating point values that are allowed to /// be between the left and the right floating point values
/// be between the left and the right floating point values /// </param>
/// </param> /// <returns>True if both numbers are equal or close to being equal</returns>
/// <returns>True if both numbers are equal or close to being equal</returns> /// <remarks>
/// <remarks> /// <para>
/// <para> /// Floating point values can only represent a finite subset of natural numbers.
/// Floating point values can only represent a finite subset of natural numbers. /// For example, the values 2.00000000 and 2.00000024 can be stored in a float,
/// For example, the values 2.00000000 and 2.00000024 can be stored in a float, /// but nothing inbetween them.
/// but nothing inbetween them. /// </para>
/// </para> /// <para>
/// <para> /// This comparison will count how many possible floating point values are between
/// This comparison will count how many possible floating point values are between /// the left and the right number. If the number of possible values between both
/// the left and the right number. If the number of possible values between both /// numbers is less than or equal to maxUlps, then the numbers are considered as
/// numbers is less than or equal to maxUlps, then the numbers are considered as /// being equal.
/// being equal. /// </para>
/// </para> /// <para>
/// <para> /// Implementation partially follows the code outlined here (link now defunct):
/// Implementation partially follows the code outlined here (link now defunct): /// http://www.anttirt.net/2007/08/19/proper-floating-point-comparisons/
/// http://www.anttirt.net/2007/08/19/proper-floating-point-comparisons/ /// And here:
/// And here: /// http://www.altdevblogaday.com/2012/02/22/comparing-floating-point-numbers-2012-edition/
/// http://www.altdevblogaday.com/2012/02/22/comparing-floating-point-numbers-2012-edition/ /// </para>
/// </para> /// </remarks>
/// </remarks> public static bool AreAlmostEqual(float left, float right, int maxUlps) {
public static bool AreAlmostEqual(float left, float right, int maxUlps) { var leftUnion = new FloatIntUnion();
var leftUnion = new FloatIntUnion(); var rightUnion = new FloatIntUnion();
var rightUnion = new FloatIntUnion();
leftUnion.Float = left;
leftUnion.Float = left; rightUnion.Float = right;
rightUnion.Float = right;
if(leftUnion.Int < 0) {
if(leftUnion.Int < 0) { leftUnion.Int = unchecked((int)0x80000000 - leftUnion.Int);
leftUnion.Int = unchecked((int)0x80000000 - leftUnion.Int); }
} if(rightUnion.Int < 0) {
if(rightUnion.Int < 0) { rightUnion.Int = unchecked((int)0x80000000 - rightUnion.Int);
rightUnion.Int = unchecked((int)0x80000000 - rightUnion.Int); }
}
return Math.Abs(rightUnion.Int - leftUnion.Int) <= maxUlps;
return Math.Abs(rightUnion.Int - leftUnion.Int) <= maxUlps; }
} #if false
#if false public static bool OldAreAlmostEqual(float left, float right, int maxUlps) {
public static bool OldAreAlmostEqual(float left, float right, int maxUlps) { FloatInt32Union leftUnion = new FloatInt32Union();
FloatInt32Union leftUnion = new FloatInt32Union(); FloatInt32Union rightUnion = new FloatInt32Union();
FloatInt32Union rightUnion = new FloatInt32Union();
leftUnion.Float = left;
leftUnion.Float = left; rightUnion.Float = right;
rightUnion.Float = right;
uint leftSignMask = (leftUnion.UInt >> 31);
uint leftSignMask = (leftUnion.UInt >> 31); uint rightSignMask = (rightUnion.UInt >> 31);
uint rightSignMask = (rightUnion.UInt >> 31);
uint leftTemp = ((0x80000000 - leftUnion.UInt) & leftSignMask);
uint leftTemp = ((0x80000000 - leftUnion.UInt) & leftSignMask); leftUnion.UInt = leftTemp | (leftUnion.UInt & ~leftSignMask);
leftUnion.UInt = leftTemp | (leftUnion.UInt & ~leftSignMask);
uint rightTemp = ((0x80000000 - rightUnion.UInt) & rightSignMask);
uint rightTemp = ((0x80000000 - rightUnion.UInt) & rightSignMask); rightUnion.UInt = rightTemp | (rightUnion.UInt & ~rightSignMask);
rightUnion.UInt = rightTemp | (rightUnion.UInt & ~rightSignMask);
return (Math.Abs(leftUnion.Int - rightUnion.Int) <= maxUlps);
return (Math.Abs(leftUnion.Int - rightUnion.Int) <= maxUlps); }
} #endif
#endif
/// <summary>Compares two double precision floating point values for equality</summary>
/// <summary>Compares two double precision floating point values for equality</summary> /// <param name="left">First double precision floating point value to be compared</param>
/// <param name="left">First double precision floating point value to be compared</param> /// <param name="right">Second double precision floating point value t be compared</param>
/// <param name="right">Second double precision floating point value t be compared</param> /// <param name="maxUlps">
/// <param name="maxUlps"> /// Maximum number of representable double precision floating point values that are
/// Maximum number of representable double precision floating point values that are /// allowed to be between the left and the right double precision floating point values
/// allowed to be between the left and the right double precision floating point values /// </param>
/// </param> /// <returns>True if both numbers are equal or close to being equal</returns>
/// <returns>True if both numbers are equal or close to being equal</returns> /// <remarks>
/// <remarks> /// <para>
/// <para> /// Double precision floating point values can only represent a limited series of
/// Double precision floating point values can only represent a limited series of /// natural numbers. For example, the values 2.0000000000000000 and 2.0000000000000004
/// natural numbers. For example, the values 2.0000000000000000 and 2.0000000000000004 /// can be stored in a double, but nothing inbetween them.
/// can be stored in a double, but nothing inbetween them. /// </para>
/// </para> /// <para>
/// <para> /// This comparison will count how many possible double precision floating point
/// This comparison will count how many possible double precision floating point /// values are between the left and the right number. If the number of possible
/// values are between the left and the right number. If the number of possible /// values between both numbers is less than or equal to maxUlps, then the numbers
/// values between both numbers is less than or equal to maxUlps, then the numbers /// are considered as being equal.
/// are considered as being equal. /// </para>
/// </para> /// <para>
/// <para> /// Implementation partially follows the code outlined here:
/// Implementation partially follows the code outlined here: /// http://www.anttirt.net/2007/08/19/proper-floating-point-comparisons/
/// http://www.anttirt.net/2007/08/19/proper-floating-point-comparisons/ /// And here:
/// And here: /// http://www.altdevblogaday.com/2012/02/22/comparing-floating-point-numbers-2012-edition/
/// http://www.altdevblogaday.com/2012/02/22/comparing-floating-point-numbers-2012-edition/ /// </para>
/// </para> /// </remarks>
/// </remarks> public static bool AreAlmostEqual(double left, double right, long maxUlps) {
public static bool AreAlmostEqual(double left, double right, long maxUlps) { var leftUnion = new DoubleLongUnion();
var leftUnion = new DoubleLongUnion(); var rightUnion = new DoubleLongUnion();
var rightUnion = new DoubleLongUnion();
leftUnion.Double = left;
leftUnion.Double = left; rightUnion.Double = right;
rightUnion.Double = right;
if(leftUnion.Long < 0) {
if(leftUnion.Long < 0) { leftUnion.Long = unchecked((long)0x8000000000000000 - leftUnion.Long);
leftUnion.Long = unchecked((long)0x8000000000000000 - leftUnion.Long); }
} if(rightUnion.Long < 0) {
if(rightUnion.Long < 0) { rightUnion.Long = unchecked((long)0x8000000000000000 - rightUnion.Long);
rightUnion.Long = unchecked((long)0x8000000000000000 - rightUnion.Long); }
}
return Math.Abs(rightUnion.Long - leftUnion.Long) <= maxUlps;
return Math.Abs(rightUnion.Long - leftUnion.Long) <= maxUlps; }
} #if false
#if false public static bool OldAreAlmostEqual(double left, double right, long maxUlps) {
public static bool OldAreAlmostEqual(double left, double right, long maxUlps) { DoubleInt64Union leftUnion = new DoubleInt64Union();
DoubleInt64Union leftUnion = new DoubleInt64Union(); DoubleInt64Union rightUnion = new DoubleInt64Union();
DoubleInt64Union rightUnion = new DoubleInt64Union();
leftUnion.Double = left;
leftUnion.Double = left; rightUnion.Double = right;
rightUnion.Double = right;
ulong leftSignMask = (leftUnion.ULong >> 63);
ulong leftSignMask = (leftUnion.ULong >> 63); ulong rightSignMask = (rightUnion.ULong >> 63);
ulong rightSignMask = (rightUnion.ULong >> 63);
ulong leftTemp = ((0x8000000000000000 - leftUnion.ULong) & leftSignMask);
ulong leftTemp = ((0x8000000000000000 - leftUnion.ULong) & leftSignMask); leftUnion.ULong = leftTemp | (leftUnion.ULong & ~leftSignMask);
leftUnion.ULong = leftTemp | (leftUnion.ULong & ~leftSignMask);
ulong rightTemp = ((0x8000000000000000 - rightUnion.ULong) & rightSignMask);
ulong rightTemp = ((0x8000000000000000 - rightUnion.ULong) & rightSignMask); rightUnion.ULong = rightTemp | (rightUnion.ULong & ~rightSignMask);
rightUnion.ULong = rightTemp | (rightUnion.ULong & ~rightSignMask);
return (Math.Abs(leftUnion.Long - rightUnion.Long) <= maxUlps);
return (Math.Abs(leftUnion.Long - rightUnion.Long) <= maxUlps); }
} #endif
#endif
/// <summary>
/// <summary> /// Reinterprets the memory contents of a floating point value as an integer value
/// Reinterprets the memory contents of a floating point value as an integer value /// </summary>
/// </summary> /// <param name="value">
/// <param name="value"> /// Floating point value whose memory contents to reinterpret
/// Floating point value whose memory contents to reinterpret /// </param>
/// </param> /// <returns>
/// <returns> /// The memory contents of the floating point value interpreted as an integer
/// The memory contents of the floating point value interpreted as an integer /// </returns>
/// </returns> public static int ReinterpretAsInt(this float value) {
public static int ReinterpretAsInt(this float value) { FloatIntUnion union = new FloatIntUnion();
FloatIntUnion union = new FloatIntUnion(); union.Float = value;
union.Float = value; return union.Int;
return union.Int; }
}
/// <summary>
/// <summary> /// Reinterprets the memory contents of a double precision floating point
/// Reinterprets the memory contents of a double precision floating point /// value as an integer value
/// value as an integer value /// </summary>
/// </summary> /// <param name="value">
/// <param name="value"> /// Double precision floating point value whose memory contents to reinterpret
/// Double precision floating point value whose memory contents to reinterpret /// </param>
/// </param> /// <returns>
/// <returns> /// The memory contents of the double precision floating point value
/// The memory contents of the double precision floating point value /// interpreted as an integer
/// interpreted as an integer /// </returns>
/// </returns> public static long ReinterpretAsLong(this double value) {
public static long ReinterpretAsLong(this double value) { DoubleLongUnion union = new DoubleLongUnion();
DoubleLongUnion union = new DoubleLongUnion(); union.Double = value;
union.Double = value; return union.Long;
return union.Long; }
}
/// <summary>
/// <summary> /// Reinterprets the memory contents of an integer as a floating point value
/// Reinterprets the memory contents of an integer as a floating point value /// </summary>
/// </summary> /// <param name="value">Integer value whose memory contents to reinterpret</param>
/// <param name="value">Integer value whose memory contents to reinterpret</param> /// <returns>
/// <returns> /// The memory contents of the integer value interpreted as a floating point value
/// The memory contents of the integer value interpreted as a floating point value /// </returns>
/// </returns> public static float ReinterpretAsFloat(this int value) {
public static float ReinterpretAsFloat(this int value) { FloatIntUnion union = new FloatIntUnion();
FloatIntUnion union = new FloatIntUnion(); union.Int = value;
union.Int = value; return union.Float;
return union.Float; }
}
/// <summary>
/// <summary> /// Reinterprets the memory contents of an integer value as a double precision
/// Reinterprets the memory contents of an integer value as a double precision /// floating point value
/// floating point value /// </summary>
/// </summary> /// <param name="value">Integer whose memory contents to reinterpret</param>
/// <param name="value">Integer whose memory contents to reinterpret</param> /// <returns>
/// <returns> /// The memory contents of the integer interpreted as a double precision
/// The memory contents of the integer interpreted as a double precision /// floating point value
/// floating point value /// </returns>
/// </returns> public static double ReinterpretAsDouble(this long value) {
public static double ReinterpretAsDouble(this long value) { DoubleLongUnion union = new DoubleLongUnion();
DoubleLongUnion union = new DoubleLongUnion(); union.Long = value;
union.Long = value; return union.Double;
return union.Double; }
}
}
}
} // namespace Nuclex.Support
} // namespace Nuclex.Support

View File

@ -1,35 +1,34 @@
#region CPL License #region Apache License 2.0
/* /*
Nuclex Framework Nuclex .NET Framework
Copyright (C) 2002-2017 Nuclex Development Labs Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
This library is free software; you can redistribute it and/or Licensed under the Apache License, Version 2.0 (the "License");
modify it under the terms of the IBM Common Public License as you may not use this file except in compliance with the License.
published by the IBM Corporation; either version 1.0 of the You may obtain a copy of the License at
License, or (at your option) any later version.
http://www.apache.org/licenses/LICENSE-2.0
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of Unless required by applicable law or agreed to in writing, software
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the distributed under the License is distributed on an "AS IS" BASIS,
IBM Common Public License for more details. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
You should have received a copy of the IBM Common Public limitations under the License.
License along with this library */
*/ #endregion // Apache License 2.0
#endregion
using System;
using System;
namespace Nuclex.Support {
namespace Nuclex.Support {
/// <summary>How to behave in in respect to the garbage collector</summary>
/// <summary>How to behave in in respect to the garbage collector</summary> public enum GarbagePolicy {
public enum GarbagePolicy {
/// <summary>Avoid feeding the garbage collector whenever possible</summary>
/// <summary>Avoid feeding the garbage collector whenever possible</summary> Avoid,
Avoid, /// <summary>Accept garbage production</summary>
/// <summary>Accept garbage production</summary> Accept
Accept
}
}
} // namespace Nuclex.Support
} // namespace Nuclex.Support

File diff suppressed because it is too large Load Diff

View File

@ -1,459 +1,458 @@
#region CPL License #region Apache License 2.0
/* /*
Nuclex Framework Nuclex .NET Framework
Copyright (C) 2002-2017 Nuclex Development Labs Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
This library is free software; you can redistribute it and/or Licensed under the Apache License, Version 2.0 (the "License");
modify it under the terms of the IBM Common Public License as you may not use this file except in compliance with the License.
published by the IBM Corporation; either version 1.0 of the You may obtain a copy of the License at
License, or (at your option) any later version.
http://www.apache.org/licenses/LICENSE-2.0
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of Unless required by applicable law or agreed to in writing, software
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the distributed under the License is distributed on an "AS IS" BASIS,
IBM Common Public License for more details. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
You should have received a copy of the IBM Common Public limitations under the License.
License along with this library */
*/ #endregion // Apache License 2.0
#endregion
using System;
using System; using System.Diagnostics;
using System.Diagnostics; using System.IO;
using System.IO;
namespace Nuclex.Support.IO {
namespace Nuclex.Support.IO {
/// <summary>Chains a series of independent streams into a single stream</summary>
/// <summary>Chains a series of independent streams into a single stream</summary> /// <remarks>
/// <remarks> /// <para>
/// <para> /// This class can be used to chain multiple independent streams into a single
/// This class can be used to chain multiple independent streams into a single /// stream that acts as if its chained streams were only one combined stream.
/// stream that acts as if its chained streams were only one combined stream. /// It is useful to avoid creating huge memory streams or temporary files when
/// It is useful to avoid creating huge memory streams or temporary files when /// you just need to prepend or append some data to a stream or if you need to
/// you just need to prepend or append some data to a stream or if you need to /// read a file that was split into several parts as if it was a single file.
/// read a file that was split into several parts as if it was a single file. /// </para>
/// </para> /// <para>
/// <para> /// It is not recommended to change the size of any chained stream after it
/// It is not recommended to change the size of any chained stream after it /// has become part of a stream chainer, though the stream chainer will do its
/// has become part of a stream chainer, though the stream chainer will do its /// best to cope with the changes as they occur. Increasing the length of a
/// best to cope with the changes as they occur. Increasing the length of a /// chained stream is generally not an issue for streams that support seeking,
/// chained stream is generally not an issue for streams that support seeking, /// but reducing the length might invalidate the stream chainer's file pointer,
/// but reducing the length might invalidate the stream chainer's file pointer, /// resulting in an IOException when Read() or Write() is next called.
/// resulting in an IOException when Read() or Write() is next called. /// </para>
/// </para> /// </remarks>
/// </remarks> public class ChainStream : Stream {
public class ChainStream : Stream {
/// <summary>Initializes a new stream chainer</summary>
/// <summary>Initializes a new stream chainer</summary> /// <param name="streams">Array of streams that will be chained together</param>
/// <param name="streams">Array of streams that will be chained together</param> public ChainStream(params Stream[] streams) {
public ChainStream(params Stream[] streams) { this.streams = (Stream[])streams.Clone();
this.streams = (Stream[])streams.Clone();
determineCapabilities();
determineCapabilities(); }
}
/// <summary>Whether data can be read from the stream</summary>
/// <summary>Whether data can be read from the stream</summary> public override bool CanRead {
public override bool CanRead { get { return this.allStreamsCanRead; }
get { return this.allStreamsCanRead; } }
}
/// <summary>Whether the stream supports seeking</summary>
/// <summary>Whether the stream supports seeking</summary> public override bool CanSeek {
public override bool CanSeek { get { return this.allStreamsCanSeek; }
get { return this.allStreamsCanSeek; } }
}
/// <summary>Whether data can be written into the stream</summary>
/// <summary>Whether data can be written into the stream</summary> public override bool CanWrite {
public override bool CanWrite { get { return this.allStreamsCanWrite; }
get { return this.allStreamsCanWrite; } }
}
/// <summary>
/// <summary> /// Clears all buffers for this stream and causes any buffered data to be written
/// Clears all buffers for this stream and causes any buffered data to be written /// to the underlying device.
/// to the underlying device. /// </summary>
/// </summary> public override void Flush() {
public override void Flush() { for(int index = 0; index < this.streams.Length; ++index) {
for(int index = 0; index < this.streams.Length; ++index) { this.streams[index].Flush();
this.streams[index].Flush(); }
} }
}
/// <summary>Length of the stream in bytes</summary>
/// <summary>Length of the stream in bytes</summary> /// <exception cref="NotSupportedException">
/// <exception cref="NotSupportedException"> /// At least one of the chained streams does not support seeking
/// At least one of the chained streams does not support seeking /// </exception>
/// </exception> public override long Length {
public override long Length { get {
get { if(!this.allStreamsCanSeek) {
if(!this.allStreamsCanSeek) { throw makeSeekNotSupportedException("determine length");
throw makeSeekNotSupportedException("determine length"); }
}
// Sum up the length of all chained streams
// Sum up the length of all chained streams long length = 0;
long length = 0; for(int index = 0; index < this.streams.Length; ++index) {
for(int index = 0; index < this.streams.Length; ++index) { length += this.streams[index].Length;
length += this.streams[index].Length; }
}
return length;
return length; }
} }
}
/// <summary>Absolute position of the file pointer within the stream</summary>
/// <summary>Absolute position of the file pointer within the stream</summary> /// <exception cref="NotSupportedException">
/// <exception cref="NotSupportedException"> /// At least one of the chained streams does not support seeking
/// At least one of the chained streams does not support seeking /// </exception>
/// </exception> public override long Position {
public override long Position { get {
get { if(!this.allStreamsCanSeek) {
if(!this.allStreamsCanSeek) { throw makeSeekNotSupportedException("seek");
throw makeSeekNotSupportedException("seek"); }
}
return this.position;
return this.position; }
} set { moveFilePointer(value); }
set { moveFilePointer(value); } }
}
/// <summary>
/// <summary> /// Reads a sequence of bytes from the stream and advances the position of
/// Reads a sequence of bytes from the stream and advances the position of /// the file pointer by the number of bytes read.
/// the file pointer by the number of bytes read. /// </summary>
/// </summary> /// <param name="buffer">Buffer that will receive the data read from the stream</param>
/// <param name="buffer">Buffer that will receive the data read from the stream</param> /// <param name="offset">
/// <param name="offset"> /// Offset in the buffer at which the stream will place the data read
/// Offset in the buffer at which the stream will place the data read /// </param>
/// </param> /// <param name="count">Maximum number of bytes that will be read</param>
/// <param name="count">Maximum number of bytes that will be read</param> /// <returns>
/// <returns> /// The number of bytes that were actually read from the stream and written into
/// The number of bytes that were actually read from the stream and written into /// the provided buffer
/// the provided buffer /// </returns>
/// </returns> /// <exception cref="NotSupportedException">
/// <exception cref="NotSupportedException"> /// The chained stream at the current position does not support reading
/// The chained stream at the current position does not support reading /// </exception>
/// </exception> public override int Read(byte[] buffer, int offset, int count) {
public override int Read(byte[] buffer, int offset, int count) { if(!this.allStreamsCanRead) {
if(!this.allStreamsCanRead) { throw new NotSupportedException(
throw new NotSupportedException( "Can't read: at least one of the chained streams doesn't support reading"
"Can't read: at least one of the chained streams doesn't support reading" );
); }
}
int totalBytesRead = 0;
int totalBytesRead = 0; int lastStreamIndex = this.streams.Length - 1;
int lastStreamIndex = this.streams.Length - 1;
if(this.allStreamsCanSeek) {
if(this.allStreamsCanSeek) {
// Find out from which stream and at which position we need to begin reading
// Find out from which stream and at which position we need to begin reading int streamIndex;
int streamIndex; long streamOffset;
long streamOffset; findStreamIndexAndOffset(this.position, out streamIndex, out streamOffset);
findStreamIndexAndOffset(this.position, out streamIndex, out streamOffset);
// Try to read from the stream our current file pointer falls into. If more
// Try to read from the stream our current file pointer falls into. If more // data was requested than the stream contains, read each stream to its end
// data was requested than the stream contains, read each stream to its end // until we either have enough data or run out of streams.
// until we either have enough data or run out of streams. while(count > 0) {
while(count > 0) { Stream currentStream = this.streams[streamIndex];
Stream currentStream = this.streams[streamIndex];
// Read up to count bytes from the current stream. Count is decreased each
// Read up to count bytes from the current stream. Count is decreased each // time we successfully get data and holds the number of bytes remaining
// time we successfully get data and holds the number of bytes remaining // to be read
// to be read long maximumBytes = Math.Min(count, currentStream.Length - streamOffset);
long maximumBytes = Math.Min(count, currentStream.Length - streamOffset); currentStream.Position = streamOffset;
currentStream.Position = streamOffset; int bytesRead = currentStream.Read(buffer, offset, (int)maximumBytes);
int bytesRead = currentStream.Read(buffer, offset, (int)maximumBytes);
// Accumulate the total number of bytes we read for the return value
// Accumulate the total number of bytes we read for the return value totalBytesRead += bytesRead;
totalBytesRead += bytesRead;
// If the stream returned partial data, stop here. Also, if this was the
// If the stream returned partial data, stop here. Also, if this was the // last stream we queried, this is as far as we can go.
// last stream we queried, this is as far as we can go. if((bytesRead < maximumBytes) || (streamIndex == lastStreamIndex)) {
if((bytesRead < maximumBytes) || (streamIndex == lastStreamIndex)) { break;
break; }
}
// Move on to the next stream in the chain
// Move on to the next stream in the chain ++streamIndex;
++streamIndex; streamOffset = 0;
streamOffset = 0; count -= bytesRead;
count -= bytesRead; offset += bytesRead;
offset += bytesRead; }
}
this.position += totalBytesRead;
this.position += totalBytesRead;
} else {
} else {
// Try to read from the active read stream. If the end of the active read
// Try to read from the active read stream. If the end of the active read // stream is reached, switch to the next stream in the chain until we have
// stream is reached, switch to the next stream in the chain until we have // no more streams left to read from
// no more streams left to read from while(this.activeReadStreamIndex <= lastStreamIndex) {
while(this.activeReadStreamIndex <= lastStreamIndex) {
// Try to read from the stream. The stream can either return any amount
// Try to read from the stream. The stream can either return any amount // of data > 0 if there's still data left ot be read or 0 if the end of
// of data > 0 if there's still data left ot be read or 0 if the end of // the stream was reached
// the stream was reached Stream activeStream = this.streams[this.activeReadStreamIndex];
Stream activeStream = this.streams[this.activeReadStreamIndex]; if(activeStream.CanSeek) {
if(activeStream.CanSeek) { activeStream.Position = this.activeReadStreamPosition;
activeStream.Position = this.activeReadStreamPosition; }
} totalBytesRead = activeStream.Read(buffer, offset, count);
totalBytesRead = activeStream.Read(buffer, offset, count);
// If we got any data, we're done, exit the loop
// If we got any data, we're done, exit the loop if(totalBytesRead != 0) {
if(totalBytesRead != 0) { break;
break; } else { // Otherwise, go to the next stream in the chain
} else { // Otherwise, go to the next stream in the chain this.activeReadStreamPosition = 0;
this.activeReadStreamPosition = 0; ++this.activeReadStreamIndex;
++this.activeReadStreamIndex; }
} }
}
this.activeReadStreamPosition += totalBytesRead;
this.activeReadStreamPosition += totalBytesRead;
}
}
return totalBytesRead;
return totalBytesRead; }
}
/// <summary>Changes the position of the file pointer</summary>
/// <summary>Changes the position of the file pointer</summary> /// <param name="offset">
/// <param name="offset"> /// Offset to move the file pointer by, relative to the position indicated by
/// Offset to move the file pointer by, relative to the position indicated by /// the <paramref name="origin" /> parameter.
/// the <paramref name="origin" /> parameter. /// </param>
/// </param> /// <param name="origin">
/// <param name="origin"> /// Reference point relative to which the file pointer is placed
/// Reference point relative to which the file pointer is placed /// </param>
/// </param> /// <returns>The new absolute position within the stream</returns>
/// <returns>The new absolute position within the stream</returns> public override long Seek(long offset, SeekOrigin origin) {
public override long Seek(long offset, SeekOrigin origin) { switch(origin) {
switch(origin) { case SeekOrigin.Begin: {
case SeekOrigin.Begin: { return Position = offset;
return Position = offset; }
} case SeekOrigin.Current: {
case SeekOrigin.Current: { return Position += offset;
return Position += offset; }
} case SeekOrigin.End: {
case SeekOrigin.End: { return Position = (Length + offset);
return Position = (Length + offset); }
} default: {
default: { throw new ArgumentException("Invalid seek origin", "origin");
throw new ArgumentException("Invalid seek origin", "origin"); }
} }
} }
}
/// <summary>Changes the length of the stream</summary>
/// <summary>Changes the length of the stream</summary> /// <param name="value">New length the stream shall have</param>
/// <param name="value">New length the stream shall have</param> /// <exception cref="NotSupportedException">
/// <exception cref="NotSupportedException"> /// Always, the stream chainer does not support the SetLength() operation
/// Always, the stream chainer does not support the SetLength() operation /// </exception>
/// </exception> public override void SetLength(long value) {
public override void SetLength(long value) { throw new NotSupportedException("Resizing chained streams is not supported");
throw new NotSupportedException("Resizing chained streams is not supported"); }
}
/// <summary>
/// <summary> /// Writes a sequence of bytes to the stream and advances the position of
/// Writes a sequence of bytes to the stream and advances the position of /// the file pointer by the number of bytes written.
/// the file pointer by the number of bytes written. /// </summary>
/// </summary> /// <param name="buffer">
/// <param name="buffer"> /// Buffer containing the data that will be written to the stream
/// Buffer containing the data that will be written to the stream /// </param>
/// </param> /// <param name="offset">
/// <param name="offset"> /// Offset in the buffer at which the data to be written starts
/// Offset in the buffer at which the data to be written starts /// </param>
/// </param> /// <param name="count">Number of bytes that will be written into the stream</param>
/// <param name="count">Number of bytes that will be written into the stream</param> /// <remarks>
/// <remarks> /// The behavior of this method is as follows: If one or more chained streams
/// The behavior of this method is as follows: If one or more chained streams /// do not support seeking, all data is appended to the final stream in the
/// do not support seeking, all data is appended to the final stream in the /// chain. Otherwise, writing will begin with the stream the current file pointer
/// chain. Otherwise, writing will begin with the stream the current file pointer /// offset falls into. If the end of that stream is reached, writing continues
/// offset falls into. If the end of that stream is reached, writing continues /// in the next stream. On the last stream, writing more data into the stream
/// in the next stream. On the last stream, writing more data into the stream /// that it current size allows will enlarge the stream.
/// that it current size allows will enlarge the stream. /// </remarks>
/// </remarks> public override void Write(byte[] buffer, int offset, int count) {
public override void Write(byte[] buffer, int offset, int count) { if(!this.allStreamsCanWrite) {
if(!this.allStreamsCanWrite) { throw new NotSupportedException(
throw new NotSupportedException( "Can't write: at least one of the chained streams doesn't support writing"
"Can't write: at least one of the chained streams doesn't support writing" );
); }
}
int remaining = count;
int remaining = count;
// If seeking is supported, we can write into the mid of the stream,
// If seeking is supported, we can write into the mid of the stream, // if the user so desires
// if the user so desires if(this.allStreamsCanSeek) {
if(this.allStreamsCanSeek) {
// Find out in which stream and at which position we need to begin writing
// Find out in which stream and at which position we need to begin writing int streamIndex;
int streamIndex; long streamOffset;
long streamOffset; findStreamIndexAndOffset(this.position, out streamIndex, out streamOffset);
findStreamIndexAndOffset(this.position, out streamIndex, out streamOffset);
// Write data into the streams, switching over to the next stream if data is
// Write data into the streams, switching over to the next stream if data is // too large to fit into the current stream, until all data is spent.
// too large to fit into the current stream, until all data is spent. int lastStreamIndex = this.streams.Length - 1;
int lastStreamIndex = this.streams.Length - 1; while(remaining > 0) {
while(remaining > 0) { Stream currentStream = this.streams[streamIndex];
Stream currentStream = this.streams[streamIndex];
// If this is the last stream, just write. If the data is larger than the last
// If this is the last stream, just write. If the data is larger than the last // stream's remaining bytes, it will append to that stream, enlarging it.
// stream's remaining bytes, it will append to that stream, enlarging it. if(streamIndex == lastStreamIndex) {
if(streamIndex == lastStreamIndex) {
// Write all remaining data into the last stream
// Write all remaining data into the last stream currentStream.Position = streamOffset;
currentStream.Position = streamOffset; currentStream.Write(buffer, offset, remaining);
currentStream.Write(buffer, offset, remaining); remaining = 0;
remaining = 0;
} else { // We're writing into a stream that's followed by another stream
} else { // We're writing into a stream that's followed by another stream
// Find out how much data we can put into the current stream without
// Find out how much data we can put into the current stream without // enlarging it (if seeking is supported, so is the Length property)
// enlarging it (if seeking is supported, so is the Length property) long currentStreamRemaining = currentStream.Length - streamOffset;
long currentStreamRemaining = currentStream.Length - streamOffset; int bytesToWrite = (int)Math.Min((long)remaining, currentStreamRemaining);
int bytesToWrite = (int)Math.Min((long)remaining, currentStreamRemaining);
// Write all data that can fit into the current stream
// Write all data that can fit into the current stream currentStream.Position = streamOffset;
currentStream.Position = streamOffset; currentStream.Write(buffer, offset, bytesToWrite);
currentStream.Write(buffer, offset, bytesToWrite);
// Adjust the offsets and count for the next stream
// Adjust the offsets and count for the next stream offset += bytesToWrite;
offset += bytesToWrite; remaining -= bytesToWrite;
remaining -= bytesToWrite; streamOffset = 0;
streamOffset = 0; ++streamIndex;
++streamIndex;
}
} }
}
} else { // Seeking not supported, append everything to the last stream
} else { // Seeking not supported, append everything to the last stream Stream lastStream = this.streams[this.streams.Length - 1];
Stream lastStream = this.streams[this.streams.Length - 1]; if(lastStream.CanSeek) {
if(lastStream.CanSeek) { lastStream.Seek(0, SeekOrigin.End);
lastStream.Seek(0, SeekOrigin.End); }
} lastStream.Write(buffer, offset, remaining);
lastStream.Write(buffer, offset, remaining); }
}
this.position += count;
this.position += count; }
}
/// <summary>Streams being combined by the stream chainer</summary>
/// <summary>Streams being combined by the stream chainer</summary> public Stream[] ChainedStreams {
public Stream[] ChainedStreams { get { return this.streams; }
get { return this.streams; } }
}
/// <summary>Moves the file pointer</summary>
/// <summary>Moves the file pointer</summary> /// <param name="position">New position the file pointer will be moved to</param>
/// <param name="position">New position the file pointer will be moved to</param> private void moveFilePointer(long position) {
private void moveFilePointer(long position) { if(!this.allStreamsCanSeek) {
if(!this.allStreamsCanSeek) { throw makeSeekNotSupportedException("seek");
throw makeSeekNotSupportedException("seek"); }
}
// Seemingly, it is okay to move the file pointer beyond the end of
// Seemingly, it is okay to move the file pointer beyond the end of // the stream until you try to Read() or Write()
// the stream until you try to Read() or Write() this.position = position;
this.position = position; }
}
/// <summary>
/// <summary> /// Finds the stream index and local offset for an absolute position within
/// Finds the stream index and local offset for an absolute position within /// the combined streams.
/// the combined streams. /// </summary>
/// </summary> /// <param name="overallPosition">Absolute position within the combined streams</param>
/// <param name="overallPosition">Absolute position within the combined streams</param> /// <param name="streamIndex">
/// <param name="streamIndex"> /// Index of the stream the overall position falls into
/// Index of the stream the overall position falls into /// </param>
/// </param> /// <param name="streamPosition">
/// <param name="streamPosition"> /// Local position within the stream indicated by <paramref name="streamIndex" />
/// Local position within the stream indicated by <paramref name="streamIndex" /> /// </param>
/// </param> private void findStreamIndexAndOffset(
private void findStreamIndexAndOffset( long overallPosition, out int streamIndex, out long streamPosition
long overallPosition, out int streamIndex, out long streamPosition ) {
) { Debug.Assert(
Debug.Assert( this.allStreamsCanSeek, "Call to findStreamIndexAndOffset() but no seek support"
this.allStreamsCanSeek, "Call to findStreamIndexAndOffset() but no seek support" );
);
// In case the position is beyond the stream's end, this is what we will
// In case the position is beyond the stream's end, this is what we will // return to the caller
// return to the caller streamIndex = (this.streams.Length - 1);
streamIndex = (this.streams.Length - 1);
// Search until we have found the stream the position must lie in
// Search until we have found the stream the position must lie in for(int index = 0; index < this.streams.Length; ++index) {
for(int index = 0; index < this.streams.Length; ++index) { long streamLength = this.streams[index].Length;
long streamLength = this.streams[index].Length;
if(overallPosition < streamLength) {
if(overallPosition < streamLength) { streamIndex = index;
streamIndex = index; break;
break; }
}
overallPosition -= streamLength;
overallPosition -= streamLength; }
}
// The overall position will have been decreased by each skipped stream's length,
// The overall position will have been decreased by each skipped stream's length, // so it should now contain the local position for the final stream we checked.
// so it should now contain the local position for the final stream we checked. streamPosition = overallPosition;
streamPosition = overallPosition; }
}
/// <summary>Determines the capabilities of the chained streams</summary>
/// <summary>Determines the capabilities of the chained streams</summary> /// <remarks>
/// <remarks> /// <para>
/// <para> /// Theoretically, it would be possible to create a stream chainer that supported
/// Theoretically, it would be possible to create a stream chainer that supported /// writing only when the file pointer was on a chained stream with write support,
/// writing only when the file pointer was on a chained stream with write support, /// that could seek within the beginning of the stream until the first chained
/// that could seek within the beginning of the stream until the first chained /// stream with no seek capability was encountered and so on.
/// stream with no seek capability was encountered and so on. /// </para>
/// </para> /// <para>
/// <para> /// However, the interface of the Stream class requires us to make a definitive
/// However, the interface of the Stream class requires us to make a definitive /// statement as to whether the Stream supports seeking, reading and writing.
/// statement as to whether the Stream supports seeking, reading and writing. /// We can't return "maybe" or "mostly" in CanSeek, so the only sane choice that
/// We can't return "maybe" or "mostly" in CanSeek, so the only sane choice that /// doesn't violate the Stream interface is to implement these capabilities as
/// doesn't violate the Stream interface is to implement these capabilities as /// all or nothing - either all streams support a feature, or the stream chainer
/// all or nothing - either all streams support a feature, or the stream chainer /// will report the feature as unsupported.
/// will report the feature as unsupported. /// </para>
/// </para> /// </remarks>
/// </remarks> private void determineCapabilities() {
private void determineCapabilities() { this.allStreamsCanSeek = true;
this.allStreamsCanSeek = true; this.allStreamsCanRead = true;
this.allStreamsCanRead = true; this.allStreamsCanWrite = true;
this.allStreamsCanWrite = true;
for(int index = 0; index < this.streams.Length; ++index) {
for(int index = 0; index < this.streams.Length; ++index) { this.allStreamsCanSeek &= this.streams[index].CanSeek;
this.allStreamsCanSeek &= this.streams[index].CanSeek; this.allStreamsCanRead &= this.streams[index].CanRead;
this.allStreamsCanRead &= this.streams[index].CanRead; this.allStreamsCanWrite &= this.streams[index].CanWrite;
this.allStreamsCanWrite &= this.streams[index].CanWrite; }
} }
}
/// <summary>
/// <summary> /// Constructs a NotSupportException for an error caused by one of the chained
/// Constructs a NotSupportException for an error caused by one of the chained /// streams having no seek support
/// streams having no seek support /// </summary>
/// </summary> /// <param name="action">Action that was tried to perform</param>
/// <param name="action">Action that was tried to perform</param> /// <returns>The newly constructed NotSupportedException</returns>
/// <returns>The newly constructed NotSupportedException</returns> private static NotSupportedException makeSeekNotSupportedException(string action) {
private static NotSupportedException makeSeekNotSupportedException(string action) { return new NotSupportedException(
return new NotSupportedException( string.Format(
string.Format( "Can't {0}: at least one of the chained streams does not support seeking",
"Can't {0}: at least one of the chained streams does not support seeking", action
action )
) );
); }
}
/// <summary>Streams that have been chained together</summary>
/// <summary>Streams that have been chained together</summary> private Stream[] streams;
private Stream[] streams; /// <summary>Current position of the overall file pointer</summary>
/// <summary>Current position of the overall file pointer</summary> private long position;
private long position; /// <summary>Stream we're currently reading from if seeking is not supported</summary>
/// <summary>Stream we're currently reading from if seeking is not supported</summary> /// <remarks>
/// <remarks> /// If seeking is not supported, the stream chainer will read from each stream
/// If seeking is not supported, the stream chainer will read from each stream /// until the end was reached
/// until the end was reached /// sequentially
/// sequentially /// </remarks>
/// </remarks> private int activeReadStreamIndex;
private int activeReadStreamIndex; /// <summary>Position in the current read stream if seeking is not supported</summary>
/// <summary>Position in the current read stream if seeking is not supported</summary> /// <remarks>
/// <remarks> /// If there is a mix of streams supporting seeking and not supporting seeking, we
/// If there is a mix of streams supporting seeking and not supporting seeking, we /// need to keep track of the read index for those streams that do. If, for example,
/// need to keep track of the read index for those streams that do. If, for example, /// the last stream is written to and read from in succession, the file pointer
/// the last stream is written to and read from in succession, the file pointer /// of that stream would have been moved to the end by the write attempt, skipping
/// of that stream would have been moved to the end by the write attempt, skipping /// data that should have been read in the following read attempt.
/// data that should have been read in the following read attempt. /// </remarks>
/// </remarks> private long activeReadStreamPosition;
private long activeReadStreamPosition;
/// <summary>Whether all of the chained streams support seeking</summary>
/// <summary>Whether all of the chained streams support seeking</summary> private bool allStreamsCanSeek;
private bool allStreamsCanSeek; /// <summary>Whether all of the chained streams support reading</summary>
/// <summary>Whether all of the chained streams support reading</summary> private bool allStreamsCanRead;
private bool allStreamsCanRead; /// <summary>Whether all of the chained streams support writing</summary>
/// <summary>Whether all of the chained streams support writing</summary> private bool allStreamsCanWrite;
private bool allStreamsCanWrite;
}
}
} // namespace Nuclex.Support.IO
} // namespace Nuclex.Support.IO

File diff suppressed because it is too large Load Diff

View File

@ -1,261 +1,260 @@
#region CPL License #region Apache License 2.0
/* /*
Nuclex Framework Nuclex .NET Framework
Copyright (C) 2002-2017 Nuclex Development Labs Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
This library is free software; you can redistribute it and/or Licensed under the Apache License, Version 2.0 (the "License");
modify it under the terms of the IBM Common Public License as you may not use this file except in compliance with the License.
published by the IBM Corporation; either version 1.0 of the You may obtain a copy of the License at
License, or (at your option) any later version.
http://www.apache.org/licenses/LICENSE-2.0
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of Unless required by applicable law or agreed to in writing, software
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the distributed under the License is distributed on an "AS IS" BASIS,
IBM Common Public License for more details. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
You should have received a copy of the IBM Common Public limitations under the License.
License along with this library */
*/ #endregion // Apache License 2.0
#endregion
using System;
using System; using System.IO;
using System.IO;
namespace Nuclex.Support.IO {
namespace Nuclex.Support.IO {
/// <summary>Wraps a stream and exposes only a limited region of its data</summary>
/// <summary>Wraps a stream and exposes only a limited region of its data</summary> public class PartialStream : Stream {
public class PartialStream : Stream {
/// <summary>Initializes a new partial stream</summary>
/// <summary>Initializes a new partial stream</summary> /// <param name="stream">
/// <param name="stream"> /// Stream the wrapper will make a limited region accessible of
/// Stream the wrapper will make a limited region accessible of /// </param>
/// </param> /// <param name="start">
/// <param name="start"> /// Start index in the stream which becomes the beginning for the wrapper
/// Start index in the stream which becomes the beginning for the wrapper /// </param>
/// </param> /// <param name="length">
/// <param name="length"> /// Length the wrapped stream should report and allow access to
/// Length the wrapped stream should report and allow access to /// </param>
/// </param> public PartialStream(Stream stream, long start, long length) {
public PartialStream(Stream stream, long start, long length) { if(start < 0) {
if(start < 0) { throw new ArgumentException("Start index must not be less than 0", "start");
throw new ArgumentException("Start index must not be less than 0", "start"); }
}
if(stream.CanSeek) {
if(stream.CanSeek) { if(start + length > stream.Length) {
if(start + length > stream.Length) { throw new ArgumentException(
throw new ArgumentException( "Partial stream exceeds end of full stream", "length"
"Partial stream exceeds end of full stream", "length" );
); }
} } else {
} else { if(start != 0) {
if(start != 0) { throw new ArgumentException(
throw new ArgumentException( "The only valid start for unseekable streams is 0", "start"
"The only valid start for unseekable streams is 0", "start" );
); }
} }
}
this.stream = stream;
this.stream = stream; this.start = start;
this.start = start; this.length = length;
this.length = length; }
}
/// <summary>Whether data can be read from the stream</summary>
/// <summary>Whether data can be read from the stream</summary> public override bool CanRead {
public override bool CanRead { get { return this.stream.CanRead; }
get { return this.stream.CanRead; } }
}
/// <summary>Whether the stream supports seeking</summary>
/// <summary>Whether the stream supports seeking</summary> public override bool CanSeek {
public override bool CanSeek { get { return this.stream.CanSeek; }
get { return this.stream.CanSeek; } }
}
/// <summary>Whether data can be written into the stream</summary>
/// <summary>Whether data can be written into the stream</summary> public override bool CanWrite {
public override bool CanWrite { get { return this.stream.CanWrite; }
get { return this.stream.CanWrite; } }
}
/// <summary>
/// <summary> /// Clears all buffers for this stream and causes any buffered data to be written
/// Clears all buffers for this stream and causes any buffered data to be written /// to the underlying device.
/// to the underlying device. /// </summary>
/// </summary> public override void Flush() {
public override void Flush() { this.stream.Flush();
this.stream.Flush(); }
}
/// <summary>Length of the stream in bytes</summary>
/// <summary>Length of the stream in bytes</summary> /// <exception cref="NotSupportedException">
/// <exception cref="NotSupportedException"> /// The wrapped stream does not support seeking
/// The wrapped stream does not support seeking /// </exception>
/// </exception> public override long Length {
public override long Length { get { return this.length; }
get { return this.length; } }
}
/// <summary>Absolute position of the file pointer within the stream</summary>
/// <summary>Absolute position of the file pointer within the stream</summary> /// <exception cref="NotSupportedException">
/// <exception cref="NotSupportedException"> /// The wrapped stream does not support seeking
/// The wrapped stream does not support seeking /// </exception>
/// </exception> public override long Position {
public override long Position { get {
get { if(!this.stream.CanSeek) {
if(!this.stream.CanSeek) { throw makeSeekNotSupportedException("seek");
throw makeSeekNotSupportedException("seek"); }
}
return this.position;
return this.position; }
} set { moveFilePointer(value); }
set { moveFilePointer(value); } }
}
/// <summary>
/// <summary> /// Reads a sequence of bytes from the stream and advances the position of
/// Reads a sequence of bytes from the stream and advances the position of /// the file pointer by the number of bytes read.
/// the file pointer by the number of bytes read. /// </summary>
/// </summary> /// <param name="buffer">Buffer that will receive the data read from the stream</param>
/// <param name="buffer">Buffer that will receive the data read from the stream</param> /// <param name="offset">
/// <param name="offset"> /// Offset in the buffer at which the stream will place the data read
/// Offset in the buffer at which the stream will place the data read /// </param>
/// </param> /// <param name="count">Maximum number of bytes that will be read</param>
/// <param name="count">Maximum number of bytes that will be read</param> /// <returns>
/// <returns> /// The number of bytes that were actually read from the stream and written into
/// The number of bytes that were actually read from the stream and written into /// the provided buffer
/// the provided buffer /// </returns>
/// </returns> /// <exception cref="NotSupportedException">
/// <exception cref="NotSupportedException"> /// The wrapped stream does not support reading
/// The wrapped stream does not support reading /// </exception>
/// </exception> public override int Read(byte[] buffer, int offset, int count) {
public override int Read(byte[] buffer, int offset, int count) { if(!this.stream.CanRead) {
if(!this.stream.CanRead) { throw new NotSupportedException(
throw new NotSupportedException( "Can't read: the wrapped stream doesn't support reading"
"Can't read: the wrapped stream doesn't support reading" );
); }
}
long remaining = this.length - this.position;
long remaining = this.length - this.position; int bytesToRead = (int)Math.Min(count, remaining);
int bytesToRead = (int)Math.Min(count, remaining);
if(this.stream.CanSeek) {
if(this.stream.CanSeek) { this.stream.Position = this.position + this.start;
this.stream.Position = this.position + this.start; }
} int bytesRead = this.stream.Read(buffer, offset, bytesToRead);
int bytesRead = this.stream.Read(buffer, offset, bytesToRead); this.position += bytesRead;
this.position += bytesRead;
return bytesRead;
return bytesRead; }
}
/// <summary>Changes the position of the file pointer</summary>
/// <summary>Changes the position of the file pointer</summary> /// <param name="offset">
/// <param name="offset"> /// Offset to move the file pointer by, relative to the position indicated by
/// Offset to move the file pointer by, relative to the position indicated by /// the <paramref name="origin" /> parameter.
/// the <paramref name="origin" /> parameter. /// </param>
/// </param> /// <param name="origin">
/// <param name="origin"> /// Reference point relative to which the file pointer is placed
/// Reference point relative to which the file pointer is placed /// </param>
/// </param> /// <returns>The new absolute position within the stream</returns>
/// <returns>The new absolute position within the stream</returns> public override long Seek(long offset, SeekOrigin origin) {
public override long Seek(long offset, SeekOrigin origin) { switch(origin) {
switch(origin) { case SeekOrigin.Begin: {
case SeekOrigin.Begin: { return Position = offset;
return Position = offset; }
} case SeekOrigin.Current: {
case SeekOrigin.Current: { return Position += offset;
return Position += offset; }
} case SeekOrigin.End: {
case SeekOrigin.End: { return Position = (Length + offset);
return Position = (Length + offset); }
} default: {
default: { throw new ArgumentException("Invalid seek origin", "origin");
throw new ArgumentException("Invalid seek origin", "origin"); }
} }
} }
}
/// <summary>Changes the length of the stream</summary>
/// <summary>Changes the length of the stream</summary> /// <param name="value">New length the stream shall have</param>
/// <param name="value">New length the stream shall have</param> /// <exception cref="NotSupportedException">
/// <exception cref="NotSupportedException"> /// Always, the stream chainer does not support the SetLength() operation
/// Always, the stream chainer does not support the SetLength() operation /// </exception>
/// </exception> public override void SetLength(long value) {
public override void SetLength(long value) { throw new NotSupportedException("Resizing partial streams is not supported");
throw new NotSupportedException("Resizing partial streams is not supported"); }
}
/// <summary>
/// <summary> /// Writes a sequence of bytes to the stream and advances the position of
/// Writes a sequence of bytes to the stream and advances the position of /// the file pointer by the number of bytes written.
/// the file pointer by the number of bytes written. /// </summary>
/// </summary> /// <param name="buffer">
/// <param name="buffer"> /// Buffer containing the data that will be written to the stream
/// Buffer containing the data that will be written to the stream /// </param>
/// </param> /// <param name="offset">
/// <param name="offset"> /// Offset in the buffer at which the data to be written starts
/// Offset in the buffer at which the data to be written starts /// </param>
/// </param> /// <param name="count">Number of bytes that will be written into the stream</param>
/// <param name="count">Number of bytes that will be written into the stream</param> /// <remarks>
/// <remarks> /// The behavior of this method is as follows: If one or more chained streams
/// The behavior of this method is as follows: If one or more chained streams /// do not support seeking, all data is appended to the final stream in the
/// do not support seeking, all data is appended to the final stream in the /// chain. Otherwise, writing will begin with the stream the current file pointer
/// chain. Otherwise, writing will begin with the stream the current file pointer /// offset falls into. If the end of that stream is reached, writing continues
/// offset falls into. If the end of that stream is reached, writing continues /// in the next stream. On the last stream, writing more data into the stream
/// in the next stream. On the last stream, writing more data into the stream /// that it current size allows will enlarge the stream.
/// that it current size allows will enlarge the stream. /// </remarks>
/// </remarks> public override void Write(byte[] buffer, int offset, int count) {
public override void Write(byte[] buffer, int offset, int count) { long remaining = this.length - this.position;
long remaining = this.length - this.position; if(count > remaining) {
if(count > remaining) { throw new NotSupportedException(
throw new NotSupportedException( "Cannot extend the length of the partial stream"
"Cannot extend the length of the partial stream" );
); }
}
if(this.stream.CanSeek) {
if(this.stream.CanSeek) { this.stream.Position = this.position + this.start;
this.stream.Position = this.position + this.start; }
} this.stream.Write(buffer, offset, count);
this.stream.Write(buffer, offset, count);
this.position += count;
this.position += count; }
}
/// <summary>Stream being wrapped by the partial stream wrapper</summary>
/// <summary>Stream being wrapped by the partial stream wrapper</summary> public Stream CompleteStream {
public Stream CompleteStream { get { return this.stream; }
get { return this.stream; } }
}
/// <summary>Moves the file pointer</summary>
/// <summary>Moves the file pointer</summary> /// <param name="position">New position the file pointer will be moved to</param>
/// <param name="position">New position the file pointer will be moved to</param> private void moveFilePointer(long position) {
private void moveFilePointer(long position) { if(!this.stream.CanSeek) {
if(!this.stream.CanSeek) { throw makeSeekNotSupportedException("seek");
throw makeSeekNotSupportedException("seek"); }
}
// Seemingly, it is okay to move the file pointer beyond the end of
// Seemingly, it is okay to move the file pointer beyond the end of // the stream until you try to Read() or Write()
// the stream until you try to Read() or Write() this.position = position;
this.position = position; }
}
/// <summary>
/// <summary> /// Constructs a NotSupportException for an error caused by the wrapped
/// Constructs a NotSupportException for an error caused by the wrapped /// stream having no seek support
/// stream having no seek support /// </summary>
/// </summary> /// <param name="action">Action that was tried to perform</param>
/// <param name="action">Action that was tried to perform</param> /// <returns>The newly constructed NotSupportedException</returns>
/// <returns>The newly constructed NotSupportedException</returns> private static NotSupportedException makeSeekNotSupportedException(string action) {
private static NotSupportedException makeSeekNotSupportedException(string action) { return new NotSupportedException(
return new NotSupportedException( string.Format(
string.Format( "Can't {0}: the wrapped stream does not support seeking",
"Can't {0}: the wrapped stream does not support seeking", action
action )
) );
); }
}
/// <summary>Streams that have been chained together</summary>
/// <summary>Streams that have been chained together</summary> private Stream stream;
private Stream stream; /// <summary>Start index of the partial stream in the wrapped stream</summary>
/// <summary>Start index of the partial stream in the wrapped stream</summary> private long start;
private long start; /// <summary>Zero-based position of the partial stream's file pointer</summary>
/// <summary>Zero-based position of the partial stream's file pointer</summary> /// <remarks>
/// <remarks> /// If the stream does not support seeking, the position will simply be counted
/// If the stream does not support seeking, the position will simply be counted /// up until it reaches <see cref="PartialStream.length" />.
/// up until it reaches <see cref="PartialStream.length" />. /// </remarks>
/// </remarks> private long position;
private long position; /// <summary>Length of the partial stream</summary>
/// <summary>Length of the partial stream</summary> private long length;
private long length;
}
}
} // namespace Nuclex.Support.IO
} // namespace Nuclex.Support.IO

View File

@ -1,330 +1,329 @@
#region CPL License #region Apache License 2.0
/* /*
Nuclex Framework Nuclex .NET Framework
Copyright (C) 2002-2017 Nuclex Development Labs Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
This library is free software; you can redistribute it and/or Licensed under the Apache License, Version 2.0 (the "License");
modify it under the terms of the IBM Common Public License as you may not use this file except in compliance with the License.
published by the IBM Corporation; either version 1.0 of the You may obtain a copy of the License at
License, or (at your option) any later version.
http://www.apache.org/licenses/LICENSE-2.0
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of Unless required by applicable law or agreed to in writing, software
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the distributed under the License is distributed on an "AS IS" BASIS,
IBM Common Public License for more details. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
You should have received a copy of the IBM Common Public limitations under the License.
License along with this library */
*/ #endregion // Apache License 2.0
#endregion
using System;
using System; using System.IO;
using System.IO;
#if UNITTEST
#if UNITTEST
using NUnit.Framework;
using NUnit.Framework;
namespace Nuclex.Support.IO {
namespace Nuclex.Support.IO {
/// <summary>Unit Test for the ring buffer class</summary>
/// <summary>Unit Test for the ring buffer class</summary> [TestFixture]
[TestFixture] internal class RingMemoryStreamTest {
internal class RingMemoryStreamTest {
/// <summary>Prepares some test data for the units test methods</summary>
/// <summary>Prepares some test data for the units test methods</summary> [TestFixtureSetUp]
[TestFixtureSetUp] public void Setup() {
public void Setup() { this.testBytes = new byte[20];
this.testBytes = new byte[20]; for(int i = 0; i < 20; ++i)
for(int i = 0; i < 20; ++i) this.testBytes[i] = (byte)i;
this.testBytes[i] = (byte)i; }
}
/// <summary>
/// <summary> /// Ensures that the ring buffer blocks write attempts that would exceed its capacity
/// Ensures that the ring buffer blocks write attempts that would exceed its capacity /// </summary>
/// </summary> [Test]
[Test] public void TestWriteTooLargeChunk() {
public void TestWriteTooLargeChunk() { Assert.Throws<OverflowException>(
Assert.Throws<OverflowException>( delegate() { new RingMemoryStream(10).Write(this.testBytes, 0, 11); }
delegate() { new RingMemoryStream(10).Write(this.testBytes, 0, 11); } );
); }
}
/// <summary>
/// <summary> /// Ensures that the ring buffer still accepts write attempts that would fill the
/// Ensures that the ring buffer still accepts write attempts that would fill the /// entire buffer in one go.
/// entire buffer in one go. /// </summary>
/// </summary> [Test]
[Test] public void TestWriteBarelyFittingChunk() {
public void TestWriteBarelyFittingChunk() { new RingMemoryStream(10).Write(this.testBytes, 0, 10);
new RingMemoryStream(10).Write(this.testBytes, 0, 10); }
}
/// <summary>
/// <summary> /// Ensures that the ring buffer correctly manages write attempts that have to
/// Ensures that the ring buffer correctly manages write attempts that have to /// be split at the end of the ring buffer
/// be split at the end of the ring buffer /// </summary>
/// </summary> [Test]
[Test] public void TestWriteSplitBlock() {
public void TestWriteSplitBlock() { RingMemoryStream testRing = new RingMemoryStream(10);
RingMemoryStream testRing = new RingMemoryStream(10); testRing.Write(this.testBytes, 0, 8);
testRing.Write(this.testBytes, 0, 8); testRing.Read(this.testBytes, 0, 5);
testRing.Read(this.testBytes, 0, 5); testRing.Write(this.testBytes, 0, 7);
testRing.Write(this.testBytes, 0, 7);
byte[] actual = new byte[10];
byte[] actual = new byte[10]; testRing.Read(actual, 0, 10);
testRing.Read(actual, 0, 10); Assert.AreEqual(new byte[] { 5, 6, 7, 0, 1, 2, 3, 4, 5, 6 }, actual);
Assert.AreEqual(new byte[] { 5, 6, 7, 0, 1, 2, 3, 4, 5, 6 }, actual); }
}
/// <summary>
/// <summary> /// Ensures that the ring buffer correctly manages write attempts that write into
/// Ensures that the ring buffer correctly manages write attempts that write into /// the gap after the ring buffer's data has become split
/// the gap after the ring buffer's data has become split /// </summary>
/// </summary> [Test]
[Test] public void TestWriteSplitAndLinearBlock() {
public void TestWriteSplitAndLinearBlock() { RingMemoryStream testRing = new RingMemoryStream(10);
RingMemoryStream testRing = new RingMemoryStream(10); testRing.Write(this.testBytes, 0, 8);
testRing.Write(this.testBytes, 0, 8); testRing.Read(this.testBytes, 0, 5);
testRing.Read(this.testBytes, 0, 5); testRing.Write(this.testBytes, 0, 5);
testRing.Write(this.testBytes, 0, 5); testRing.Write(this.testBytes, 0, 2);
testRing.Write(this.testBytes, 0, 2);
byte[] actual = new byte[10];
byte[] actual = new byte[10]; testRing.Read(actual, 0, 10);
testRing.Read(actual, 0, 10); Assert.AreEqual(new byte[] { 5, 6, 7, 0, 1, 2, 3, 4, 0, 1 }, actual);
Assert.AreEqual(new byte[] { 5, 6, 7, 0, 1, 2, 3, 4, 0, 1 }, actual); }
}
/// <summary>
/// <summary> /// Ensures that the ring buffer still detects write that would exceed its capacity
/// Ensures that the ring buffer still detects write that would exceed its capacity /// if they write into the gap after the ring buffer's data has become split
/// if they write into the gap after the ring buffer's data has become split /// </summary>
/// </summary> [Test]
[Test] public void TestWriteSplitAndLinearTooLargeBlock() {
public void TestWriteSplitAndLinearTooLargeBlock() { RingMemoryStream testRing = new RingMemoryStream(10);
RingMemoryStream testRing = new RingMemoryStream(10); testRing.Write(this.testBytes, 0, 8);
testRing.Write(this.testBytes, 0, 8); testRing.Read(this.testBytes, 0, 5);
testRing.Read(this.testBytes, 0, 5); testRing.Write(this.testBytes, 0, 5);
testRing.Write(this.testBytes, 0, 5); Assert.Throws<OverflowException>(
Assert.Throws<OverflowException>( delegate() { testRing.Write(this.testBytes, 0, 3); }
delegate() { testRing.Write(this.testBytes, 0, 3); } );
); }
}
/// <summary>Tests whether the ring buffer correctly handles fragmentation</summary>
/// <summary>Tests whether the ring buffer correctly handles fragmentation</summary> [Test]
[Test] public void TestSplitBlockWrappedRead() {
public void TestSplitBlockWrappedRead() { RingMemoryStream testRing = new RingMemoryStream(10);
RingMemoryStream testRing = new RingMemoryStream(10); testRing.Write(this.testBytes, 0, 10);
testRing.Write(this.testBytes, 0, 10); testRing.Read(this.testBytes, 0, 5);
testRing.Read(this.testBytes, 0, 5); testRing.Write(this.testBytes, 0, 5);
testRing.Write(this.testBytes, 0, 5);
byte[] actual = new byte[10];
byte[] actual = new byte[10]; testRing.Read(actual, 0, 10);
testRing.Read(actual, 0, 10); Assert.AreEqual(new byte[] { 5, 6, 7, 8, 9, 0, 1, 2, 3, 4 }, actual);
Assert.AreEqual(new byte[] { 5, 6, 7, 8, 9, 0, 1, 2, 3, 4 }, actual); }
}
/// <summary>Tests whether the ring buffer correctly handles fragmentation</summary>
/// <summary>Tests whether the ring buffer correctly handles fragmentation</summary> [Test]
[Test] public void TestSplitBlockLinearRead() {
public void TestSplitBlockLinearRead() { RingMemoryStream testRing = new RingMemoryStream(10);
RingMemoryStream testRing = new RingMemoryStream(10); testRing.Write(this.testBytes, 0, 10);
testRing.Write(this.testBytes, 0, 10); testRing.Read(this.testBytes, 0, 5);
testRing.Read(this.testBytes, 0, 5); testRing.Write(this.testBytes, 0, 5);
testRing.Write(this.testBytes, 0, 5);
byte[] actual = new byte[5];
byte[] actual = new byte[5]; testRing.Read(actual, 0, 5);
testRing.Read(actual, 0, 5); Assert.AreEqual(new byte[] { 5, 6, 7, 8, 9 }, actual);
Assert.AreEqual(new byte[] { 5, 6, 7, 8, 9 }, actual); }
}
/// <summary>
/// <summary> /// Tests whether the ring buffer correctly returns partial data if more
/// Tests whether the ring buffer correctly returns partial data if more /// data is requested than is contained in it.
/// data is requested than is contained in it. /// </summary>
/// </summary> [Test]
[Test] public void TestEndOfStream() {
public void TestEndOfStream() { byte[] tempBytes = new byte[10];
byte[] tempBytes = new byte[10];
RingMemoryStream testRing = new RingMemoryStream(10);
RingMemoryStream testRing = new RingMemoryStream(10); Assert.AreEqual(0, testRing.Read(tempBytes, 0, 5));
Assert.AreEqual(0, testRing.Read(tempBytes, 0, 5));
testRing.Write(this.testBytes, 0, 5);
testRing.Write(this.testBytes, 0, 5); Assert.AreEqual(5, testRing.Read(tempBytes, 0, 10));
Assert.AreEqual(5, testRing.Read(tempBytes, 0, 10));
testRing.Write(this.testBytes, 0, 6);
testRing.Write(this.testBytes, 0, 6); testRing.Read(tempBytes, 0, 5);
testRing.Read(tempBytes, 0, 5); testRing.Write(this.testBytes, 0, 9);
testRing.Write(this.testBytes, 0, 9); Assert.AreEqual(10, testRing.Read(tempBytes, 0, 20));
Assert.AreEqual(10, testRing.Read(tempBytes, 0, 20)); }
}
/// <summary>
/// <summary> /// Validates that the ring buffer can extend its capacity without loosing data
/// Validates that the ring buffer can extend its capacity without loosing data /// </summary>
/// </summary> [Test]
[Test] public void TestCapacityIncrease() {
public void TestCapacityIncrease() { RingMemoryStream testRing = new RingMemoryStream(10);
RingMemoryStream testRing = new RingMemoryStream(10); testRing.Write(this.testBytes, 0, 10);
testRing.Write(this.testBytes, 0, 10);
testRing.Capacity = 20;
testRing.Capacity = 20; byte[] actual = new byte[10];
byte[] actual = new byte[10]; testRing.Read(actual, 0, 10);
testRing.Read(actual, 0, 10);
Assert.AreEqual(new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }, actual);
Assert.AreEqual(new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }, actual); }
}
/// <summary>
/// <summary> /// Validates that the ring buffer can reduce its capacity without loosing data
/// Validates that the ring buffer can reduce its capacity without loosing data /// </summary>
/// </summary> [Test]
[Test] public void TestCapacityDecrease() {
public void TestCapacityDecrease() { RingMemoryStream testRing = new RingMemoryStream(20);
RingMemoryStream testRing = new RingMemoryStream(20); testRing.Write(this.testBytes, 0, 10);
testRing.Write(this.testBytes, 0, 10);
testRing.Capacity = 10;
testRing.Capacity = 10; byte[] actual = new byte[10];
byte[] actual = new byte[10]; testRing.Read(actual, 0, 10);
testRing.Read(actual, 0, 10);
Assert.AreEqual(new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }, actual);
Assert.AreEqual(new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }, actual); }
}
/// <summary>
/// <summary> /// Checks that an exception is thrown when the ring buffer's capacity is
/// Checks that an exception is thrown when the ring buffer's capacity is /// reduced so much it would have to give up some of its contained data
/// reduced so much it would have to give up some of its contained data /// </summary>
/// </summary> [Test]
[Test] public void TestCapacityDecreaseException() {
public void TestCapacityDecreaseException() { RingMemoryStream testRing = new RingMemoryStream(20);
RingMemoryStream testRing = new RingMemoryStream(20); testRing.Write(this.testBytes, 0, 20);
testRing.Write(this.testBytes, 0, 20);
Assert.Throws<ArgumentOutOfRangeException>(
Assert.Throws<ArgumentOutOfRangeException>( delegate() { testRing.Capacity = 10; }
delegate() { testRing.Capacity = 10; } );
); }
}
/// <summary>Tests whether the Capacity property returns the current capacity</summary>
/// <summary>Tests whether the Capacity property returns the current capacity</summary> [Test]
[Test] public void TestCapacity() {
public void TestCapacity() { RingMemoryStream testRing = new RingMemoryStream(123);
RingMemoryStream testRing = new RingMemoryStream(123);
Assert.AreEqual(123, testRing.Capacity);
Assert.AreEqual(123, testRing.Capacity); }
}
/// <summary>Ensures that the CanRead property returns true</summary>
/// <summary>Ensures that the CanRead property returns true</summary> [Test]
[Test] public void TestCanRead() {
public void TestCanRead() { Assert.IsTrue(new RingMemoryStream(10).CanRead);
Assert.IsTrue(new RingMemoryStream(10).CanRead); }
}
/// <summary>Ensures that the CanSeek property returns false</summary>
/// <summary>Ensures that the CanSeek property returns false</summary> [Test]
[Test] public void TestCanSeek() {
public void TestCanSeek() { Assert.IsFalse(new RingMemoryStream(10).CanSeek);
Assert.IsFalse(new RingMemoryStream(10).CanSeek); }
}
/// <summary>Ensures that the CanWrite property returns true</summary>
/// <summary>Ensures that the CanWrite property returns true</summary> [Test]
[Test] public void TestCanWrite() {
public void TestCanWrite() { Assert.IsTrue(new RingMemoryStream(10).CanWrite);
Assert.IsTrue(new RingMemoryStream(10).CanWrite); }
}
/// <summary>
/// <summary> /// Tests whether the auto reset feature works (resets the buffer pointer to the
/// Tests whether the auto reset feature works (resets the buffer pointer to the /// left end of the buffer when it gets empty; mainly a performance feature).
/// left end of the buffer when it gets empty; mainly a performance feature). /// </summary>
/// </summary> [Test]
[Test] public void TestAutoReset() {
public void TestAutoReset() { byte[] tempBytes = new byte[10];
byte[] tempBytes = new byte[10]; RingMemoryStream testRing = new RingMemoryStream(10);
RingMemoryStream testRing = new RingMemoryStream(10);
testRing.Write(this.testBytes, 0, 8);
testRing.Write(this.testBytes, 0, 8); testRing.Read(tempBytes, 0, 2);
testRing.Read(tempBytes, 0, 2); testRing.Read(tempBytes, 0, 2);
testRing.Read(tempBytes, 0, 2); testRing.Read(tempBytes, 0, 1);
testRing.Read(tempBytes, 0, 1); testRing.Read(tempBytes, 0, 1);
testRing.Read(tempBytes, 0, 1);
Assert.AreEqual(2, testRing.Length);
Assert.AreEqual(2, testRing.Length); }
}
/// <summary>
/// <summary> /// Verifies that an exception is thrown when the Position property of the ring
/// Verifies that an exception is thrown when the Position property of the ring /// memory stream is used to retrieve the current file pointer position
/// memory stream is used to retrieve the current file pointer position /// </summary>
/// </summary> [Test]
[Test] public void TestThrowOnRetrievePosition() {
public void TestThrowOnRetrievePosition() { Assert.Throws<NotSupportedException>(
Assert.Throws<NotSupportedException>( delegate() { Console.WriteLine(new RingMemoryStream(10).Position); }
delegate() { Console.WriteLine(new RingMemoryStream(10).Position); } );
); }
}
/// <summary>
/// <summary> /// Verifies that an exception is thrown when the Position property of the ring
/// Verifies that an exception is thrown when the Position property of the ring /// memory stream is used to modify the current file pointer position
/// memory stream is used to modify the current file pointer position /// </summary>
/// </summary> [Test]
[Test] public void TestThrowOnAssignPosition() {
public void TestThrowOnAssignPosition() { Assert.Throws<NotSupportedException>(
Assert.Throws<NotSupportedException>( delegate() { new RingMemoryStream(10).Position = 0; }
delegate() { new RingMemoryStream(10).Position = 0; } );
); }
}
/// <summary>
/// <summary> /// Verifies that an exception is thrown when the Seek() method of the ring memory
/// Verifies that an exception is thrown when the Seek() method of the ring memory /// stream is attempted to be used
/// stream is attempted to be used /// </summary>
/// </summary> [Test]
[Test] public void TestThrowOnSeek() {
public void TestThrowOnSeek() { Assert.Throws<NotSupportedException>(
Assert.Throws<NotSupportedException>( delegate() { new RingMemoryStream(10).Seek(0, SeekOrigin.Begin); }
delegate() { new RingMemoryStream(10).Seek(0, SeekOrigin.Begin); } );
); }
}
/// <summary>
/// <summary> /// Verifies that an exception is thrown when the SetLength() method of the ring
/// Verifies that an exception is thrown when the SetLength() method of the ring /// memory stream is attempted to be used
/// memory stream is attempted to be used /// </summary>
/// </summary> [Test]
[Test] public void TestThrowOnSetLength() {
public void TestThrowOnSetLength() { Assert.Throws<NotSupportedException>(
Assert.Throws<NotSupportedException>( delegate() { new RingMemoryStream(10).SetLength(10); }
delegate() { new RingMemoryStream(10).SetLength(10); } );
); }
}
/// <summary>
/// <summary> /// Tests the Flush() method of the ring memory stream, which is either a dummy
/// Tests the Flush() method of the ring memory stream, which is either a dummy /// implementation or has no side effects
/// implementation or has no side effects /// </summary>
/// </summary> [Test]
[Test] public void TestFlush() {
public void TestFlush() { new RingMemoryStream(10).Flush();
new RingMemoryStream(10).Flush(); }
}
/// <summary>
/// <summary> /// Tests whether the length property is updated in accordance to the data written
/// Tests whether the length property is updated in accordance to the data written /// into the ring memory stream
/// into the ring memory stream /// </summary>
/// </summary> [Test]
[Test] public void TestLengthOnLinearBlock() {
public void TestLengthOnLinearBlock() { RingMemoryStream testRing = new RingMemoryStream(10);
RingMemoryStream testRing = new RingMemoryStream(10); testRing.Write(new byte[10], 0, 10);
testRing.Write(new byte[10], 0, 10);
Assert.AreEqual(10, testRing.Length);
Assert.AreEqual(10, testRing.Length); }
}
/// <summary>
/// <summary> /// Tests whether the length property is updated in accordance to the data written
/// Tests whether the length property is updated in accordance to the data written /// into the ring memory stream when the data is split within the stream
/// into the ring memory stream when the data is split within the stream /// </summary>
/// </summary> [Test]
[Test] public void TestLengthOnSplitBlock() {
public void TestLengthOnSplitBlock() { RingMemoryStream testRing = new RingMemoryStream(10);
RingMemoryStream testRing = new RingMemoryStream(10);
testRing.Write(new byte[10], 0, 10);
testRing.Write(new byte[10], 0, 10); testRing.Read(new byte[5], 0, 5);
testRing.Read(new byte[5], 0, 5); testRing.Write(new byte[5], 0, 5);
testRing.Write(new byte[5], 0, 5);
Assert.AreEqual(10, testRing.Length);
Assert.AreEqual(10, testRing.Length); }
}
/// <summary>Test data for the ring buffer unit tests</summary>
/// <summary>Test data for the ring buffer unit tests</summary> private byte[] testBytes;
private byte[] testBytes;
}
}
} // namespace Nuclex.Support.IO
} // namespace Nuclex.Support.IO
#endif // UNITTEST
#endif // UNITTEST

View File

@ -1,256 +1,255 @@
#region CPL License #region Apache License 2.0
/* /*
Nuclex Framework Nuclex .NET Framework
Copyright (C) 2002-2017 Nuclex Development Labs Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
This library is free software; you can redistribute it and/or Licensed under the Apache License, Version 2.0 (the "License");
modify it under the terms of the IBM Common Public License as you may not use this file except in compliance with the License.
published by the IBM Corporation; either version 1.0 of the You may obtain a copy of the License at
License, or (at your option) any later version.
http://www.apache.org/licenses/LICENSE-2.0
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of Unless required by applicable law or agreed to in writing, software
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the distributed under the License is distributed on an "AS IS" BASIS,
IBM Common Public License for more details. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
You should have received a copy of the IBM Common Public limitations under the License.
License along with this library */
*/ #endregion // Apache License 2.0
#endregion
using System;
using System; using System.IO;
using System.IO;
namespace Nuclex.Support.IO {
namespace Nuclex.Support.IO {
/// <summary>Specialized memory stream for ring buffers</summary>
/// <summary>Specialized memory stream for ring buffers</summary> /// <remarks>
/// <remarks> /// This ring buffer class is specialized for binary data and tries to achieve
/// This ring buffer class is specialized for binary data and tries to achieve /// optimal efficiency when storing and retrieving chunks of several bytes
/// optimal efficiency when storing and retrieving chunks of several bytes /// at once. Typical use cases include audio and network buffers where one party
/// at once. Typical use cases include audio and network buffers where one party /// is responsible for refilling the buffer at regular intervals while the other
/// is responsible for refilling the buffer at regular intervals while the other /// constantly streams data out of it.
/// constantly streams data out of it. /// </remarks>
/// </remarks> public class RingMemoryStream : Stream {
public class RingMemoryStream : Stream {
/// <summary>Initializes a new ring memory stream</summary>
/// <summary>Initializes a new ring memory stream</summary> /// <param name="capacity">Maximum capacity of the stream</param>
/// <param name="capacity">Maximum capacity of the stream</param> public RingMemoryStream(int capacity) {
public RingMemoryStream(int capacity) { this.ringBuffer = new MemoryStream(capacity);
this.ringBuffer = new MemoryStream(capacity); this.ringBuffer.SetLength(capacity);
this.ringBuffer.SetLength(capacity); this.empty = true;
this.empty = true; }
}
/// <summary>Maximum amount of data that will fit into the ring memory stream</summary>
/// <summary>Maximum amount of data that will fit into the ring memory stream</summary> /// <exception cref="ArgumentOutOfRangeException">
/// <exception cref="ArgumentOutOfRangeException"> /// Thrown if the new capacity is too small for the data already contained
/// Thrown if the new capacity is too small for the data already contained /// in the ring buffer.
/// in the ring buffer. /// </exception>
/// </exception> public long Capacity {
public long Capacity { get { return this.ringBuffer.Length; }
get { return this.ringBuffer.Length; } set {
set { int length = (int)Length;
int length = (int)Length; if(value < length) {
if(value < length) { throw new ArgumentOutOfRangeException(
throw new ArgumentOutOfRangeException( "New capacity is less than the stream's current length"
"New capacity is less than the stream's current length" );
); }
}
// This could be done in a more efficient manner than just replacing
// This could be done in a more efficient manner than just replacing // the entire buffer, but since this operation will probably be only called
// the entire buffer, but since this operation will probably be only called // once during the lifetime of the application, if at all, I don't see
// once during the lifetime of the application, if at all, I don't see // the need to optimize it...
// the need to optimize it... MemoryStream newBuffer = new MemoryStream((int)value);
MemoryStream newBuffer = new MemoryStream((int)value);
newBuffer.SetLength(value);
newBuffer.SetLength(value); if(length > 0) {
if(length > 0) { Read(newBuffer.GetBuffer(), 0, length);
Read(newBuffer.GetBuffer(), 0, length); }
}
this.ringBuffer.Close(); // Equals dispose of the old buffer
this.ringBuffer.Close(); // Equals dispose of the old buffer this.ringBuffer = newBuffer;
this.ringBuffer = newBuffer; this.startIndex = 0;
this.startIndex = 0; this.endIndex = length;
this.endIndex = length; }
}
}
}
/// <summary>Whether it's possible to read from this stream</summary>
/// <summary>Whether it's possible to read from this stream</summary> public override bool CanRead { get { return true; } }
public override bool CanRead { get { return true; } } /// <summary>Whether this stream supports random access</summary>
/// <summary>Whether this stream supports random access</summary> public override bool CanSeek { get { return false; } }
public override bool CanSeek { get { return false; } } /// <summary>Whether it's possible to write into this stream</summary>
/// <summary>Whether it's possible to write into this stream</summary> public override bool CanWrite { get { return true; } }
public override bool CanWrite { get { return true; } } /// <summary>Flushes the buffers and writes down unsaved data</summary>
/// <summary>Flushes the buffers and writes down unsaved data</summary> public override void Flush() { }
public override void Flush() { }
/// <summary>Current length of the stream</summary>
/// <summary>Current length of the stream</summary> public override long Length {
public override long Length { get {
get { if((this.endIndex > this.startIndex) || this.empty) {
if((this.endIndex > this.startIndex) || this.empty) { return this.endIndex - this.startIndex;
return this.endIndex - this.startIndex; } else {
} else { return this.ringBuffer.Length - this.startIndex + this.endIndex;
return this.ringBuffer.Length - this.startIndex + this.endIndex; }
} }
} }
}
/// <summary>Current cursor position within the stream</summary>
/// <summary>Current cursor position within the stream</summary> /// <exception cref="NotSupportedException">Always</exception>
/// <exception cref="NotSupportedException">Always</exception> public override long Position {
public override long Position { get { throw new NotSupportedException("The ring buffer does not support seeking"); }
get { throw new NotSupportedException("The ring buffer does not support seeking"); } set { throw new NotSupportedException("The ring buffer does not support seeking"); }
set { throw new NotSupportedException("The ring buffer does not support seeking"); } }
}
/// <summary>Reads data from the beginning of the stream</summary>
/// <summary>Reads data from the beginning of the stream</summary> /// <param name="buffer">Buffer in which to store the data</param>
/// <param name="buffer">Buffer in which to store the data</param> /// <param name="offset">Starting index at which to begin writing the buffer</param>
/// <param name="offset">Starting index at which to begin writing the buffer</param> /// <param name="count">Number of bytes to read from the stream</param>
/// <param name="count">Number of bytes to read from the stream</param> /// <returns>Die Number of bytes actually read</returns>
/// <returns>Die Number of bytes actually read</returns> public override int Read(byte[] buffer, int offset, int count) {
public override int Read(byte[] buffer, int offset, int count) {
// The end index lies behind the start index (usual case), so the
// The end index lies behind the start index (usual case), so the // ring memory is not fragmented. Example: |-----<#######>-----|
// ring memory is not fragmented. Example: |-----<#######>-----| if((this.startIndex < this.endIndex) || this.empty) {
if((this.startIndex < this.endIndex) || this.empty) {
// The Stream interface requires us to return less than the requested
// The Stream interface requires us to return less than the requested // number of bytes if we don't have enough data
// number of bytes if we don't have enough data count = Math.Min(count, this.endIndex - this.startIndex);
count = Math.Min(count, this.endIndex - this.startIndex); if(count > 0) {
if(count > 0) { this.ringBuffer.Position = this.startIndex;
this.ringBuffer.Position = this.startIndex; this.ringBuffer.Read(buffer, offset, count);
this.ringBuffer.Read(buffer, offset, count); this.startIndex += count;
this.startIndex += count;
if(this.startIndex == this.endIndex) {
if(this.startIndex == this.endIndex) { setEmpty();
setEmpty(); }
} }
}
} else { // The end index lies in front of the start index
} else { // The end index lies in front of the start index
// With the end before the start index, the data in the ring memory
// With the end before the start index, the data in the ring memory // stream is fragmented. Example: |#####>-------<#####|
// stream is fragmented. Example: |#####>-------<#####| int linearAvailable = (int)this.ringBuffer.Length - this.startIndex;
int linearAvailable = (int)this.ringBuffer.Length - this.startIndex;
// Will this read process cross the end of the ring buffer, requiring us to
// Will this read process cross the end of the ring buffer, requiring us to // read the data in 2 steps?
// read the data in 2 steps? if(count > linearAvailable) {
if(count > linearAvailable) {
// The Stream interface requires us to return less than the requested
// The Stream interface requires us to return less than the requested // number of bytes if we don't have enough data
// number of bytes if we don't have enough data count = Math.Min(count, linearAvailable + this.endIndex);
count = Math.Min(count, linearAvailable + this.endIndex);
this.ringBuffer.Position = this.startIndex;
this.ringBuffer.Position = this.startIndex; this.ringBuffer.Read(buffer, offset, linearAvailable);
this.ringBuffer.Read(buffer, offset, linearAvailable); this.ringBuffer.Position = 0;
this.ringBuffer.Position = 0; this.startIndex = count - linearAvailable;
this.startIndex = count - linearAvailable; this.ringBuffer.Read(buffer, offset + linearAvailable, this.startIndex);
this.ringBuffer.Read(buffer, offset + linearAvailable, this.startIndex);
} else { // Nope, the amount of requested data can be read in one piece
} else { // Nope, the amount of requested data can be read in one piece this.ringBuffer.Position = this.startIndex;
this.ringBuffer.Position = this.startIndex; this.ringBuffer.Read(buffer, offset, count);
this.ringBuffer.Read(buffer, offset, count); this.startIndex += count;
this.startIndex += count;
}
}
// If we consumed the entire ring buffer, set the empty flag and move
// If we consumed the entire ring buffer, set the empty flag and move // the indexes back to zero for better performance
// the indexes back to zero for better performance if(this.startIndex == this.endIndex) {
if(this.startIndex == this.endIndex) { setEmpty();
setEmpty(); }
}
}
}
return count;
return count; }
}
/// <summary>Appends data to the end of the stream</summary>
/// <summary>Appends data to the end of the stream</summary> /// <param name="buffer">Buffer containing the data to append</param>
/// <param name="buffer">Buffer containing the data to append</param> /// <param name="offset">Starting index of the data in the buffer</param>
/// <param name="offset">Starting index of the data in the buffer</param> /// <param name="count">Number of bytes to write to the stream</param>
/// <param name="count">Number of bytes to write to the stream</param> /// <exception cref="OverflowException">When the ring buffer is full</exception>
/// <exception cref="OverflowException">When the ring buffer is full</exception> public override void Write(byte[] buffer, int offset, int count) {
public override void Write(byte[] buffer, int offset, int count) {
// The end index lies behind the start index (usual case), so the
// The end index lies behind the start index (usual case), so the // unused buffer space is fragmented. Example: |-----<#######>-----|
// unused buffer space is fragmented. Example: |-----<#######>-----| if((this.startIndex < this.endIndex) || this.empty) {
if((this.startIndex < this.endIndex) || this.empty) { int linearAvailable = (int)(this.ringBuffer.Length - this.endIndex);
int linearAvailable = (int)(this.ringBuffer.Length - this.endIndex);
// If the data to be written would cross the ring memory stream's end,
// If the data to be written would cross the ring memory stream's end, // we have to check that there's enough space at the beginning of the
// we have to check that there's enough space at the beginning of the // stream to contain the remainder of the data.
// stream to contain the remainder of the data. if(count > linearAvailable) {
if(count > linearAvailable) { if(count > (linearAvailable + this.startIndex))
if(count > (linearAvailable + this.startIndex)) throw new OverflowException("Data does not fit in buffer");
throw new OverflowException("Data does not fit in buffer");
this.ringBuffer.Position = this.endIndex;
this.ringBuffer.Position = this.endIndex; this.ringBuffer.Write(buffer, offset, linearAvailable);
this.ringBuffer.Write(buffer, offset, linearAvailable); this.ringBuffer.Position = 0;
this.ringBuffer.Position = 0; this.endIndex = count - linearAvailable;
this.endIndex = count - linearAvailable; this.ringBuffer.Write(buffer, offset + linearAvailable, this.endIndex);
this.ringBuffer.Write(buffer, offset + linearAvailable, this.endIndex);
} else { // All data can be appended at the current stream position
} else { // All data can be appended at the current stream position this.ringBuffer.Position = this.endIndex;
this.ringBuffer.Position = this.endIndex; this.ringBuffer.Write(buffer, offset, count);
this.ringBuffer.Write(buffer, offset, count); this.endIndex += count;
this.endIndex += count; }
}
this.empty = false;
this.empty = false;
} else { // The end index lies before the start index
} else { // The end index lies before the start index
// The ring memory stream has been fragmented. This means the gap into which
// The ring memory stream has been fragmented. This means the gap into which // we are about to write is not fragmented. Example: |#####>-------<#####|
// we are about to write is not fragmented. Example: |#####>-------<#####| if(count > (this.startIndex - this.endIndex))
if(count > (this.startIndex - this.endIndex)) throw new OverflowException("Data does not fit in buffer");
throw new OverflowException("Data does not fit in buffer");
// Because the gap isn't fragmented, we can be sure that a single
// Because the gap isn't fragmented, we can be sure that a single // write call will suffice.
// write call will suffice. this.ringBuffer.Position = this.endIndex;
this.ringBuffer.Position = this.endIndex; this.ringBuffer.Write(buffer, offset, count);
this.ringBuffer.Write(buffer, offset, count); this.endIndex += count;
this.endIndex += count;
}
}
}
}
/// <summary>Jumps to the specified location within the stream</summary>
/// <summary>Jumps to the specified location within the stream</summary> /// <param name="offset">Position to jump to</param>
/// <param name="offset">Position to jump to</param> /// <param name="origin">Origin towards which to interpret the offset</param>
/// <param name="origin">Origin towards which to interpret the offset</param> /// <returns>The new offset within the stream</returns>
/// <returns>The new offset within the stream</returns> /// <exception cref="NotSupportedException">Always</exception>
/// <exception cref="NotSupportedException">Always</exception> public override long Seek(long offset, SeekOrigin origin) {
public override long Seek(long offset, SeekOrigin origin) { throw new NotSupportedException("The ring buffer does not support seeking");
throw new NotSupportedException("The ring buffer does not support seeking"); }
}
/// <summary>Changes the length of the stream</summary>
/// <summary>Changes the length of the stream</summary> /// <param name="value">New length to resize the stream to</param>
/// <param name="value">New length to resize the stream to</param> /// <exception cref="NotSupportedException">Always</exception>
/// <exception cref="NotSupportedException">Always</exception> public override void SetLength(long value) {
public override void SetLength(long value) { throw new NotSupportedException("This operation is not supported");
throw new NotSupportedException("This operation is not supported"); }
}
/// <summary>Resets the stream to its empty state</summary>
/// <summary>Resets the stream to its empty state</summary> private void setEmpty() {
private void setEmpty() { this.empty = true;
this.empty = true; this.startIndex = 0;
this.startIndex = 0; this.endIndex = 0;
this.endIndex = 0; }
}
/// <summary>Internal stream containing the ring buffer data</summary>
/// <summary>Internal stream containing the ring buffer data</summary> private MemoryStream ringBuffer;
private MemoryStream ringBuffer; /// <summary>Start index of the data within the ring buffer</summary>
/// <summary>Start index of the data within the ring buffer</summary> private int startIndex;
private int startIndex; /// <summary>End index of the data within the ring buffer</summary>
/// <summary>End index of the data within the ring buffer</summary> private int endIndex;
private int endIndex; /// <summary>Whether the ring buffer is empty</summary>
/// <summary>Whether the ring buffer is empty</summary> /// <remarks>
/// <remarks> /// This field is required to differentiate between the ring buffer being
/// This field is required to differentiate between the ring buffer being /// filled to the limit and being totally empty, because in both cases,
/// filled to the limit and being totally empty, because in both cases, /// the start index and the end index will be the same.
/// the start index and the end index will be the same. /// </remarks>
/// </remarks> private bool empty;
private bool empty;
}
}
} // namespace Nuclex.Support.IO
} // namespace Nuclex.Support.IO

View File

@ -1,130 +1,129 @@
#region CPL License #region Apache License 2.0
/* /*
Nuclex Framework Nuclex .NET Framework
Copyright (C) 2002-2017 Nuclex Development Labs Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
This library is free software; you can redistribute it and/or Licensed under the Apache License, Version 2.0 (the "License");
modify it under the terms of the IBM Common Public License as you may not use this file except in compliance with the License.
published by the IBM Corporation; either version 1.0 of the You may obtain a copy of the License at
License, or (at your option) any later version.
http://www.apache.org/licenses/LICENSE-2.0
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of Unless required by applicable law or agreed to in writing, software
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the distributed under the License is distributed on an "AS IS" BASIS,
IBM Common Public License for more details. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
You should have received a copy of the IBM Common Public limitations under the License.
License along with this library */
*/ #endregion // Apache License 2.0
#endregion
#if UNITTEST
#if UNITTEST
using System;
using System; using System.Collections.Generic;
using System.Collections.Generic;
using NUnit.Framework;
using NUnit.Framework;
namespace Nuclex.Support {
namespace Nuclex.Support {
/// <summary>Contains unit tests for the integer helper class</summary>
/// <summary>Contains unit tests for the integer helper class</summary> [TestFixture]
[TestFixture] internal class IntegerHelperTest {
internal class IntegerHelperTest {
/// <summary>
/// <summary> /// Verifies that the next power of 2 calculation works for long integers
/// Verifies that the next power of 2 calculation works for long integers /// </summary>
/// </summary> [Test]
[Test] public void TestNextPowerOf2ULong() {
public void TestNextPowerOf2ULong() { Assert.AreEqual(1UL, IntegerHelper.NextPowerOf2(0UL));
Assert.AreEqual(1UL, IntegerHelper.NextPowerOf2(0UL)); Assert.AreEqual(1UL, IntegerHelper.NextPowerOf2(1UL));
Assert.AreEqual(1UL, IntegerHelper.NextPowerOf2(1UL)); Assert.AreEqual(2UL, IntegerHelper.NextPowerOf2(2UL));
Assert.AreEqual(2UL, IntegerHelper.NextPowerOf2(2UL)); Assert.AreEqual(4UL, IntegerHelper.NextPowerOf2(3UL));
Assert.AreEqual(4UL, IntegerHelper.NextPowerOf2(3UL)); Assert.AreEqual(4UL, IntegerHelper.NextPowerOf2(4UL));
Assert.AreEqual(4UL, IntegerHelper.NextPowerOf2(4UL)); Assert.AreEqual(
Assert.AreEqual( 9223372036854775808UL, IntegerHelper.NextPowerOf2(4611686018427387905UL)
9223372036854775808UL, IntegerHelper.NextPowerOf2(4611686018427387905UL) );
); Assert.AreEqual(
Assert.AreEqual( 9223372036854775808UL, IntegerHelper.NextPowerOf2(9223372036854775807UL)
9223372036854775808UL, IntegerHelper.NextPowerOf2(9223372036854775807UL) );
); Assert.AreEqual(
Assert.AreEqual( 9223372036854775808UL, IntegerHelper.NextPowerOf2(9223372036854775808UL)
9223372036854775808UL, IntegerHelper.NextPowerOf2(9223372036854775808UL) );
); }
}
/// <summary>
/// <summary> /// Verifies that the next power of 2 calculation works for long integers
/// Verifies that the next power of 2 calculation works for long integers /// </summary>
/// </summary> [Test]
[Test] public void TestNextPowerOf2Long() {
public void TestNextPowerOf2Long() { Assert.AreEqual(1L, IntegerHelper.NextPowerOf2(0L));
Assert.AreEqual(1L, IntegerHelper.NextPowerOf2(0L)); Assert.AreEqual(1L, IntegerHelper.NextPowerOf2(1L));
Assert.AreEqual(1L, IntegerHelper.NextPowerOf2(1L)); Assert.AreEqual(2L, IntegerHelper.NextPowerOf2(2L));
Assert.AreEqual(2L, IntegerHelper.NextPowerOf2(2L)); Assert.AreEqual(4L, IntegerHelper.NextPowerOf2(3L));
Assert.AreEqual(4L, IntegerHelper.NextPowerOf2(3L)); Assert.AreEqual(4L, IntegerHelper.NextPowerOf2(4L));
Assert.AreEqual(4L, IntegerHelper.NextPowerOf2(4L)); Assert.AreEqual(4611686018427387904L, IntegerHelper.NextPowerOf2(2305843009213693953L));
Assert.AreEqual(4611686018427387904L, IntegerHelper.NextPowerOf2(2305843009213693953L)); Assert.AreEqual(4611686018427387904L, IntegerHelper.NextPowerOf2(4611686018427387903L));
Assert.AreEqual(4611686018427387904L, IntegerHelper.NextPowerOf2(4611686018427387903L)); Assert.AreEqual(4611686018427387904L, IntegerHelper.NextPowerOf2(4611686018427387904L));
Assert.AreEqual(4611686018427387904L, IntegerHelper.NextPowerOf2(4611686018427387904L)); }
}
/// <summary>
/// <summary> /// Verifies that the next power of 2 calculation works for integers
/// Verifies that the next power of 2 calculation works for integers /// </summary>
/// </summary> [Test]
[Test] public void TestNextPowerOf2UInt() {
public void TestNextPowerOf2UInt() { Assert.AreEqual(1U, IntegerHelper.NextPowerOf2(0U));
Assert.AreEqual(1U, IntegerHelper.NextPowerOf2(0U)); Assert.AreEqual(1U, IntegerHelper.NextPowerOf2(1U));
Assert.AreEqual(1U, IntegerHelper.NextPowerOf2(1U)); Assert.AreEqual(2U, IntegerHelper.NextPowerOf2(2U));
Assert.AreEqual(2U, IntegerHelper.NextPowerOf2(2U)); Assert.AreEqual(4U, IntegerHelper.NextPowerOf2(3U));
Assert.AreEqual(4U, IntegerHelper.NextPowerOf2(3U)); Assert.AreEqual(4U, IntegerHelper.NextPowerOf2(4U));
Assert.AreEqual(4U, IntegerHelper.NextPowerOf2(4U)); Assert.AreEqual(2147483648U, IntegerHelper.NextPowerOf2(1073741825U));
Assert.AreEqual(2147483648U, IntegerHelper.NextPowerOf2(1073741825U)); Assert.AreEqual(2147483648U, IntegerHelper.NextPowerOf2(2147483647U));
Assert.AreEqual(2147483648U, IntegerHelper.NextPowerOf2(2147483647U)); Assert.AreEqual(2147483648U, IntegerHelper.NextPowerOf2(2147483648U));
Assert.AreEqual(2147483648U, IntegerHelper.NextPowerOf2(2147483648U)); }
}
/// <summary>
/// <summary> /// Verifies that the next power of 2 calculation works for integers
/// Verifies that the next power of 2 calculation works for integers /// </summary>
/// </summary> [Test]
[Test] public void TestNextPowerOf2Int() {
public void TestNextPowerOf2Int() { Assert.AreEqual(1, IntegerHelper.NextPowerOf2(0));
Assert.AreEqual(1, IntegerHelper.NextPowerOf2(0)); Assert.AreEqual(1, IntegerHelper.NextPowerOf2(1));
Assert.AreEqual(1, IntegerHelper.NextPowerOf2(1)); Assert.AreEqual(2, IntegerHelper.NextPowerOf2(2));
Assert.AreEqual(2, IntegerHelper.NextPowerOf2(2)); Assert.AreEqual(4, IntegerHelper.NextPowerOf2(3));
Assert.AreEqual(4, IntegerHelper.NextPowerOf2(3)); Assert.AreEqual(4, IntegerHelper.NextPowerOf2(4));
Assert.AreEqual(4, IntegerHelper.NextPowerOf2(4)); Assert.AreEqual(1073741824, IntegerHelper.NextPowerOf2(536870913));
Assert.AreEqual(1073741824, IntegerHelper.NextPowerOf2(536870913)); Assert.AreEqual(1073741824, IntegerHelper.NextPowerOf2(1073741823));
Assert.AreEqual(1073741824, IntegerHelper.NextPowerOf2(1073741823)); Assert.AreEqual(1073741824, IntegerHelper.NextPowerOf2(1073741824));
Assert.AreEqual(1073741824, IntegerHelper.NextPowerOf2(1073741824)); }
}
/// <summary>Verifies that the bit counting method for integers works</summary>
/// <summary>Verifies that the bit counting method for integers works</summary> [Test]
[Test] public void TestCountBitsInInteger() {
public void TestCountBitsInInteger() { Assert.AreEqual(0, IntegerHelper.CountBits(0));
Assert.AreEqual(0, IntegerHelper.CountBits(0)); Assert.AreEqual(32, IntegerHelper.CountBits(-1));
Assert.AreEqual(32, IntegerHelper.CountBits(-1)); Assert.AreEqual(16, IntegerHelper.CountBits(0x55555555));
Assert.AreEqual(16, IntegerHelper.CountBits(0x55555555)); Assert.AreEqual(16, IntegerHelper.CountBits(0xAAAAAAAA));
Assert.AreEqual(16, IntegerHelper.CountBits(0xAAAAAAAA));
for (int bitIndex = 0; bitIndex < 32; ++bitIndex) {
for (int bitIndex = 0; bitIndex < 32; ++bitIndex) { Assert.AreEqual(1, IntegerHelper.CountBits(1 << bitIndex));
Assert.AreEqual(1, IntegerHelper.CountBits(1 << bitIndex)); }
} }
}
/// <summary>Verifies that the bit counting method for long integers works</summary>
/// <summary>Verifies that the bit counting method for long integers works</summary> [Test]
[Test] public void TestCountBitsInLongInteger() {
public void TestCountBitsInLongInteger() { Assert.AreEqual(0, IntegerHelper.CountBits(0L));
Assert.AreEqual(0, IntegerHelper.CountBits(0L)); Assert.AreEqual(64, IntegerHelper.CountBits(-1L));
Assert.AreEqual(64, IntegerHelper.CountBits(-1L)); Assert.AreEqual(32, IntegerHelper.CountBits(0x5555555555555555));
Assert.AreEqual(32, IntegerHelper.CountBits(0x5555555555555555)); Assert.AreEqual(32, IntegerHelper.CountBits(0xAAAAAAAAAAAAAAAA));
Assert.AreEqual(32, IntegerHelper.CountBits(0xAAAAAAAAAAAAAAAA));
for (int bitIndex = 0; bitIndex < 64; ++bitIndex) {
for (int bitIndex = 0; bitIndex < 64; ++bitIndex) { Assert.AreEqual(1, IntegerHelper.CountBits(1 << bitIndex));
Assert.AreEqual(1, IntegerHelper.CountBits(1 << bitIndex)); }
} }
}
}
}
} // namespace Nuclex.Support
} // namespace Nuclex.Support
#endif // UNITTEST
#endif // UNITTEST

View File

@ -1,127 +1,126 @@
#region CPL License #region Apache License 2.0
/* /*
Nuclex Framework Nuclex .NET Framework
Copyright (C) 2002-2017 Nuclex Development Labs Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
This library is free software; you can redistribute it and/or Licensed under the Apache License, Version 2.0 (the "License");
modify it under the terms of the IBM Common Public License as you may not use this file except in compliance with the License.
published by the IBM Corporation; either version 1.0 of the You may obtain a copy of the License at
License, or (at your option) any later version.
http://www.apache.org/licenses/LICENSE-2.0
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of Unless required by applicable law or agreed to in writing, software
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the distributed under the License is distributed on an "AS IS" BASIS,
IBM Common Public License for more details. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
You should have received a copy of the IBM Common Public limitations under the License.
License along with this library */
*/ #endregion // Apache License 2.0
#endregion
using System;
using System;
namespace Nuclex.Support {
namespace Nuclex.Support {
/// <summary>Helper methods for working with integer types</summary>
/// <summary>Helper methods for working with integer types</summary> public static class IntegerHelper {
public static class IntegerHelper {
/// <summary>Returns the next highest power of 2 from the specified value</summary>
/// <summary>Returns the next highest power of 2 from the specified value</summary> /// <param name="value">Value of which to return the next highest power of 2</param>
/// <param name="value">Value of which to return the next highest power of 2</param> /// <returns>The next highest power of 2 to the value</returns>
/// <returns>The next highest power of 2 to the value</returns> public static long NextPowerOf2(this long value) {
public static long NextPowerOf2(this long value) { return (long)NextPowerOf2((ulong)value);
return (long)NextPowerOf2((ulong)value); }
}
/// <summary>Returns the next highest power of 2 from the specified value</summary>
/// <summary>Returns the next highest power of 2 from the specified value</summary> /// <param name="value">Value of which to return the next highest power of 2</param>
/// <param name="value">Value of which to return the next highest power of 2</param> /// <returns>The next highest power of 2 to the value</returns>
/// <returns>The next highest power of 2 to the value</returns> public static ulong NextPowerOf2(this ulong value) {
public static ulong NextPowerOf2(this ulong value) { if (value == 0)
if (value == 0) return 1;
return 1;
--value;
--value; value |= value >> 1;
value |= value >> 1; value |= value >> 2;
value |= value >> 2; value |= value >> 4;
value |= value >> 4; value |= value >> 8;
value |= value >> 8; value |= value >> 16;
value |= value >> 16; value |= value >> 32;
value |= value >> 32; ++value;
++value;
return value;
return value; }
}
/// <summary>Returns the next highest power of 2 from the specified value</summary>
/// <summary>Returns the next highest power of 2 from the specified value</summary> /// <param name="value">Value of which to return the next highest power of 2</param>
/// <param name="value">Value of which to return the next highest power of 2</param> /// <returns>The next highest power of 2 to the value</returns>
/// <returns>The next highest power of 2 to the value</returns> public static int NextPowerOf2(this int value) {
public static int NextPowerOf2(this int value) { return (int)NextPowerOf2((uint)value);
return (int)NextPowerOf2((uint)value); }
}
/// <summary>Returns the next highest power of 2 from the specified value</summary>
/// <summary>Returns the next highest power of 2 from the specified value</summary> /// <param name="value">Value of which to return the next highest power of 2</param>
/// <param name="value">Value of which to return the next highest power of 2</param> /// <returns>The next highest power of 2 to the value</returns>
/// <returns>The next highest power of 2 to the value</returns> public static uint NextPowerOf2(this uint value) {
public static uint NextPowerOf2(this uint value) { if (value == 0)
if (value == 0) return 1;
return 1;
--value;
--value; value |= value >> 1;
value |= value >> 1; value |= value >> 2;
value |= value >> 2; value |= value >> 4;
value |= value >> 4; value |= value >> 8;
value |= value >> 8; value |= value >> 16;
value |= value >> 16; ++value;
++value;
return value;
return value; }
}
/// <summary>Returns the number of bits set in an integer</summary>
/// <summary>Returns the number of bits set in an integer</summary> /// <param name="value">Value whose bits will be counted</param>
/// <param name="value">Value whose bits will be counted</param> /// <returns>The number of bits set in the integer</returns>
/// <returns>The number of bits set in the integer</returns> public static int CountBits(this int value) {
public static int CountBits(this int value) { return CountBits((uint)value);
return CountBits((uint)value); }
}
/// <summary>Returns the number of bits set in an unsigned integer</summary>
/// <summary>Returns the number of bits set in an unsigned integer</summary> /// <param name="value">Value whose bits will be counted</param>
/// <param name="value">Value whose bits will be counted</param> /// <returns>The number of bits set in the unsigned integer</returns>
/// <returns>The number of bits set in the unsigned integer</returns> /// <remarks>
/// <remarks> /// Based on a trick revealed here:
/// Based on a trick revealed here: /// http://stackoverflow.com/questions/109023
/// http://stackoverflow.com/questions/109023 /// </remarks>
/// </remarks> public static int CountBits(this uint value) {
public static int CountBits(this uint value) { value = value - ((value >> 1) & 0x55555555);
value = value - ((value >> 1) & 0x55555555); value = (value & 0x33333333) + ((value >> 2) & 0x33333333);
value = (value & 0x33333333) + ((value >> 2) & 0x33333333);
return (int)unchecked(
return (int)unchecked( ((value + (value >> 4) & 0xF0F0F0F) * 0x1010101) >> 24
((value + (value >> 4) & 0xF0F0F0F) * 0x1010101) >> 24 );
); }
}
/// <summary>Returns the number of bits set in a long integer</summary>
/// <summary>Returns the number of bits set in a long integer</summary> /// <param name="value">Value whose bits will be counted</param>
/// <param name="value">Value whose bits will be counted</param> /// <returns>The number of bits set in the long integer</returns>
/// <returns>The number of bits set in the long integer</returns> public static int CountBits(this long value) {
public static int CountBits(this long value) { return CountBits((ulong)value);
return CountBits((ulong)value); }
}
/// <summary>Returns the number of bits set in an unsigned long integer</summary>
/// <summary>Returns the number of bits set in an unsigned long integer</summary> /// <param name="value">Value whose bits will be counted</param>
/// <param name="value">Value whose bits will be counted</param> /// <returns>The number of bits set in the unsigned long integer</returns>
/// <returns>The number of bits set in the unsigned long integer</returns> /// <remarks>
/// <remarks> /// Based on a trick revealed here:
/// Based on a trick revealed here: /// http://stackoverflow.com/questions/2709430
/// http://stackoverflow.com/questions/2709430 /// </remarks>
/// </remarks> public static int CountBits(this ulong value) {
public static int CountBits(this ulong value) { value = value - ((value >> 1) & 0x5555555555555555UL);
value = value - ((value >> 1) & 0x5555555555555555UL); value = (value & 0x3333333333333333UL) + ((value >> 2) & 0x3333333333333333UL);
value = (value & 0x3333333333333333UL) + ((value >> 2) & 0x3333333333333333UL);
return (int)unchecked(
return (int)unchecked( (((value + (value >> 4)) & 0xF0F0F0F0F0F0F0FUL) * 0x101010101010101UL) >> 56
(((value + (value >> 4)) & 0xF0F0F0F0F0F0F0FUL) * 0x101010101010101UL) >> 56 );
); }
}
}
}
} // namespace Nuclex.Support
} // namespace Nuclex.Support

View File

@ -1,142 +1,141 @@
#region CPL License #region Apache License 2.0
/* /*
Nuclex Framework Nuclex .NET Framework
Copyright (C) 2002-2017 Nuclex Development Labs Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
This library is free software; you can redistribute it and/or Licensed under the Apache License, Version 2.0 (the "License");
modify it under the terms of the IBM Common Public License as you may not use this file except in compliance with the License.
published by the IBM Corporation; either version 1.0 of the You may obtain a copy of the License at
License, or (at your option) any later version.
http://www.apache.org/licenses/LICENSE-2.0
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of Unless required by applicable law or agreed to in writing, software
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the distributed under the License is distributed on an "AS IS" BASIS,
IBM Common Public License for more details. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
You should have received a copy of the IBM Common Public limitations under the License.
License along with this library */
*/ #endregion // Apache License 2.0
#endregion
using System;
using System; using System.Collections;
using System.Collections;
#if UNITTEST
#if UNITTEST
using NUnit.Framework;
using NUnit.Framework;
namespace Nuclex.Support.Licensing {
namespace Nuclex.Support.Licensing {
/// <summary>Unit test for the license key class</summary>
/// <summary>Unit test for the license key class</summary> [TestFixture]
[TestFixture] internal class LicenseKeyTest {
internal class LicenseKeyTest {
/// <summary>Tests the default constructor of the license key class</summary>
/// <summary>Tests the default constructor of the license key class</summary> [Test]
[Test] public void DefaultConstructorCanBeUsed() {
public void DefaultConstructorCanBeUsed() { Assert.IsNotNull(new LicenseKey()); // Nonsense, prevents compiler warning
Assert.IsNotNull(new LicenseKey()); // Nonsense, prevents compiler warning }
}
/// <summary>Validates the correct translation of keys to GUIDs and back</summary>
/// <summary>Validates the correct translation of keys to GUIDs and back</summary> [Test]
[Test] public void LicenseKeysCanBeConvertedToGuidsAndBack() {
public void LicenseKeysCanBeConvertedToGuidsAndBack() { for(int i = 0; i < 128; ++i) {
for(int i = 0; i < 128; ++i) {
// Create a new BitArray with the n.th bit set
// Create a new BitArray with the n.th bit set BitArray guidBits = new BitArray(128);
BitArray guidBits = new BitArray(128); guidBits[i] = true;
guidBits[i] = true;
// Create a GUID from this Bitarray
// Create a GUID from this Bitarray byte[] guidBytes = new byte[16];
byte[] guidBytes = new byte[16]; guidBits.CopyTo(guidBytes, 0);
guidBits.CopyTo(guidBytes, 0); Guid originalGuid = new Guid(guidBytes);
Guid originalGuid = new Guid(guidBytes);
// Convert the GUID into a license key and back to a GUID
// Convert the GUID into a license key and back to a GUID string licenseKey = new LicenseKey(originalGuid).ToString();
string licenseKey = new LicenseKey(originalGuid).ToString(); Guid rebuiltGuid = LicenseKey.Parse(licenseKey).ToGuid();
Guid rebuiltGuid = LicenseKey.Parse(licenseKey).ToGuid();
// Verify that the original GUID matches the fore-and-back converted one
// Verify that the original GUID matches the fore-and-back converted one Assert.AreEqual(originalGuid, rebuiltGuid, "Test for GUID bit " + i);
Assert.AreEqual(originalGuid, rebuiltGuid, "Test for GUID bit " + i);
}
} }
}
/// <summary>Tests whether license keys can be modified without destroying them</summary>
/// <summary>Tests whether license keys can be modified without destroying them</summary> [Test]
[Test] public void LicenseKeysCanBeModified() {
public void LicenseKeysCanBeModified() {
for(int i = 0; i < 4; ++i) {
for(int i = 0; i < 4; ++i) { for(int j = 0; j < 8; ++j) {
for(int j = 0; j < 8; ++j) {
LicenseKey testKey = new LicenseKey(
LicenseKey testKey = new LicenseKey( new Guid(-1, -1, -1, 255, 255, 255, 255, 255, 255, 255, 255)
new Guid(-1, -1, -1, 255, 255, 255, 255, 255, 255, 255, 255) );
);
string originalString = testKey.ToString();
string originalString = testKey.ToString(); testKey[i] &= ~(1 << j);
testKey[i] &= ~(1 << j); string modifiedString = testKey.ToString();
string modifiedString = testKey.ToString();
Assert.IsTrue(
Assert.IsTrue( originalString != modifiedString, "Modified string differs from original"
originalString != modifiedString, "Modified string differs from original" );
);
testKey[i] |= (1 << j);
testKey[i] |= (1 << j); string revertedString = testKey.ToString();
string revertedString = testKey.ToString();
Assert.AreEqual(
Assert.AreEqual( originalString, revertedString, "Original state restorable"
originalString, revertedString, "Original state restorable" );
);
} // for j
} // for j } // for i
} // for i
}
}
/// <summary>Tests whether license keys can be modified without destroying them</summary>
/// <summary>Tests whether license keys can be modified without destroying them</summary> [Test]
[Test] public void ParsingInvalidLicenseKeyThrowsArgumentException() {
public void ParsingInvalidLicenseKeyThrowsArgumentException() { Assert.Throws<ArgumentException>(
Assert.Throws<ArgumentException>( delegate() { LicenseKey.Parse("hello world"); }
delegate() { LicenseKey.Parse("hello world"); } );
); }
}
/// <summary>
/// <summary> /// Tests whether an exception is thrown if the indexer of a license key is used
/// Tests whether an exception is thrown if the indexer of a license key is used /// with an invalid index to retrieve a component of the key
/// with an invalid index to retrieve a component of the key /// </summary>
/// </summary> [Test]
[Test] public void ReadingInvalidIndexThrowsIndexOutOfRangeException() {
public void ReadingInvalidIndexThrowsIndexOutOfRangeException() { LicenseKey key = new LicenseKey();
LicenseKey key = new LicenseKey(); Assert.Throws<IndexOutOfRangeException>(
Assert.Throws<IndexOutOfRangeException>( delegate() { Console.WriteLine(key[-1]); }
delegate() { Console.WriteLine(key[-1]); } );
); }
}
/// <summary>
/// <summary> /// Tests whether an exception is thrown if the indexer of a license key is used
/// Tests whether an exception is thrown if the indexer of a license key is used /// with an invalid index to set a component of the key
/// with an invalid index to set a component of the key /// </summary>
/// </summary> [Test]
[Test] public void WritingInvalidIndexThrowsIndexOutOfRangeException() {
public void WritingInvalidIndexThrowsIndexOutOfRangeException() { LicenseKey key = new LicenseKey();
LicenseKey key = new LicenseKey(); Assert.Throws<IndexOutOfRangeException>(
Assert.Throws<IndexOutOfRangeException>( delegate() { key[-1] = 0; }
delegate() { key[-1] = 0; } );
); }
}
/// <summary>
/// <summary> /// Verifies that a license key can be converted into a byte array
/// Verifies that a license key can be converted into a byte array /// </summary>
/// </summary> [Test]
[Test] public void LicenseKeyCanBeConvertedToByteArray() {
public void LicenseKeyCanBeConvertedToByteArray() { Guid someGuid = Guid.NewGuid();
Guid someGuid = Guid.NewGuid(); LicenseKey someKey = new LicenseKey(someGuid);
LicenseKey someKey = new LicenseKey(someGuid);
CollectionAssert.AreEqual(someGuid.ToByteArray(), someKey.ToByteArray());
CollectionAssert.AreEqual(someGuid.ToByteArray(), someKey.ToByteArray()); }
}
}
}
} // namespace Nuclex.Support.Licensing
} // namespace Nuclex.Support.Licensing
#endif // UNITTEST
#endif // UNITTEST

View File

@ -1,257 +1,256 @@
#region CPL License #region Apache License 2.0
/* /*
Nuclex Framework Nuclex .NET Framework
Copyright (C) 2002-2017 Nuclex Development Labs Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
This library is free software; you can redistribute it and/or Licensed under the Apache License, Version 2.0 (the "License");
modify it under the terms of the IBM Common Public License as you may not use this file except in compliance with the License.
published by the IBM Corporation; either version 1.0 of the You may obtain a copy of the License at
License, or (at your option) any later version.
http://www.apache.org/licenses/LICENSE-2.0
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of Unless required by applicable law or agreed to in writing, software
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the distributed under the License is distributed on an "AS IS" BASIS,
IBM Common Public License for more details. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
You should have received a copy of the IBM Common Public limitations under the License.
License along with this library */
*/ #endregion // Apache License 2.0
#endregion
using System;
using System; using System.Collections;
using System.Collections; using System.Text;
using System.Text;
namespace Nuclex.Support.Licensing {
namespace Nuclex.Support.Licensing {
/// <summary>Typical license key with 5x5 alphanumerical characters</summary>
/// <summary>Typical license key with 5x5 alphanumerical characters</summary> /// <remarks>
/// <remarks> /// <para>
/// <para> /// This class manages a license key like it is used in Microsoft products.
/// This class manages a license key like it is used in Microsoft products. /// Althought it is probably not the exact same technique used by Microsoft,
/// Althought it is probably not the exact same technique used by Microsoft, /// the textual representation of the license keys looks identical,
/// the textual representation of the license keys looks identical, /// eg. <code>O809J-RN5TD-IM3CU-4IG1O-O90X9</code>.
/// eg. <code>O809J-RN5TD-IM3CU-4IG1O-O90X9</code>. /// </para>
/// </para> /// <para>
/// <para> /// Available storage space is used efficiently and allows for up to four
/// Available storage space is used efficiently and allows for up to four /// 32 bit integers to be stored within the key, that's enough for a full GUID.
/// 32 bit integers to be stored within the key, that's enough for a full GUID. /// The four integers can be modified directly, for example to store feature
/// The four integers can be modified directly, for example to store feature /// lists, checksums or other data within the key.
/// lists, checksums or other data within the key. /// </para>
/// </para> /// </remarks>
/// </remarks> public class LicenseKey {
public class LicenseKey {
/// <summary>Parses the license key contained in a string</summary>
/// <summary>Parses the license key contained in a string</summary> /// <param name="key">String containing a license key that is to be parsed</param>
/// <param name="key">String containing a license key that is to be parsed</param> /// <returns>The license key parsed from provided string</returns>
/// <returns>The license key parsed from provided string</returns> /// <exception cref="ArgumentException">
/// <exception cref="ArgumentException"> /// When the provided string is not a license key
/// When the provided string is not a license key /// </exception>
/// </exception> public static LicenseKey Parse(string key) {
public static LicenseKey Parse(string key) { key = key.Replace(" ", string.Empty).Replace("-", string.Empty).ToUpper();
key = key.Replace(" ", string.Empty).Replace("-", string.Empty).ToUpper(); if(key.Length != 25)
if(key.Length != 25) throw new ArgumentException("This is not a license key");
throw new ArgumentException("This is not a license key");
BitArray bits = new BitArray(128);
BitArray bits = new BitArray(128); uint sequence;
uint sequence;
// Convert the first 4 sequences of 6 chars into 124 bits
// Convert the first 4 sequences of 6 chars into 124 bits for(int j = 0; j < 4; j++) {
for(int j = 0; j < 4; j++) {
sequence =
sequence = (uint)codeTable.IndexOf(key[j * 6 + 5]) * 60466176 +
(uint)codeTable.IndexOf(key[j * 6 + 5]) * 60466176 + (uint)codeTable.IndexOf(key[j * 6 + 4]) * 1679616 +
(uint)codeTable.IndexOf(key[j * 6 + 4]) * 1679616 + (uint)codeTable.IndexOf(key[j * 6 + 3]) * 46656 +
(uint)codeTable.IndexOf(key[j * 6 + 3]) * 46656 + (uint)codeTable.IndexOf(key[j * 6 + 2]) * 1296 +
(uint)codeTable.IndexOf(key[j * 6 + 2]) * 1296 + (uint)codeTable.IndexOf(key[j * 6 + 1]) * 36 +
(uint)codeTable.IndexOf(key[j * 6 + 1]) * 36 + (uint)codeTable.IndexOf(key[j * 6 + 0]);
(uint)codeTable.IndexOf(key[j * 6 + 0]);
for(int i = 0; i < 31; i++)
for(int i = 0; i < 31; i++) bits[j * 31 + i] = (sequence & powersOfTwo[i, 1]) != 0;
bits[j * 31 + i] = (sequence & powersOfTwo[i, 1]) != 0;
}
}
// Append the remaining character's 4 bits
// Append the remaining character's 4 bits sequence = (uint)codeTable.IndexOf(key[24]);
sequence = (uint)codeTable.IndexOf(key[24]); bits[124] = (sequence & powersOfTwo[4, 1]) != 0;
bits[124] = (sequence & powersOfTwo[4, 1]) != 0; bits[125] = (sequence & powersOfTwo[3, 1]) != 0;
bits[125] = (sequence & powersOfTwo[3, 1]) != 0; bits[126] = (sequence & powersOfTwo[2, 1]) != 0;
bits[126] = (sequence & powersOfTwo[2, 1]) != 0; bits[127] = (sequence & powersOfTwo[1, 1]) != 0;
bits[127] = (sequence & powersOfTwo[1, 1]) != 0;
// Revert the mangling that was applied to the key when encoding...
// Revert the mangling that was applied to the key when encoding... unmangle(bits);
unmangle(bits);
// ...and we've got our GUID back!
// ...and we've got our GUID back! byte[] guidBytes = new byte[16];
byte[] guidBytes = new byte[16]; bits.CopyTo(guidBytes, 0);
bits.CopyTo(guidBytes, 0);
return new LicenseKey(new Guid(guidBytes));
return new LicenseKey(new Guid(guidBytes)); }
}
/// <summary>Initializes a new, empty license key</summary>
/// <summary>Initializes a new, empty license key</summary> public LicenseKey() : this(Guid.Empty) { }
public LicenseKey() : this(Guid.Empty) { }
/// <summary>Initializes the license key from a GUID</summary>
/// <summary>Initializes the license key from a GUID</summary> /// <param name="source">GUID that is used to create the license key</param>
/// <param name="source">GUID that is used to create the license key</param> public LicenseKey(Guid source) {
public LicenseKey(Guid source) { this.guid = source;
this.guid = source; }
}
/// <summary>Accesses the four integer values within a license key</summary>
/// <summary>Accesses the four integer values within a license key</summary> /// <exception cref="IndexOutOfRangeException">
/// <exception cref="IndexOutOfRangeException"> /// When the index lies outside of the key's fields
/// When the index lies outside of the key's fields /// </exception>
/// </exception> public int this[int index] {
public int this[int index] { get {
get { if((index < 0) || (index > 3))
if((index < 0) || (index > 3)) throw new IndexOutOfRangeException("Index out of range");
throw new IndexOutOfRangeException("Index out of range");
return BitConverter.ToInt32(this.guid.ToByteArray(), index * 4);
return BitConverter.ToInt32(this.guid.ToByteArray(), index * 4); }
} set {
set { if((index < 0) || (index > 3))
if((index < 0) || (index > 3)) throw new IndexOutOfRangeException("Index out of range");
throw new IndexOutOfRangeException("Index out of range");
// Convert the GUID into binary data so we can replace one of its values
// Convert the GUID into binary data so we can replace one of its values byte[] guidBytes = this.guid.ToByteArray();
byte[] guidBytes = this.guid.ToByteArray();
// Overwrite the section at the index specified by the user with the new value
// Overwrite the section at the index specified by the user with the new value Array.Copy(
Array.Copy( BitConverter.GetBytes(value), 0, // source and start index
BitConverter.GetBytes(value), 0, // source and start index guidBytes, index * 4, // destination and start index
guidBytes, index * 4, // destination and start index 4 // length
4 // length );
);
// Replacement finished, now we can reconstruct our guid
// Replacement finished, now we can reconstruct our guid this.guid = new Guid(guidBytes);
this.guid = new Guid(guidBytes); }
} }
}
/// <summary>Converts the license key into a GUID</summary>
/// <summary>Converts the license key into a GUID</summary> /// <returns>The GUID created from the license key</returns>
/// <returns>The GUID created from the license key</returns> public Guid ToGuid() {
public Guid ToGuid() { return this.guid;
return this.guid; }
}
/// <summary>Converts the license key into a byte array</summary>
/// <summary>Converts the license key into a byte array</summary> /// <returns>A byte array containing the converted license key</returns>
/// <returns>A byte array containing the converted license key</returns> public byte[] ToByteArray() {
public byte[] ToByteArray() { return this.guid.ToByteArray();
return this.guid.ToByteArray(); }
}
/// <summary>Converts the license key to a string</summary>
/// <summary>Converts the license key to a string</summary> /// <returns>A string containing the converted license key</returns>
/// <returns>A string containing the converted license key</returns> public override string ToString() {
public override string ToString() { StringBuilder resultBuilder = new StringBuilder();
StringBuilder resultBuilder = new StringBuilder();
// Build a bit array from the input data
// Build a bit array from the input data BitArray bits = new BitArray(this.guid.ToByteArray());
BitArray bits = new BitArray(this.guid.ToByteArray()); mangle(bits);
mangle(bits);
int sequence = 0;
int sequence = 0;
// Build 4 sequences of 6 characters from the first 124 bits
// Build 4 sequences of 6 characters from the first 124 bits for(int i = 0; i < 4; ++i) {
for(int i = 0; i < 4; ++i) {
// We take the next 31 bits from the buffer
// We take the next 31 bits from the buffer for(int j = 0; j < 31; ++j)
for(int j = 0; j < 31; ++j) sequence |= (int)powersOfTwo[j, bits[i * 31 + j] ? 1 : 0];
sequence |= (int)powersOfTwo[j, bits[i * 31 + j] ? 1 : 0];
// Using 31 bits, a number up to 2.147.483.648 can be represented,
// Using 31 bits, a number up to 2.147.483.648 can be represented, // while 6 alpha-numerical characters allow for 2.176.782.336 possible values,
// while 6 alpha-numerical characters allow for 2.176.782.336 possible values, // which means we can fit 31 bits into every 6 alpha-numerical characters.
// which means we can fit 31 bits into every 6 alpha-numerical characters. for(int j = 0; j < 6; ++j) {
for(int j = 0; j < 6; ++j) { resultBuilder.Append(codeTable[sequence % 36]);
resultBuilder.Append(codeTable[sequence % 36]); sequence /= 36;
sequence /= 36; }
}
}
}
// Use the remaining 4 bits to build the final character
// Use the remaining 4 bits to build the final character resultBuilder.Append(
resultBuilder.Append( codeTable[
codeTable[ (int)(
(int)( powersOfTwo[4, bits[124] ? 1 : 0] |
powersOfTwo[4, bits[124] ? 1 : 0] | powersOfTwo[3, bits[125] ? 1 : 0] |
powersOfTwo[3, bits[125] ? 1 : 0] | powersOfTwo[2, bits[126] ? 1 : 0] |
powersOfTwo[2, bits[126] ? 1 : 0] | powersOfTwo[1, bits[127] ? 1 : 0] |
powersOfTwo[1, bits[127] ? 1 : 0] | powersOfTwo[0, 1] // One bit remains unused :)
powersOfTwo[0, 1] // One bit remains unused :) )
) ]
] );
);
// Now build a nice, readable string from the decoded characters
// Now build a nice, readable string from the decoded characters resultBuilder.Insert(5, keyDelimiter, 0, 1);
resultBuilder.Insert(5, keyDelimiter, 0, 1); resultBuilder.Insert(11, keyDelimiter, 0, 1);
resultBuilder.Insert(11, keyDelimiter, 0, 1); resultBuilder.Insert(17, keyDelimiter, 0, 1);
resultBuilder.Insert(17, keyDelimiter, 0, 1); resultBuilder.Insert(23, keyDelimiter, 0, 1);
resultBuilder.Insert(23, keyDelimiter, 0, 1); return resultBuilder.ToString();
return resultBuilder.ToString(); }
}
/// <summary>Mangles a bit array</summary>
/// <summary>Mangles a bit array</summary> /// <param name="bits">Bit array that will be mangled</param>
/// <param name="bits">Bit array that will be mangled</param> private static void mangle(BitArray bits) {
private static void mangle(BitArray bits) { BitArray temp = new BitArray(bits);
BitArray temp = new BitArray(bits);
for(int i = 0; i < temp.Length; ++i) {
for(int i = 0; i < temp.Length; ++i) { bits[i] = temp[shuffle[i]];
bits[i] = temp[shuffle[i]];
if((i & 1) != 0)
if((i & 1) != 0) bits[i] = !bits[i];
bits[i] = !bits[i]; }
} }
}
/// <summary>Unmangles a bit array</summary>
/// <summary>Unmangles a bit array</summary> /// <param name="bits">Bit array that will be unmangled</param>
/// <param name="bits">Bit array that will be unmangled</param> private static void unmangle(BitArray bits) {
private static void unmangle(BitArray bits) { BitArray temp = new BitArray(bits);
BitArray temp = new BitArray(bits);
for(int i = 0; i < temp.Length; ++i) {
for(int i = 0; i < temp.Length; ++i) { if((i & 1) != 0)
if((i & 1) != 0) temp[i] = !temp[i];
temp[i] = !temp[i];
bits[shuffle[i]] = temp[i];
bits[shuffle[i]] = temp[i]; }
} }
}
/// <summary>Character used to delimit each 5 digit group in a license key</summary>
/// <summary>Character used to delimit each 5 digit group in a license key</summary> /// <remarks>
/// <remarks> /// Required to be a char array because the .NET Compact Framework only provides
/// Required to be a char array because the .NET Compact Framework only provides /// an overload for char[] in the StringBuilder.Insert() method.
/// an overload for char[] in the StringBuilder.Insert() method. /// </remarks>
/// </remarks> private static char[] keyDelimiter = new char[] { '-' };
private static char[] keyDelimiter = new char[] { '-' };
/// <summary>Table with the individual characters in a key</summary>
/// <summary>Table with the individual characters in a key</summary> private static readonly string codeTable =
private static readonly string codeTable = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
/// <summary>Helper array containing the precalculated powers of two</summary>
/// <summary>Helper array containing the precalculated powers of two</summary> private static readonly uint[,] powersOfTwo = new uint[32, 2] {
private static readonly uint[,] powersOfTwo = new uint[32, 2] { { 0, 1 }, { 0, 2 }, { 0, 4 }, { 0, 8 },
{ 0, 1 }, { 0, 2 }, { 0, 4 }, { 0, 8 }, { 0, 16 }, { 0, 32 }, { 0, 64 }, { 0, 128 },
{ 0, 16 }, { 0, 32 }, { 0, 64 }, { 0, 128 }, { 0, 256 }, { 0, 512 }, { 0, 1024 }, { 0, 2048 },
{ 0, 256 }, { 0, 512 }, { 0, 1024 }, { 0, 2048 }, { 0, 4096 }, { 0, 8192 }, { 0, 16384 }, { 0, 32768 },
{ 0, 4096 }, { 0, 8192 }, { 0, 16384 }, { 0, 32768 }, { 0, 65536 }, { 0, 131072 }, { 0, 262144 }, { 0, 524288 },
{ 0, 65536 }, { 0, 131072 }, { 0, 262144 }, { 0, 524288 }, { 0, 1048576 }, { 0, 2097152 }, { 0, 4194304 }, { 0, 8388608 },
{ 0, 1048576 }, { 0, 2097152 }, { 0, 4194304 }, { 0, 8388608 }, { 0, 16777216 }, { 0, 33554432 }, { 0, 67108864 }, { 0, 134217728 },
{ 0, 16777216 }, { 0, 33554432 }, { 0, 67108864 }, { 0, 134217728 }, { 0, 268435456 }, { 0, 536870912 }, { 0, 1073741824 }, { 0, 2147483648 }
{ 0, 268435456 }, { 0, 536870912 }, { 0, 1073741824 }, { 0, 2147483648 } };
};
/// <summary>Index list for rotating the bit arrays</summary>
/// <summary>Index list for rotating the bit arrays</summary> private static readonly byte[] shuffle = new byte[128] {
private static readonly byte[] shuffle = new byte[128] { 99, 47, 19, 104, 40, 71, 35, 82, 88, 2, 117, 118, 105, 42, 84, 48,
99, 47, 19, 104, 40, 71, 35, 82, 88, 2, 117, 118, 105, 42, 84, 48, 33, 54, 43, 27, 78, 53, 61, 50, 109, 87, 69, 66, 25, 76, 45, 14,
33, 54, 43, 27, 78, 53, 61, 50, 109, 87, 69, 66, 25, 76, 45, 14, 92, 16, 123, 98, 95, 37, 34, 8, 1, 49, 20, 90, 15, 97, 22, 108,
92, 16, 123, 98, 95, 37, 34, 8, 1, 49, 20, 90, 15, 97, 22, 108, 5, 32, 120, 106, 122, 70, 67, 55, 46, 89, 100, 0, 26, 94, 121, 7,
5, 32, 120, 106, 122, 70, 67, 55, 46, 89, 100, 0, 26, 94, 121, 7, 56, 59, 103, 79, 107, 36, 125, 119, 126, 44, 18, 93, 75, 116, 31, 9,
56, 59, 103, 79, 107, 36, 125, 119, 126, 44, 18, 93, 75, 116, 31, 9, 73, 113, 3, 41, 124, 60, 77, 91, 28, 114, 65, 12, 39, 127, 72, 17,
73, 113, 3, 41, 124, 60, 77, 91, 28, 114, 65, 12, 39, 127, 72, 17, 112, 21, 96, 111, 83, 101, 85, 80, 23, 68, 57, 13, 4, 10, 51, 63,
112, 21, 96, 111, 83, 101, 85, 80, 23, 68, 57, 13, 4, 10, 51, 63, 11, 30, 115, 102, 86, 81, 74, 110, 62, 38, 29, 64, 52, 6, 24, 58
11, 30, 115, 102, 86, 81, 74, 110, 62, 38, 29, 64, 52, 6, 24, 58 };
};
/// <summary>GUID in which the key is stored</summary>
/// <summary>GUID in which the key is stored</summary> private Guid guid;
private Guid guid;
}
}
} // namespace Nuclex.Support.Licensing
} // namespace Nuclex.Support.Licensing

View File

@ -1,172 +1,171 @@
#region CPL License #region Apache License 2.0
/* /*
Nuclex Framework Nuclex .NET Framework
Copyright (C) 2002-2017 Nuclex Development Labs Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
This library is free software; you can redistribute it and/or Licensed under the Apache License, Version 2.0 (the "License");
modify it under the terms of the IBM Common Public License as you may not use this file except in compliance with the License.
published by the IBM Corporation; either version 1.0 of the You may obtain a copy of the License at
License, or (at your option) any later version.
http://www.apache.org/licenses/LICENSE-2.0
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of Unless required by applicable law or agreed to in writing, software
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the distributed under the License is distributed on an "AS IS" BASIS,
IBM Common Public License for more details. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
You should have received a copy of the IBM Common Public limitations under the License.
License along with this library */
*/ #endregion // Apache License 2.0
#endregion
#if UNITTEST
#if UNITTEST
using System;
using System; using System.ComponentModel;
using System.ComponentModel;
using NUnit.Framework;
using NUnit.Framework; using NMock;
using NMock;
namespace Nuclex.Support {
namespace Nuclex.Support {
/// <summary>Unit tests for observable class</summary>
/// <summary>Unit tests for observable class</summary> [TestFixture]
[TestFixture] internal class ObservableTest {
internal class ObservableTest {
#region class TestObservable
#region class TestObservable
/// <summary>Example class on which unit test generates change notifications</summary>
/// <summary>Example class on which unit test generates change notifications</summary> public class TestObservable : Observable {
public class TestObservable : Observable {
/// <summary>Triggers the property changed event for the specified property</summary>
/// <summary>Triggers the property changed event for the specified property</summary> /// <param name="propertyName">
/// <param name="propertyName"> /// Name of the property that will be reported as changed
/// Name of the property that will be reported as changed /// </param>
/// </param> public void FirePropertyChanged(string propertyName) {
public void FirePropertyChanged(string propertyName) { OnPropertyChanged(propertyName);
OnPropertyChanged(propertyName); }
}
/// <summary>Fires the property changed event for the 'SomePropety' property</summary>
/// <summary>Fires the property changed event for the 'SomePropety' property</summary> public void FireSomePropertyChanged() {
public void FireSomePropertyChanged() { #pragma warning disable 0618
#pragma warning disable 0618 OnPropertyChanged(() => SomeProperty);
OnPropertyChanged(() => SomeProperty); #pragma warning restore 0618
#pragma warning restore 0618 }
}
/// <summary>Example property that will be reported to have changed</summary>
/// <summary>Example property that will be reported to have changed</summary> public int SomeProperty { get; set; }
public int SomeProperty { get; set; }
}
}
#endregion // class TestObservable
#endregion // class TestObservable
#region class MockedSubscriber
#region class MockedSubscriber
/// <summary>Mocked change notification subscriber</summary>
/// <summary>Mocked change notification subscriber</summary> public class MockedSubscriber {
public class MockedSubscriber {
/// <summary>Called when the value of a property has changed</summary>
/// <summary>Called when the value of a property has changed</summary> /// <param name="sender">Object of which a property has changed</param>
/// <param name="sender">Object of which a property has changed</param> /// <param name="arguments">Contains the name of the changed property</param>
/// <param name="arguments">Contains the name of the changed property</param> public void PropertyChanged(object sender, PropertyChangedEventArgs arguments) {
public void PropertyChanged(object sender, PropertyChangedEventArgs arguments) { this.wasNotified = true;
this.wasNotified = true; this.changedPropertyName = arguments.PropertyName;
this.changedPropertyName = arguments.PropertyName; }
}
/// <summary>Whether the subscriber was notified of a property change</summary>
/// <summary>Whether the subscriber was notified of a property change</summary> public bool WasNotified {
public bool WasNotified { get { return this.wasNotified; }
get { return this.wasNotified; } }
}
/// <summary>
/// <summary> /// Checks whether a change notification for the specified property was received
/// Checks whether a change notification for the specified property was received /// </summary>
/// </summary> /// <param name="propertyName">Name of the property that will be checked for</param>
/// <param name="propertyName">Name of the property that will be checked for</param> /// <returns>
/// <returns> /// True if a change notification for the specified property was received
/// True if a change notification for the specified property was received /// </returns>
/// </returns> public bool WasNotifiedOfChangeTo(string propertyName) {
public bool WasNotifiedOfChangeTo(string propertyName) { if(!this.wasNotified) {
if(!this.wasNotified) { return false;
return false; }
}
if(string.IsNullOrEmpty(propertyName)) {
if(string.IsNullOrEmpty(propertyName)) { return string.IsNullOrEmpty(this.changedPropertyName);
return string.IsNullOrEmpty(this.changedPropertyName); }
}
return (propertyName == this.changedPropertyName);
return (propertyName == this.changedPropertyName); }
}
/// <summary>Whether a change notification was received</summary>
/// <summary>Whether a change notification was received</summary> private bool wasNotified;
private bool wasNotified; /// <summary>Name of the property for which a change notification was received</summary>
/// <summary>Name of the property for which a change notification was received</summary> private string changedPropertyName;
private string changedPropertyName;
}
}
#endregion // class MockedSubscriber
#endregion // class MockedSubscriber
/// <summary>Called before each unit test is run</summary>
/// <summary>Called before each unit test is run</summary> [SetUp]
[SetUp] public void Setup() {
public void Setup() { this.testObservable = new TestObservable();
this.testObservable = new TestObservable(); this.subscriber = new MockedSubscriber();
this.subscriber = new MockedSubscriber();
this.testObservable.PropertyChanged += this.subscriber.PropertyChanged;
this.testObservable.PropertyChanged += this.subscriber.PropertyChanged; }
}
/// <summary>
/// <summary> /// Verifies that the name of the changed property can be specified manually
/// Verifies that the name of the changed property can be specified manually /// when triggering the PropertyChanged event
/// when triggering the PropertyChanged event /// </summary>
/// </summary> [Test]
[Test] public void PropertyNameCanBeSpecifiedManually() {
public void PropertyNameCanBeSpecifiedManually() { this.testObservable.FirePropertyChanged("SomeProperty");
this.testObservable.FirePropertyChanged("SomeProperty"); Assert.IsTrue(this.subscriber.WasNotifiedOfChangeTo("SomeProperty"));
Assert.IsTrue(this.subscriber.WasNotifiedOfChangeTo("SomeProperty")); }
}
#if DEBUG // The check is conditionally performed only in debug mode
#if DEBUG // The check is conditionally performed only in debug mode /// <summary>
/// <summary> /// Verifies that specifying the name of a property that doesn't exist
/// Verifies that specifying the name of a property that doesn't exist /// causes an ArgumentException to be thrown
/// causes an ArgumentException to be thrown /// </summary>
/// </summary> [Test]
[Test] public void SpecifyingInvalidPropertyNameThrowsArgumentException() {
public void SpecifyingInvalidPropertyNameThrowsArgumentException() { Assert.Throws<ArgumentException>(
Assert.Throws<ArgumentException>( delegate() { this.testObservable.FirePropertyChanged("DoesntExist"); }
delegate() { this.testObservable.FirePropertyChanged("DoesntExist"); } );
); }
} #endif
#endif
/// <summary>
/// <summary> /// Verifies that the observable is capable of deducing the name of the property
/// Verifies that the observable is capable of deducing the name of the property /// from a lambda expression
/// from a lambda expression /// </summary>
/// </summary> [Test]
[Test] public void PropertyNameCanBeDeducedFromLambdaExpression() {
public void PropertyNameCanBeDeducedFromLambdaExpression() { this.testObservable.FireSomePropertyChanged();
this.testObservable.FireSomePropertyChanged(); Assert.IsTrue(this.subscriber.WasNotifiedOfChangeTo("SomeProperty"));
Assert.IsTrue(this.subscriber.WasNotifiedOfChangeTo("SomeProperty")); }
}
/// <summary>
/// <summary> /// Verifies that change notifications for all properties of a type can
/// Verifies that change notifications for all properties of a type can /// be generated
/// be generated /// </summary>
/// </summary> [Test]
[Test] public void WildcardChangeNotificationsCanBeSent() {
public void WildcardChangeNotificationsCanBeSent() { this.testObservable.FirePropertyChanged(string.Empty);
this.testObservable.FirePropertyChanged(string.Empty); Assert.IsTrue(this.subscriber.WasNotifiedOfChangeTo(null));
Assert.IsTrue(this.subscriber.WasNotifiedOfChangeTo(null));
this.testObservable.FirePropertyChanged(null);
this.testObservable.FirePropertyChanged(null); Assert.IsTrue(this.subscriber.WasNotifiedOfChangeTo(string.Empty));
Assert.IsTrue(this.subscriber.WasNotifiedOfChangeTo(string.Empty)); }
}
/// <summary>Observable object being tested</summary>
/// <summary>Observable object being tested</summary> private TestObservable testObservable;
private TestObservable testObservable; /// <summary>Subscriber to the observable object being tested</summary>
/// <summary>Subscriber to the observable object being tested</summary> private MockedSubscriber subscriber;
private MockedSubscriber subscriber;
}
}
} // namespace Nuclex.Support
} // namespace Nuclex.Support
#endif // UNITTEST
#endif // UNITTEST

Some files were not shown because too many files have changed in this diff Show More