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" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- 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.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
<!-- 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.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>

View File

@ -351,11 +351,11 @@
<Content Include="Documents\Request Framework.txt" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- 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.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
<!-- 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.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>

View File

@ -1,37 +1,56 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("Nuclex.Support")]
[assembly: AssemblyProduct("Nuclex.Support")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyCompany("Nuclex Development Labs")]
[assembly: AssemblyCopyright("Copyright © Nuclex Development Labs 2008-2017")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// 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
// 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")]
#region Apache License 2.0
/*
Nuclex .NET Framework
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
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 // Apache License 2.0
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("Nuclex.Support")]
[assembly: AssemblyProduct("Nuclex.Support")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyCompany("Nuclex Development Labs")]
[assembly: AssemblyCopyright("Copyright © Markus Ewald / Nuclex Development Labs 2002-2024")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// 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
// 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
Copyright (C) 2002-2017 Nuclex Development Labs
Nuclex .NET Framework
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
This library is free software; you can redistribute it and/or
modify it under the terms of the IBM Common Public License as
published by the IBM Corporation; either version 1.0 of the
License, or (at your option) any later version.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
IBM Common Public License for more details.
http://www.apache.org/licenses/LICENSE-2.0
You should have received a copy of the IBM Common Public
License along with this library
Unless required by applicable law or agreed to in writing, software
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;

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
Copyright (C) 2002-2017 Nuclex Development Labs
Nuclex .NET Framework
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
This library is free software; you can redistribute it and/or
modify it under the terms of the IBM Common Public License as
published by the IBM Corporation; either version 1.0 of the
License, or (at your option) any later version.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
IBM Common Public License for more details.
http://www.apache.org/licenses/LICENSE-2.0
You should have received a copy of the IBM Common Public
License along with this library
Unless required by applicable law or agreed to in writing, software
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;

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
Copyright (C) 2002-2017 Nuclex Development Labs
Nuclex .NET Framework
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
This library is free software; you can redistribute it and/or
modify it under the terms of the IBM Common Public License as
published by the IBM Corporation; either version 1.0 of the
License, or (at your option) any later version.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
IBM Common Public License for more details.
http://www.apache.org/licenses/LICENSE-2.0
You should have received a copy of the IBM Common Public
License along with this library
Unless required by applicable law or agreed to in writing, software
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.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
Copyright (C) 2002-2017 Nuclex Development Labs
Nuclex .NET Framework
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
This library is free software; you can redistribute it and/or
modify it under the terms of the IBM Common Public License as
published by the IBM Corporation; either version 1.0 of the
License, or (at your option) any later version.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
IBM Common Public License for more details.
http://www.apache.org/licenses/LICENSE-2.0
You should have received a copy of the IBM Common Public
License along with this library
Unless required by applicable law or agreed to in writing, software
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.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
Copyright (C) 2002-2017 Nuclex Development Labs
Nuclex .NET Framework
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
This library is free software; you can redistribute it and/or
modify it under the terms of the IBM Common Public License as
published by the IBM Corporation; either version 1.0 of the
License, or (at your option) any later version.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
IBM Common Public License for more details.
http://www.apache.org/licenses/LICENSE-2.0
You should have received a copy of the IBM Common Public
License along with this library
Unless required by applicable law or agreed to in writing, software
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;

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
Copyright (C) 2002-2017 Nuclex Development Labs
Nuclex .NET Framework
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
This library is free software; you can redistribute it and/or
modify it under the terms of the IBM Common Public License as
published by the IBM Corporation; either version 1.0 of the
License, or (at your option) any later version.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
IBM Common Public License for more details.
http://www.apache.org/licenses/LICENSE-2.0
You should have received a copy of the IBM Common Public
License along with this library
Unless required by applicable law or agreed to in writing, software
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;

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
Copyright (C) 2002-2017 Nuclex Development Labs
Nuclex .NET Framework
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
This library is free software; you can redistribute it and/or
modify it under the terms of the IBM Common Public License as
published by the IBM Corporation; either version 1.0 of the
License, or (at your option) any later version.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
IBM Common Public License for more details.
http://www.apache.org/licenses/LICENSE-2.0
You should have received a copy of the IBM Common Public
License along with this library
Unless required by applicable law or agreed to in writing, software
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;

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

@ -1,459 +1,458 @@
#region CPL License
/*
Nuclex Framework
Copyright (C) 2002-2017 Nuclex Development Labs
This library is free software; you can redistribute it and/or
modify it under the terms of the IBM Common Public License as
published by the IBM Corporation; either version 1.0 of the
License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
IBM Common Public License for more details.
You should have received a copy of the IBM Common Public
License along with this library
*/
#endregion
using System;
using System.Diagnostics;
using System.IO;
namespace Nuclex.Support.IO {
/// <summary>Chains a series of independent streams into a single stream</summary>
/// <remarks>
/// <para>
/// 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.
/// 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
/// read a file that was split into several parts as if it was a single file.
/// </para>
/// <para>
/// 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
/// 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,
/// but reducing the length might invalidate the stream chainer's file pointer,
/// resulting in an IOException when Read() or Write() is next called.
/// </para>
/// </remarks>
public class ChainStream : Stream {
/// <summary>Initializes a new stream chainer</summary>
/// <param name="streams">Array of streams that will be chained together</param>
public ChainStream(params Stream[] streams) {
this.streams = (Stream[])streams.Clone();
determineCapabilities();
}
/// <summary>Whether data can be read from the stream</summary>
public override bool CanRead {
get { return this.allStreamsCanRead; }
}
/// <summary>Whether the stream supports seeking</summary>
public override bool CanSeek {
get { return this.allStreamsCanSeek; }
}
/// <summary>Whether data can be written into the stream</summary>
public override bool CanWrite {
get { return this.allStreamsCanWrite; }
}
/// <summary>
/// Clears all buffers for this stream and causes any buffered data to be written
/// to the underlying device.
/// </summary>
public override void Flush() {
for(int index = 0; index < this.streams.Length; ++index) {
this.streams[index].Flush();
}
}
/// <summary>Length of the stream in bytes</summary>
/// <exception cref="NotSupportedException">
/// At least one of the chained streams does not support seeking
/// </exception>
public override long Length {
get {
if(!this.allStreamsCanSeek) {
throw makeSeekNotSupportedException("determine length");
}
// Sum up the length of all chained streams
long length = 0;
for(int index = 0; index < this.streams.Length; ++index) {
length += this.streams[index].Length;
}
return length;
}
}
/// <summary>Absolute position of the file pointer within the stream</summary>
/// <exception cref="NotSupportedException">
/// At least one of the chained streams does not support seeking
/// </exception>
public override long Position {
get {
if(!this.allStreamsCanSeek) {
throw makeSeekNotSupportedException("seek");
}
return this.position;
}
set { moveFilePointer(value); }
}
/// <summary>
/// Reads a sequence of bytes from the stream and advances the position of
/// the file pointer by the number of bytes read.
/// </summary>
/// <param name="buffer">Buffer that will receive the data read from the stream</param>
/// <param name="offset">
/// Offset in the buffer at which the stream will place the data read
/// </param>
/// <param name="count">Maximum number of bytes that will be read</param>
/// <returns>
/// The number of bytes that were actually read from the stream and written into
/// the provided buffer
/// </returns>
/// <exception cref="NotSupportedException">
/// The chained stream at the current position does not support reading
/// </exception>
public override int Read(byte[] buffer, int offset, int count) {
if(!this.allStreamsCanRead) {
throw new NotSupportedException(
"Can't read: at least one of the chained streams doesn't support reading"
);
}
int totalBytesRead = 0;
int lastStreamIndex = this.streams.Length - 1;
if(this.allStreamsCanSeek) {
// Find out from which stream and at which position we need to begin reading
int streamIndex;
long streamOffset;
findStreamIndexAndOffset(this.position, out streamIndex, out streamOffset);
// 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
// until we either have enough data or run out of streams.
while(count > 0) {
Stream currentStream = this.streams[streamIndex];
// 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
// to be read
long maximumBytes = Math.Min(count, currentStream.Length - streamOffset);
currentStream.Position = streamOffset;
int bytesRead = currentStream.Read(buffer, offset, (int)maximumBytes);
// Accumulate the total number of bytes we read for the return value
totalBytesRead += bytesRead;
// 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.
if((bytesRead < maximumBytes) || (streamIndex == lastStreamIndex)) {
break;
}
// Move on to the next stream in the chain
++streamIndex;
streamOffset = 0;
count -= bytesRead;
offset += bytesRead;
}
this.position += totalBytesRead;
} else {
// 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
// no more streams left to read from
while(this.activeReadStreamIndex <= lastStreamIndex) {
// 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
// the stream was reached
Stream activeStream = this.streams[this.activeReadStreamIndex];
if(activeStream.CanSeek) {
activeStream.Position = this.activeReadStreamPosition;
}
totalBytesRead = activeStream.Read(buffer, offset, count);
// If we got any data, we're done, exit the loop
if(totalBytesRead != 0) {
break;
} else { // Otherwise, go to the next stream in the chain
this.activeReadStreamPosition = 0;
++this.activeReadStreamIndex;
}
}
this.activeReadStreamPosition += totalBytesRead;
}
return totalBytesRead;
}
/// <summary>Changes the position of the file pointer</summary>
/// <param name="offset">
/// Offset to move the file pointer by, relative to the position indicated by
/// the <paramref name="origin" /> parameter.
/// </param>
/// <param name="origin">
/// Reference point relative to which the file pointer is placed
/// </param>
/// <returns>The new absolute position within the stream</returns>
public override long Seek(long offset, SeekOrigin origin) {
switch(origin) {
case SeekOrigin.Begin: {
return Position = offset;
}
case SeekOrigin.Current: {
return Position += offset;
}
case SeekOrigin.End: {
return Position = (Length + offset);
}
default: {
throw new ArgumentException("Invalid seek origin", "origin");
}
}
}
/// <summary>Changes the length of the stream</summary>
/// <param name="value">New length the stream shall have</param>
/// <exception cref="NotSupportedException">
/// Always, the stream chainer does not support the SetLength() operation
/// </exception>
public override void SetLength(long value) {
throw new NotSupportedException("Resizing chained streams is not supported");
}
/// <summary>
/// Writes a sequence of bytes to the stream and advances the position of
/// the file pointer by the number of bytes written.
/// </summary>
/// <param name="buffer">
/// Buffer containing the data that will be written to the stream
/// </param>
/// <param name="offset">
/// Offset in the buffer at which the data to be written starts
/// </param>
/// <param name="count">Number of bytes that will be written into the stream</param>
/// <remarks>
/// 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
/// 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
/// in the next stream. On the last stream, writing more data into the stream
/// that it current size allows will enlarge the stream.
/// </remarks>
public override void Write(byte[] buffer, int offset, int count) {
if(!this.allStreamsCanWrite) {
throw new NotSupportedException(
"Can't write: at least one of the chained streams doesn't support writing"
);
}
int remaining = count;
// If seeking is supported, we can write into the mid of the stream,
// if the user so desires
if(this.allStreamsCanSeek) {
// Find out in which stream and at which position we need to begin writing
int streamIndex;
long streamOffset;
findStreamIndexAndOffset(this.position, out streamIndex, out streamOffset);
// 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.
int lastStreamIndex = this.streams.Length - 1;
while(remaining > 0) {
Stream currentStream = this.streams[streamIndex];
// 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.
if(streamIndex == lastStreamIndex) {
// Write all remaining data into the last stream
currentStream.Position = streamOffset;
currentStream.Write(buffer, offset, remaining);
remaining = 0;
} 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
// enlarging it (if seeking is supported, so is the Length property)
long currentStreamRemaining = currentStream.Length - streamOffset;
int bytesToWrite = (int)Math.Min((long)remaining, currentStreamRemaining);
// Write all data that can fit into the current stream
currentStream.Position = streamOffset;
currentStream.Write(buffer, offset, bytesToWrite);
// Adjust the offsets and count for the next stream
offset += bytesToWrite;
remaining -= bytesToWrite;
streamOffset = 0;
++streamIndex;
}
}
} else { // Seeking not supported, append everything to the last stream
Stream lastStream = this.streams[this.streams.Length - 1];
if(lastStream.CanSeek) {
lastStream.Seek(0, SeekOrigin.End);
}
lastStream.Write(buffer, offset, remaining);
}
this.position += count;
}
/// <summary>Streams being combined by the stream chainer</summary>
public Stream[] ChainedStreams {
get { return this.streams; }
}
/// <summary>Moves the file pointer</summary>
/// <param name="position">New position the file pointer will be moved to</param>
private void moveFilePointer(long position) {
if(!this.allStreamsCanSeek) {
throw makeSeekNotSupportedException("seek");
}
// Seemingly, it is okay to move the file pointer beyond the end of
// the stream until you try to Read() or Write()
this.position = position;
}
/// <summary>
/// Finds the stream index and local offset for an absolute position within
/// the combined streams.
/// </summary>
/// <param name="overallPosition">Absolute position within the combined streams</param>
/// <param name="streamIndex">
/// Index of the stream the overall position falls into
/// </param>
/// <param name="streamPosition">
/// Local position within the stream indicated by <paramref name="streamIndex" />
/// </param>
private void findStreamIndexAndOffset(
long overallPosition, out int streamIndex, out long streamPosition
) {
Debug.Assert(
this.allStreamsCanSeek, "Call to findStreamIndexAndOffset() but no seek support"
);
// In case the position is beyond the stream's end, this is what we will
// return to the caller
streamIndex = (this.streams.Length - 1);
// Search until we have found the stream the position must lie in
for(int index = 0; index < this.streams.Length; ++index) {
long streamLength = this.streams[index].Length;
if(overallPosition < streamLength) {
streamIndex = index;
break;
}
overallPosition -= streamLength;
}
// 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.
streamPosition = overallPosition;
}
/// <summary>Determines the capabilities of the chained streams</summary>
/// <remarks>
/// <para>
/// 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,
/// that could seek within the beginning of the stream until the first chained
/// stream with no seek capability was encountered and so on.
/// </para>
/// <para>
/// However, the interface of the Stream class requires us to make a definitive
/// 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
/// 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
/// will report the feature as unsupported.
/// </para>
/// </remarks>
private void determineCapabilities() {
this.allStreamsCanSeek = true;
this.allStreamsCanRead = true;
this.allStreamsCanWrite = true;
for(int index = 0; index < this.streams.Length; ++index) {
this.allStreamsCanSeek &= this.streams[index].CanSeek;
this.allStreamsCanRead &= this.streams[index].CanRead;
this.allStreamsCanWrite &= this.streams[index].CanWrite;
}
}
/// <summary>
/// Constructs a NotSupportException for an error caused by one of the chained
/// streams having no seek support
/// </summary>
/// <param name="action">Action that was tried to perform</param>
/// <returns>The newly constructed NotSupportedException</returns>
private static NotSupportedException makeSeekNotSupportedException(string action) {
return new NotSupportedException(
string.Format(
"Can't {0}: at least one of the chained streams does not support seeking",
action
)
);
}
/// <summary>Streams that have been chained together</summary>
private Stream[] streams;
/// <summary>Current position of the overall file pointer</summary>
private long position;
/// <summary>Stream we're currently reading from if seeking is not supported</summary>
/// <remarks>
/// If seeking is not supported, the stream chainer will read from each stream
/// until the end was reached
/// sequentially
/// </remarks>
private int activeReadStreamIndex;
/// <summary>Position in the current read stream if seeking is not supported</summary>
/// <remarks>
/// 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,
/// 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
/// data that should have been read in the following read attempt.
/// </remarks>
private long activeReadStreamPosition;
/// <summary>Whether all of the chained streams support seeking</summary>
private bool allStreamsCanSeek;
/// <summary>Whether all of the chained streams support reading</summary>
private bool allStreamsCanRead;
/// <summary>Whether all of the chained streams support writing</summary>
private bool allStreamsCanWrite;
}
} // namespace Nuclex.Support.IO
#region Apache License 2.0
/*
Nuclex .NET Framework
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
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 // Apache License 2.0
using System;
using System.Diagnostics;
using System.IO;
namespace Nuclex.Support.IO {
/// <summary>Chains a series of independent streams into a single stream</summary>
/// <remarks>
/// <para>
/// 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.
/// 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
/// read a file that was split into several parts as if it was a single file.
/// </para>
/// <para>
/// 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
/// 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,
/// but reducing the length might invalidate the stream chainer's file pointer,
/// resulting in an IOException when Read() or Write() is next called.
/// </para>
/// </remarks>
public class ChainStream : Stream {
/// <summary>Initializes a new stream chainer</summary>
/// <param name="streams">Array of streams that will be chained together</param>
public ChainStream(params Stream[] streams) {
this.streams = (Stream[])streams.Clone();
determineCapabilities();
}
/// <summary>Whether data can be read from the stream</summary>
public override bool CanRead {
get { return this.allStreamsCanRead; }
}
/// <summary>Whether the stream supports seeking</summary>
public override bool CanSeek {
get { return this.allStreamsCanSeek; }
}
/// <summary>Whether data can be written into the stream</summary>
public override bool CanWrite {
get { return this.allStreamsCanWrite; }
}
/// <summary>
/// Clears all buffers for this stream and causes any buffered data to be written
/// to the underlying device.
/// </summary>
public override void Flush() {
for(int index = 0; index < this.streams.Length; ++index) {
this.streams[index].Flush();
}
}
/// <summary>Length of the stream in bytes</summary>
/// <exception cref="NotSupportedException">
/// At least one of the chained streams does not support seeking
/// </exception>
public override long Length {
get {
if(!this.allStreamsCanSeek) {
throw makeSeekNotSupportedException("determine length");
}
// Sum up the length of all chained streams
long length = 0;
for(int index = 0; index < this.streams.Length; ++index) {
length += this.streams[index].Length;
}
return length;
}
}
/// <summary>Absolute position of the file pointer within the stream</summary>
/// <exception cref="NotSupportedException">
/// At least one of the chained streams does not support seeking
/// </exception>
public override long Position {
get {
if(!this.allStreamsCanSeek) {
throw makeSeekNotSupportedException("seek");
}
return this.position;
}
set { moveFilePointer(value); }
}
/// <summary>
/// Reads a sequence of bytes from the stream and advances the position of
/// the file pointer by the number of bytes read.
/// </summary>
/// <param name="buffer">Buffer that will receive the data read from the stream</param>
/// <param name="offset">
/// Offset in the buffer at which the stream will place the data read
/// </param>
/// <param name="count">Maximum number of bytes that will be read</param>
/// <returns>
/// The number of bytes that were actually read from the stream and written into
/// the provided buffer
/// </returns>
/// <exception cref="NotSupportedException">
/// The chained stream at the current position does not support reading
/// </exception>
public override int Read(byte[] buffer, int offset, int count) {
if(!this.allStreamsCanRead) {
throw new NotSupportedException(
"Can't read: at least one of the chained streams doesn't support reading"
);
}
int totalBytesRead = 0;
int lastStreamIndex = this.streams.Length - 1;
if(this.allStreamsCanSeek) {
// Find out from which stream and at which position we need to begin reading
int streamIndex;
long streamOffset;
findStreamIndexAndOffset(this.position, out streamIndex, out streamOffset);
// 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
// until we either have enough data or run out of streams.
while(count > 0) {
Stream currentStream = this.streams[streamIndex];
// 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
// to be read
long maximumBytes = Math.Min(count, currentStream.Length - streamOffset);
currentStream.Position = streamOffset;
int bytesRead = currentStream.Read(buffer, offset, (int)maximumBytes);
// Accumulate the total number of bytes we read for the return value
totalBytesRead += bytesRead;
// 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.
if((bytesRead < maximumBytes) || (streamIndex == lastStreamIndex)) {
break;
}
// Move on to the next stream in the chain
++streamIndex;
streamOffset = 0;
count -= bytesRead;
offset += bytesRead;
}
this.position += totalBytesRead;
} else {
// 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
// no more streams left to read from
while(this.activeReadStreamIndex <= lastStreamIndex) {
// 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
// the stream was reached
Stream activeStream = this.streams[this.activeReadStreamIndex];
if(activeStream.CanSeek) {
activeStream.Position = this.activeReadStreamPosition;
}
totalBytesRead = activeStream.Read(buffer, offset, count);
// If we got any data, we're done, exit the loop
if(totalBytesRead != 0) {
break;
} else { // Otherwise, go to the next stream in the chain
this.activeReadStreamPosition = 0;
++this.activeReadStreamIndex;
}
}
this.activeReadStreamPosition += totalBytesRead;
}
return totalBytesRead;
}
/// <summary>Changes the position of the file pointer</summary>
/// <param name="offset">
/// Offset to move the file pointer by, relative to the position indicated by
/// the <paramref name="origin" /> parameter.
/// </param>
/// <param name="origin">
/// Reference point relative to which the file pointer is placed
/// </param>
/// <returns>The new absolute position within the stream</returns>
public override long Seek(long offset, SeekOrigin origin) {
switch(origin) {
case SeekOrigin.Begin: {
return Position = offset;
}
case SeekOrigin.Current: {
return Position += offset;
}
case SeekOrigin.End: {
return Position = (Length + offset);
}
default: {
throw new ArgumentException("Invalid seek origin", "origin");
}
}
}
/// <summary>Changes the length of the stream</summary>
/// <param name="value">New length the stream shall have</param>
/// <exception cref="NotSupportedException">
/// Always, the stream chainer does not support the SetLength() operation
/// </exception>
public override void SetLength(long value) {
throw new NotSupportedException("Resizing chained streams is not supported");
}
/// <summary>
/// Writes a sequence of bytes to the stream and advances the position of
/// the file pointer by the number of bytes written.
/// </summary>
/// <param name="buffer">
/// Buffer containing the data that will be written to the stream
/// </param>
/// <param name="offset">
/// Offset in the buffer at which the data to be written starts
/// </param>
/// <param name="count">Number of bytes that will be written into the stream</param>
/// <remarks>
/// 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
/// 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
/// in the next stream. On the last stream, writing more data into the stream
/// that it current size allows will enlarge the stream.
/// </remarks>
public override void Write(byte[] buffer, int offset, int count) {
if(!this.allStreamsCanWrite) {
throw new NotSupportedException(
"Can't write: at least one of the chained streams doesn't support writing"
);
}
int remaining = count;
// If seeking is supported, we can write into the mid of the stream,
// if the user so desires
if(this.allStreamsCanSeek) {
// Find out in which stream and at which position we need to begin writing
int streamIndex;
long streamOffset;
findStreamIndexAndOffset(this.position, out streamIndex, out streamOffset);
// 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.
int lastStreamIndex = this.streams.Length - 1;
while(remaining > 0) {
Stream currentStream = this.streams[streamIndex];
// 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.
if(streamIndex == lastStreamIndex) {
// Write all remaining data into the last stream
currentStream.Position = streamOffset;
currentStream.Write(buffer, offset, remaining);
remaining = 0;
} 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
// enlarging it (if seeking is supported, so is the Length property)
long currentStreamRemaining = currentStream.Length - streamOffset;
int bytesToWrite = (int)Math.Min((long)remaining, currentStreamRemaining);
// Write all data that can fit into the current stream
currentStream.Position = streamOffset;
currentStream.Write(buffer, offset, bytesToWrite);
// Adjust the offsets and count for the next stream
offset += bytesToWrite;
remaining -= bytesToWrite;
streamOffset = 0;
++streamIndex;
}
}
} else { // Seeking not supported, append everything to the last stream
Stream lastStream = this.streams[this.streams.Length - 1];
if(lastStream.CanSeek) {
lastStream.Seek(0, SeekOrigin.End);
}
lastStream.Write(buffer, offset, remaining);
}
this.position += count;
}
/// <summary>Streams being combined by the stream chainer</summary>
public Stream[] ChainedStreams {
get { return this.streams; }
}
/// <summary>Moves the file pointer</summary>
/// <param name="position">New position the file pointer will be moved to</param>
private void moveFilePointer(long position) {
if(!this.allStreamsCanSeek) {
throw makeSeekNotSupportedException("seek");
}
// Seemingly, it is okay to move the file pointer beyond the end of
// the stream until you try to Read() or Write()
this.position = position;
}
/// <summary>
/// Finds the stream index and local offset for an absolute position within
/// the combined streams.
/// </summary>
/// <param name="overallPosition">Absolute position within the combined streams</param>
/// <param name="streamIndex">
/// Index of the stream the overall position falls into
/// </param>
/// <param name="streamPosition">
/// Local position within the stream indicated by <paramref name="streamIndex" />
/// </param>
private void findStreamIndexAndOffset(
long overallPosition, out int streamIndex, out long streamPosition
) {
Debug.Assert(
this.allStreamsCanSeek, "Call to findStreamIndexAndOffset() but no seek support"
);
// In case the position is beyond the stream's end, this is what we will
// return to the caller
streamIndex = (this.streams.Length - 1);
// Search until we have found the stream the position must lie in
for(int index = 0; index < this.streams.Length; ++index) {
long streamLength = this.streams[index].Length;
if(overallPosition < streamLength) {
streamIndex = index;
break;
}
overallPosition -= streamLength;
}
// 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.
streamPosition = overallPosition;
}
/// <summary>Determines the capabilities of the chained streams</summary>
/// <remarks>
/// <para>
/// 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,
/// that could seek within the beginning of the stream until the first chained
/// stream with no seek capability was encountered and so on.
/// </para>
/// <para>
/// However, the interface of the Stream class requires us to make a definitive
/// 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
/// 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
/// will report the feature as unsupported.
/// </para>
/// </remarks>
private void determineCapabilities() {
this.allStreamsCanSeek = true;
this.allStreamsCanRead = true;
this.allStreamsCanWrite = true;
for(int index = 0; index < this.streams.Length; ++index) {
this.allStreamsCanSeek &= this.streams[index].CanSeek;
this.allStreamsCanRead &= this.streams[index].CanRead;
this.allStreamsCanWrite &= this.streams[index].CanWrite;
}
}
/// <summary>
/// Constructs a NotSupportException for an error caused by one of the chained
/// streams having no seek support
/// </summary>
/// <param name="action">Action that was tried to perform</param>
/// <returns>The newly constructed NotSupportedException</returns>
private static NotSupportedException makeSeekNotSupportedException(string action) {
return new NotSupportedException(
string.Format(
"Can't {0}: at least one of the chained streams does not support seeking",
action
)
);
}
/// <summary>Streams that have been chained together</summary>
private Stream[] streams;
/// <summary>Current position of the overall file pointer</summary>
private long position;
/// <summary>Stream we're currently reading from if seeking is not supported</summary>
/// <remarks>
/// If seeking is not supported, the stream chainer will read from each stream
/// until the end was reached
/// sequentially
/// </remarks>
private int activeReadStreamIndex;
/// <summary>Position in the current read stream if seeking is not supported</summary>
/// <remarks>
/// 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,
/// 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
/// data that should have been read in the following read attempt.
/// </remarks>
private long activeReadStreamPosition;
/// <summary>Whether all of the chained streams support seeking</summary>
private bool allStreamsCanSeek;
/// <summary>Whether all of the chained streams support reading</summary>
private bool allStreamsCanRead;
/// <summary>Whether all of the chained streams support writing</summary>
private bool allStreamsCanWrite;
}
} // namespace Nuclex.Support.IO

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,257 +1,256 @@
#region CPL License
/*
Nuclex Framework
Copyright (C) 2002-2017 Nuclex Development Labs
This library is free software; you can redistribute it and/or
modify it under the terms of the IBM Common Public License as
published by the IBM Corporation; either version 1.0 of the
License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
IBM Common Public License for more details.
You should have received a copy of the IBM Common Public
License along with this library
*/
#endregion
using System;
using System.Collections;
using System.Text;
namespace Nuclex.Support.Licensing {
/// <summary>Typical license key with 5x5 alphanumerical characters</summary>
/// <remarks>
/// <para>
/// 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,
/// the textual representation of the license keys looks identical,
/// eg. <code>O809J-RN5TD-IM3CU-4IG1O-O90X9</code>.
/// </para>
/// <para>
/// 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.
/// The four integers can be modified directly, for example to store feature
/// lists, checksums or other data within the key.
/// </para>
/// </remarks>
public class LicenseKey {
/// <summary>Parses the license key contained in a string</summary>
/// <param name="key">String containing a license key that is to be parsed</param>
/// <returns>The license key parsed from provided string</returns>
/// <exception cref="ArgumentException">
/// When the provided string is not a license key
/// </exception>
public static LicenseKey Parse(string key) {
key = key.Replace(" ", string.Empty).Replace("-", string.Empty).ToUpper();
if(key.Length != 25)
throw new ArgumentException("This is not a license key");
BitArray bits = new BitArray(128);
uint sequence;
// Convert the first 4 sequences of 6 chars into 124 bits
for(int j = 0; j < 4; j++) {
sequence =
(uint)codeTable.IndexOf(key[j * 6 + 5]) * 60466176 +
(uint)codeTable.IndexOf(key[j * 6 + 4]) * 1679616 +
(uint)codeTable.IndexOf(key[j * 6 + 3]) * 46656 +
(uint)codeTable.IndexOf(key[j * 6 + 2]) * 1296 +
(uint)codeTable.IndexOf(key[j * 6 + 1]) * 36 +
(uint)codeTable.IndexOf(key[j * 6 + 0]);
for(int i = 0; i < 31; i++)
bits[j * 31 + i] = (sequence & powersOfTwo[i, 1]) != 0;
}
// Append the remaining character's 4 bits
sequence = (uint)codeTable.IndexOf(key[24]);
bits[124] = (sequence & powersOfTwo[4, 1]) != 0;
bits[125] = (sequence & powersOfTwo[3, 1]) != 0;
bits[126] = (sequence & powersOfTwo[2, 1]) != 0;
bits[127] = (sequence & powersOfTwo[1, 1]) != 0;
// Revert the mangling that was applied to the key when encoding...
unmangle(bits);
// ...and we've got our GUID back!
byte[] guidBytes = new byte[16];
bits.CopyTo(guidBytes, 0);
return new LicenseKey(new Guid(guidBytes));
}
/// <summary>Initializes a new, empty license key</summary>
public LicenseKey() : this(Guid.Empty) { }
/// <summary>Initializes the license key from a GUID</summary>
/// <param name="source">GUID that is used to create the license key</param>
public LicenseKey(Guid source) {
this.guid = source;
}
/// <summary>Accesses the four integer values within a license key</summary>
/// <exception cref="IndexOutOfRangeException">
/// When the index lies outside of the key's fields
/// </exception>
public int this[int index] {
get {
if((index < 0) || (index > 3))
throw new IndexOutOfRangeException("Index out of range");
return BitConverter.ToInt32(this.guid.ToByteArray(), index * 4);
}
set {
if((index < 0) || (index > 3))
throw new IndexOutOfRangeException("Index out of range");
// Convert the GUID into binary data so we can replace one of its values
byte[] guidBytes = this.guid.ToByteArray();
// Overwrite the section at the index specified by the user with the new value
Array.Copy(
BitConverter.GetBytes(value), 0, // source and start index
guidBytes, index * 4, // destination and start index
4 // length
);
// Replacement finished, now we can reconstruct our guid
this.guid = new Guid(guidBytes);
}
}
/// <summary>Converts the license key into a GUID</summary>
/// <returns>The GUID created from the license key</returns>
public Guid ToGuid() {
return this.guid;
}
/// <summary>Converts the license key into a byte array</summary>
/// <returns>A byte array containing the converted license key</returns>
public byte[] ToByteArray() {
return this.guid.ToByteArray();
}
/// <summary>Converts the license key to a string</summary>
/// <returns>A string containing the converted license key</returns>
public override string ToString() {
StringBuilder resultBuilder = new StringBuilder();
// Build a bit array from the input data
BitArray bits = new BitArray(this.guid.ToByteArray());
mangle(bits);
int sequence = 0;
// Build 4 sequences of 6 characters from the first 124 bits
for(int i = 0; i < 4; ++i) {
// We take the next 31 bits from the buffer
for(int j = 0; j < 31; ++j)
sequence |= (int)powersOfTwo[j, bits[i * 31 + j] ? 1 : 0];
// 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,
// which means we can fit 31 bits into every 6 alpha-numerical characters.
for(int j = 0; j < 6; ++j) {
resultBuilder.Append(codeTable[sequence % 36]);
sequence /= 36;
}
}
// Use the remaining 4 bits to build the final character
resultBuilder.Append(
codeTable[
(int)(
powersOfTwo[4, bits[124] ? 1 : 0] |
powersOfTwo[3, bits[125] ? 1 : 0] |
powersOfTwo[2, bits[126] ? 1 : 0] |
powersOfTwo[1, bits[127] ? 1 : 0] |
powersOfTwo[0, 1] // One bit remains unused :)
)
]
);
// Now build a nice, readable string from the decoded characters
resultBuilder.Insert(5, keyDelimiter, 0, 1);
resultBuilder.Insert(11, keyDelimiter, 0, 1);
resultBuilder.Insert(17, keyDelimiter, 0, 1);
resultBuilder.Insert(23, keyDelimiter, 0, 1);
return resultBuilder.ToString();
}
/// <summary>Mangles a bit array</summary>
/// <param name="bits">Bit array that will be mangled</param>
private static void mangle(BitArray bits) {
BitArray temp = new BitArray(bits);
for(int i = 0; i < temp.Length; ++i) {
bits[i] = temp[shuffle[i]];
if((i & 1) != 0)
bits[i] = !bits[i];
}
}
/// <summary>Unmangles a bit array</summary>
/// <param name="bits">Bit array that will be unmangled</param>
private static void unmangle(BitArray bits) {
BitArray temp = new BitArray(bits);
for(int i = 0; i < temp.Length; ++i) {
if((i & 1) != 0)
temp[i] = !temp[i];
bits[shuffle[i]] = temp[i];
}
}
/// <summary>Character used to delimit each 5 digit group in a license key</summary>
/// <remarks>
/// Required to be a char array because the .NET Compact Framework only provides
/// an overload for char[] in the StringBuilder.Insert() method.
/// </remarks>
private static char[] keyDelimiter = new char[] { '-' };
/// <summary>Table with the individual characters in a key</summary>
private static readonly string codeTable =
"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
/// <summary>Helper array containing the precalculated powers of two</summary>
private static readonly uint[,] powersOfTwo = new uint[32, 2] {
{ 0, 1 }, { 0, 2 }, { 0, 4 }, { 0, 8 },
{ 0, 16 }, { 0, 32 }, { 0, 64 }, { 0, 128 },
{ 0, 256 }, { 0, 512 }, { 0, 1024 }, { 0, 2048 },
{ 0, 4096 }, { 0, 8192 }, { 0, 16384 }, { 0, 32768 },
{ 0, 65536 }, { 0, 131072 }, { 0, 262144 }, { 0, 524288 },
{ 0, 1048576 }, { 0, 2097152 }, { 0, 4194304 }, { 0, 8388608 },
{ 0, 16777216 }, { 0, 33554432 }, { 0, 67108864 }, { 0, 134217728 },
{ 0, 268435456 }, { 0, 536870912 }, { 0, 1073741824 }, { 0, 2147483648 }
};
/// <summary>Index list for rotating the bit arrays</summary>
private static readonly byte[] shuffle = new byte[128] {
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,
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,
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,
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
};
/// <summary>GUID in which the key is stored</summary>
private Guid guid;
}
} // namespace Nuclex.Support.Licensing
#region Apache License 2.0
/*
Nuclex .NET Framework
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
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 // Apache License 2.0
using System;
using System.Collections;
using System.Text;
namespace Nuclex.Support.Licensing {
/// <summary>Typical license key with 5x5 alphanumerical characters</summary>
/// <remarks>
/// <para>
/// 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,
/// the textual representation of the license keys looks identical,
/// eg. <code>O809J-RN5TD-IM3CU-4IG1O-O90X9</code>.
/// </para>
/// <para>
/// 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.
/// The four integers can be modified directly, for example to store feature
/// lists, checksums or other data within the key.
/// </para>
/// </remarks>
public class LicenseKey {
/// <summary>Parses the license key contained in a string</summary>
/// <param name="key">String containing a license key that is to be parsed</param>
/// <returns>The license key parsed from provided string</returns>
/// <exception cref="ArgumentException">
/// When the provided string is not a license key
/// </exception>
public static LicenseKey Parse(string key) {
key = key.Replace(" ", string.Empty).Replace("-", string.Empty).ToUpper();
if(key.Length != 25)
throw new ArgumentException("This is not a license key");
BitArray bits = new BitArray(128);
uint sequence;
// Convert the first 4 sequences of 6 chars into 124 bits
for(int j = 0; j < 4; j++) {
sequence =
(uint)codeTable.IndexOf(key[j * 6 + 5]) * 60466176 +
(uint)codeTable.IndexOf(key[j * 6 + 4]) * 1679616 +
(uint)codeTable.IndexOf(key[j * 6 + 3]) * 46656 +
(uint)codeTable.IndexOf(key[j * 6 + 2]) * 1296 +
(uint)codeTable.IndexOf(key[j * 6 + 1]) * 36 +
(uint)codeTable.IndexOf(key[j * 6 + 0]);
for(int i = 0; i < 31; i++)
bits[j * 31 + i] = (sequence & powersOfTwo[i, 1]) != 0;
}
// Append the remaining character's 4 bits
sequence = (uint)codeTable.IndexOf(key[24]);
bits[124] = (sequence & powersOfTwo[4, 1]) != 0;
bits[125] = (sequence & powersOfTwo[3, 1]) != 0;
bits[126] = (sequence & powersOfTwo[2, 1]) != 0;
bits[127] = (sequence & powersOfTwo[1, 1]) != 0;
// Revert the mangling that was applied to the key when encoding...
unmangle(bits);
// ...and we've got our GUID back!
byte[] guidBytes = new byte[16];
bits.CopyTo(guidBytes, 0);
return new LicenseKey(new Guid(guidBytes));
}
/// <summary>Initializes a new, empty license key</summary>
public LicenseKey() : this(Guid.Empty) { }
/// <summary>Initializes the license key from a GUID</summary>
/// <param name="source">GUID that is used to create the license key</param>
public LicenseKey(Guid source) {
this.guid = source;
}
/// <summary>Accesses the four integer values within a license key</summary>
/// <exception cref="IndexOutOfRangeException">
/// When the index lies outside of the key's fields
/// </exception>
public int this[int index] {
get {
if((index < 0) || (index > 3))
throw new IndexOutOfRangeException("Index out of range");
return BitConverter.ToInt32(this.guid.ToByteArray(), index * 4);
}
set {
if((index < 0) || (index > 3))
throw new IndexOutOfRangeException("Index out of range");
// Convert the GUID into binary data so we can replace one of its values
byte[] guidBytes = this.guid.ToByteArray();
// Overwrite the section at the index specified by the user with the new value
Array.Copy(
BitConverter.GetBytes(value), 0, // source and start index
guidBytes, index * 4, // destination and start index
4 // length
);
// Replacement finished, now we can reconstruct our guid
this.guid = new Guid(guidBytes);
}
}
/// <summary>Converts the license key into a GUID</summary>
/// <returns>The GUID created from the license key</returns>
public Guid ToGuid() {
return this.guid;
}
/// <summary>Converts the license key into a byte array</summary>
/// <returns>A byte array containing the converted license key</returns>
public byte[] ToByteArray() {
return this.guid.ToByteArray();
}
/// <summary>Converts the license key to a string</summary>
/// <returns>A string containing the converted license key</returns>
public override string ToString() {
StringBuilder resultBuilder = new StringBuilder();
// Build a bit array from the input data
BitArray bits = new BitArray(this.guid.ToByteArray());
mangle(bits);
int sequence = 0;
// Build 4 sequences of 6 characters from the first 124 bits
for(int i = 0; i < 4; ++i) {
// We take the next 31 bits from the buffer
for(int j = 0; j < 31; ++j)
sequence |= (int)powersOfTwo[j, bits[i * 31 + j] ? 1 : 0];
// 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,
// which means we can fit 31 bits into every 6 alpha-numerical characters.
for(int j = 0; j < 6; ++j) {
resultBuilder.Append(codeTable[sequence % 36]);
sequence /= 36;
}
}
// Use the remaining 4 bits to build the final character
resultBuilder.Append(
codeTable[
(int)(
powersOfTwo[4, bits[124] ? 1 : 0] |
powersOfTwo[3, bits[125] ? 1 : 0] |
powersOfTwo[2, bits[126] ? 1 : 0] |
powersOfTwo[1, bits[127] ? 1 : 0] |
powersOfTwo[0, 1] // One bit remains unused :)
)
]
);
// Now build a nice, readable string from the decoded characters
resultBuilder.Insert(5, keyDelimiter, 0, 1);
resultBuilder.Insert(11, keyDelimiter, 0, 1);
resultBuilder.Insert(17, keyDelimiter, 0, 1);
resultBuilder.Insert(23, keyDelimiter, 0, 1);
return resultBuilder.ToString();
}
/// <summary>Mangles a bit array</summary>
/// <param name="bits">Bit array that will be mangled</param>
private static void mangle(BitArray bits) {
BitArray temp = new BitArray(bits);
for(int i = 0; i < temp.Length; ++i) {
bits[i] = temp[shuffle[i]];
if((i & 1) != 0)
bits[i] = !bits[i];
}
}
/// <summary>Unmangles a bit array</summary>
/// <param name="bits">Bit array that will be unmangled</param>
private static void unmangle(BitArray bits) {
BitArray temp = new BitArray(bits);
for(int i = 0; i < temp.Length; ++i) {
if((i & 1) != 0)
temp[i] = !temp[i];
bits[shuffle[i]] = temp[i];
}
}
/// <summary>Character used to delimit each 5 digit group in a license key</summary>
/// <remarks>
/// Required to be a char array because the .NET Compact Framework only provides
/// an overload for char[] in the StringBuilder.Insert() method.
/// </remarks>
private static char[] keyDelimiter = new char[] { '-' };
/// <summary>Table with the individual characters in a key</summary>
private static readonly string codeTable =
"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
/// <summary>Helper array containing the precalculated powers of two</summary>
private static readonly uint[,] powersOfTwo = new uint[32, 2] {
{ 0, 1 }, { 0, 2 }, { 0, 4 }, { 0, 8 },
{ 0, 16 }, { 0, 32 }, { 0, 64 }, { 0, 128 },
{ 0, 256 }, { 0, 512 }, { 0, 1024 }, { 0, 2048 },
{ 0, 4096 }, { 0, 8192 }, { 0, 16384 }, { 0, 32768 },
{ 0, 65536 }, { 0, 131072 }, { 0, 262144 }, { 0, 524288 },
{ 0, 1048576 }, { 0, 2097152 }, { 0, 4194304 }, { 0, 8388608 },
{ 0, 16777216 }, { 0, 33554432 }, { 0, 67108864 }, { 0, 134217728 },
{ 0, 268435456 }, { 0, 536870912 }, { 0, 1073741824 }, { 0, 2147483648 }
};
/// <summary>Index list for rotating the bit arrays</summary>
private static readonly byte[] shuffle = new byte[128] {
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,
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,
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,
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
};
/// <summary>GUID in which the key is stored</summary>
private Guid guid;
}
} // namespace Nuclex.Support.Licensing

View File

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

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