Changed license to Apache License 2.0
This commit is contained in:
parent
d3bf0be9d7
commit
52dc3d3708
|
@ -348,11 +348,11 @@
|
||||||
<Content Include="Documents\Request Framework.txt" />
|
<Content Include="Documents\Request Framework.txt" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||||
Other similar extension points exist, see Microsoft.Common.targets.
|
Other similar extension points exist, see Microsoft.Common.targets.
|
||||||
<Target Name="BeforeBuild">
|
<Target Name="BeforeBuild">
|
||||||
</Target>
|
</Target>
|
||||||
<Target Name="AfterBuild">
|
<Target Name="AfterBuild">
|
||||||
</Target>
|
</Target>
|
||||||
-->
|
-->
|
||||||
</Project>
|
</Project>
|
|
@ -351,11 +351,11 @@
|
||||||
<Content Include="Documents\Request Framework.txt" />
|
<Content Include="Documents\Request Framework.txt" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||||
Other similar extension points exist, see Microsoft.Common.targets.
|
Other similar extension points exist, see Microsoft.Common.targets.
|
||||||
<Target Name="BeforeBuild">
|
<Target Name="BeforeBuild">
|
||||||
</Target>
|
</Target>
|
||||||
<Target Name="AfterBuild">
|
<Target Name="AfterBuild">
|
||||||
</Target>
|
</Target>
|
||||||
-->
|
-->
|
||||||
</Project>
|
</Project>
|
|
@ -1,37 +1,56 @@
|
||||||
using System.Reflection;
|
#region Apache License 2.0
|
||||||
using System.Runtime.CompilerServices;
|
/*
|
||||||
using System.Runtime.InteropServices;
|
Nuclex .NET Framework
|
||||||
|
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
|
||||||
// General Information about an assembly is controlled through the following
|
|
||||||
// set of attributes. Change these attribute values to modify the information
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// associated with an assembly.
|
you may not use this file except in compliance with the License.
|
||||||
[assembly: AssemblyTitle("Nuclex.Support")]
|
You may obtain a copy of the License at
|
||||||
[assembly: AssemblyProduct("Nuclex.Support")]
|
|
||||||
[assembly: AssemblyDescription("")]
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
[assembly: AssemblyCompany("Nuclex Development Labs")]
|
|
||||||
[assembly: AssemblyCopyright("Copyright © Nuclex Development Labs 2008-2017")]
|
Unless required by applicable law or agreed to in writing, software
|
||||||
[assembly: AssemblyTrademark("")]
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
[assembly: AssemblyCulture("")]
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
// Setting ComVisible to false makes the types in this assembly not visible
|
limitations under the License.
|
||||||
// to COM components. If you need to access a type in this assembly from
|
*/
|
||||||
// COM, set the ComVisible attribute to true on that type.
|
#endregion // Apache License 2.0
|
||||||
[assembly: ComVisible(false)]
|
|
||||||
|
using System.Reflection;
|
||||||
// The following GUID is for the ID of the typelib if this project is exposed to COM
|
using System.Runtime.CompilerServices;
|
||||||
[assembly: Guid("1308e4c3-a0c1-423a-aaae-61c7314777e0")]
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
#if UNITTEST
|
// General Information about an assembly is controlled through the following
|
||||||
// This is required to NMock can derive its proxies from interfaces in
|
// set of attributes. Change these attribute values to modify the information
|
||||||
// the internal unit test classes
|
// associated with an assembly.
|
||||||
[assembly: InternalsVisibleTo(NMock.Constants.InternalsVisibleToDynamicProxy)]
|
[assembly: AssemblyTitle("Nuclex.Support")]
|
||||||
#endif
|
[assembly: AssemblyProduct("Nuclex.Support")]
|
||||||
|
[assembly: AssemblyDescription("")]
|
||||||
// Version information for an assembly consists of the following four values:
|
[assembly: AssemblyCompany("Nuclex Development Labs")]
|
||||||
//
|
[assembly: AssemblyCopyright("Copyright © Markus Ewald / Nuclex Development Labs 2002-2024")]
|
||||||
// Major Version
|
[assembly: AssemblyTrademark("")]
|
||||||
// Minor Version
|
[assembly: AssemblyCulture("")]
|
||||||
// Build Number
|
|
||||||
// Revision
|
// Setting ComVisible to false makes the types in this assembly not visible
|
||||||
//
|
// to COM components. If you need to access a type in this assembly from
|
||||||
[assembly: AssemblyVersion("1.0.0.0")]
|
// COM, set the ComVisible attribute to true on that type.
|
||||||
|
[assembly: ComVisible(false)]
|
||||||
|
|
||||||
|
// The following GUID is for the ID of the typelib if this project is exposed to COM
|
||||||
|
[assembly: Guid("1308e4c3-a0c1-423a-aaae-61c7314777e0")]
|
||||||
|
|
||||||
|
#if UNITTEST
|
||||||
|
// This is required to NMock can derive its proxies from interfaces in
|
||||||
|
// the internal unit test classes
|
||||||
|
[assembly: InternalsVisibleTo(NMock.Constants.InternalsVisibleToDynamicProxy)]
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Version information for an assembly consists of the following four values:
|
||||||
|
//
|
||||||
|
// Major Version
|
||||||
|
// Minor Version
|
||||||
|
// Build Number
|
||||||
|
// Revision
|
||||||
|
//
|
||||||
|
[assembly: AssemblyVersion("1.0.0.0")]
|
||||||
|
|
27
Source/Async/AsyncStatus.cs
Executable file → Normal file
27
Source/Async/AsyncStatus.cs
Executable file → Normal file
|
@ -1,22 +1,21 @@
|
||||||
#region CPL License
|
#region Apache License 2.0
|
||||||
/*
|
/*
|
||||||
Nuclex Framework
|
Nuclex .NET Framework
|
||||||
Copyright (C) 2002-2017 Nuclex Development Labs
|
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
|
||||||
|
|
||||||
This library is free software; you can redistribute it and/or
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
modify it under the terms of the IBM Common Public License as
|
you may not use this file except in compliance with the License.
|
||||||
published by the IBM Corporation; either version 1.0 of the
|
You may obtain a copy of the License at
|
||||||
License, or (at your option) any later version.
|
|
||||||
|
|
||||||
This library is distributed in the hope that it will be useful,
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
IBM Common Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the IBM Common Public
|
Unless required by applicable law or agreed to in writing, software
|
||||||
License along with this library
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
#endregion
|
#endregion // Apache License 2.0
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
|
||||||
|
|
27
Source/Async/AsyncStatusEventArgs.cs
Executable file → Normal file
27
Source/Async/AsyncStatusEventArgs.cs
Executable file → Normal file
|
@ -1,22 +1,21 @@
|
||||||
#region CPL License
|
#region Apache License 2.0
|
||||||
/*
|
/*
|
||||||
Nuclex Framework
|
Nuclex .NET Framework
|
||||||
Copyright (C) 2002-2017 Nuclex Development Labs
|
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
|
||||||
|
|
||||||
This library is free software; you can redistribute it and/or
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
modify it under the terms of the IBM Common Public License as
|
you may not use this file except in compliance with the License.
|
||||||
published by the IBM Corporation; either version 1.0 of the
|
You may obtain a copy of the License at
|
||||||
License, or (at your option) any later version.
|
|
||||||
|
|
||||||
This library is distributed in the hope that it will be useful,
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
IBM Common Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the IBM Common Public
|
Unless required by applicable law or agreed to in writing, software
|
||||||
License along with this library
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
#endregion
|
#endregion // Apache License 2.0
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
|
||||||
|
|
27
Source/Async/IAsyncAction.cs
Executable file → Normal file
27
Source/Async/IAsyncAction.cs
Executable file → Normal file
|
@ -1,22 +1,21 @@
|
||||||
#region CPL License
|
#region Apache License 2.0
|
||||||
/*
|
/*
|
||||||
Nuclex Framework
|
Nuclex .NET Framework
|
||||||
Copyright (C) 2002-2017 Nuclex Development Labs
|
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
|
||||||
|
|
||||||
This library is free software; you can redistribute it and/or
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
modify it under the terms of the IBM Common Public License as
|
you may not use this file except in compliance with the License.
|
||||||
published by the IBM Corporation; either version 1.0 of the
|
You may obtain a copy of the License at
|
||||||
License, or (at your option) any later version.
|
|
||||||
|
|
||||||
This library is distributed in the hope that it will be useful,
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
IBM Common Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the IBM Common Public
|
Unless required by applicable law or agreed to in writing, software
|
||||||
License along with this library
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
#endregion
|
#endregion // Apache License 2.0
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
|
27
Source/Async/IAsyncSwitch.cs
Executable file → Normal file
27
Source/Async/IAsyncSwitch.cs
Executable file → Normal file
|
@ -1,22 +1,21 @@
|
||||||
#region CPL License
|
#region Apache License 2.0
|
||||||
/*
|
/*
|
||||||
Nuclex Framework
|
Nuclex .NET Framework
|
||||||
Copyright (C) 2002-2017 Nuclex Development Labs
|
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
|
||||||
|
|
||||||
This library is free software; you can redistribute it and/or
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
modify it under the terms of the IBM Common Public License as
|
you may not use this file except in compliance with the License.
|
||||||
published by the IBM Corporation; either version 1.0 of the
|
You may obtain a copy of the License at
|
||||||
License, or (at your option) any later version.
|
|
||||||
|
|
||||||
This library is distributed in the hope that it will be useful,
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
IBM Common Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the IBM Common Public
|
Unless required by applicable law or agreed to in writing, software
|
||||||
License along with this library
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
#endregion
|
#endregion // Apache License 2.0
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
|
27
Source/Async/IAsyncTask.cs
Executable file → Normal file
27
Source/Async/IAsyncTask.cs
Executable file → Normal file
|
@ -1,22 +1,21 @@
|
||||||
#region CPL License
|
#region Apache License 2.0
|
||||||
/*
|
/*
|
||||||
Nuclex Framework
|
Nuclex .NET Framework
|
||||||
Copyright (C) 2002-2017 Nuclex Development Labs
|
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
|
||||||
|
|
||||||
This library is free software; you can redistribute it and/or
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
modify it under the terms of the IBM Common Public License as
|
you may not use this file except in compliance with the License.
|
||||||
published by the IBM Corporation; either version 1.0 of the
|
You may obtain a copy of the License at
|
||||||
License, or (at your option) any later version.
|
|
||||||
|
|
||||||
This library is distributed in the hope that it will be useful,
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
IBM Common Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the IBM Common Public
|
Unless required by applicable law or agreed to in writing, software
|
||||||
License along with this library
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
#endregion
|
#endregion // Apache License 2.0
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
|
||||||
|
|
27
Source/Async/ICancellable.cs
Executable file → Normal file
27
Source/Async/ICancellable.cs
Executable file → Normal file
|
@ -1,22 +1,21 @@
|
||||||
#region CPL License
|
#region Apache License 2.0
|
||||||
/*
|
/*
|
||||||
Nuclex Framework
|
Nuclex .NET Framework
|
||||||
Copyright (C) 2002-2017 Nuclex Development Labs
|
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
|
||||||
|
|
||||||
This library is free software; you can redistribute it and/or
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
modify it under the terms of the IBM Common Public License as
|
you may not use this file except in compliance with the License.
|
||||||
published by the IBM Corporation; either version 1.0 of the
|
You may obtain a copy of the License at
|
||||||
License, or (at your option) any later version.
|
|
||||||
|
|
||||||
This library is distributed in the hope that it will be useful,
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
IBM Common Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the IBM Common Public
|
Unless required by applicable law or agreed to in writing, software
|
||||||
License along with this library
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
#endregion
|
#endregion // Apache License 2.0
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
|
||||||
|
|
27
Source/Async/IProgressSource.cs
Executable file → Normal file
27
Source/Async/IProgressSource.cs
Executable file → Normal file
|
@ -1,22 +1,21 @@
|
||||||
#region CPL License
|
#region Apache License 2.0
|
||||||
/*
|
/*
|
||||||
Nuclex Framework
|
Nuclex .NET Framework
|
||||||
Copyright (C) 2002-2017 Nuclex Development Labs
|
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
|
||||||
|
|
||||||
This library is free software; you can redistribute it and/or
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
modify it under the terms of the IBM Common Public License as
|
you may not use this file except in compliance with the License.
|
||||||
published by the IBM Corporation; either version 1.0 of the
|
You may obtain a copy of the License at
|
||||||
License, or (at your option) any later version.
|
|
||||||
|
|
||||||
This library is distributed in the hope that it will be useful,
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
IBM Common Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the IBM Common Public
|
Unless required by applicable law or agreed to in writing, software
|
||||||
License along with this library
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
#endregion
|
#endregion // Apache License 2.0
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -1,226 +1,225 @@
|
||||||
#region CPL License
|
#region Apache License 2.0
|
||||||
/*
|
/*
|
||||||
Nuclex Framework
|
Nuclex .NET Framework
|
||||||
Copyright (C) 2002-2017 Nuclex Development Labs
|
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
|
||||||
|
|
||||||
This library is free software; you can redistribute it and/or
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
modify it under the terms of the IBM Common Public License as
|
you may not use this file except in compliance with the License.
|
||||||
published by the IBM Corporation; either version 1.0 of the
|
You may obtain a copy of the License at
|
||||||
License, or (at your option) any later version.
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
This library is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
Unless required by applicable law or agreed to in writing, software
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
IBM Common Public License for more details.
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
You should have received a copy of the IBM Common Public
|
limitations under the License.
|
||||||
License along with this library
|
*/
|
||||||
*/
|
#endregion // Apache License 2.0
|
||||||
#endregion
|
|
||||||
|
#if !NO_SETS
|
||||||
#if !NO_SETS
|
|
||||||
|
#if UNITTEST
|
||||||
#if UNITTEST
|
|
||||||
|
using System;
|
||||||
using System;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Generic;
|
|
||||||
|
using NUnit.Framework;
|
||||||
using NUnit.Framework;
|
|
||||||
|
namespace Nuclex.Support.Cloning {
|
||||||
namespace Nuclex.Support.Cloning {
|
|
||||||
|
/// <summary>Unit Test for the expression tree-based cloner</summary>
|
||||||
/// <summary>Unit Test for the expression tree-based cloner</summary>
|
[TestFixture]
|
||||||
[TestFixture]
|
internal class ExpressionTreeClonerTest : CloneFactoryTest {
|
||||||
internal class ExpressionTreeClonerTest : CloneFactoryTest {
|
|
||||||
|
/// <summary>Initializes a new unit test suite for the reflection cloner</summary>
|
||||||
/// <summary>Initializes a new unit test suite for the reflection cloner</summary>
|
public ExpressionTreeClonerTest() {
|
||||||
public ExpressionTreeClonerTest() {
|
this.cloneFactory = new ExpressionTreeCloner();
|
||||||
this.cloneFactory = new ExpressionTreeCloner();
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Verifies that cloning a null object simply returns null</summary>
|
||||||
/// <summary>Verifies that cloning a null object simply returns null</summary>
|
[Test]
|
||||||
[Test]
|
public void CloningNullYieldsNull() {
|
||||||
public void CloningNullYieldsNull() {
|
Assert.IsNull(this.cloneFactory.DeepFieldClone<object>(null));
|
||||||
Assert.IsNull(this.cloneFactory.DeepFieldClone<object>(null));
|
Assert.IsNull(this.cloneFactory.DeepPropertyClone<object>(null));
|
||||||
Assert.IsNull(this.cloneFactory.DeepPropertyClone<object>(null));
|
Assert.IsNull(this.cloneFactory.ShallowFieldClone<object>(null));
|
||||||
Assert.IsNull(this.cloneFactory.ShallowFieldClone<object>(null));
|
Assert.IsNull(this.cloneFactory.ShallowPropertyClone<object>(null));
|
||||||
Assert.IsNull(this.cloneFactory.ShallowPropertyClone<object>(null));
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Verifies that clones of objects whose class doesn't possess a default constructor
|
||||||
/// Verifies that clones of objects whose class doesn't possess a default constructor
|
/// can be made
|
||||||
/// can be made
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void ClassWithoutDefaultConstructorCanBeCloned() {
|
||||||
public void ClassWithoutDefaultConstructorCanBeCloned() {
|
var original = new ClassWithoutDefaultConstructor(1234);
|
||||||
var original = new ClassWithoutDefaultConstructor(1234);
|
ClassWithoutDefaultConstructor clone = this.cloneFactory.DeepFieldClone(original);
|
||||||
ClassWithoutDefaultConstructor clone = this.cloneFactory.DeepFieldClone(original);
|
|
||||||
|
Assert.AreNotSame(original, clone);
|
||||||
Assert.AreNotSame(original, clone);
|
Assert.AreEqual(original.Dummy, clone.Dummy);
|
||||||
Assert.AreEqual(original.Dummy, clone.Dummy);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Verifies that clones of primitive types can be created</summary>
|
||||||
/// <summary>Verifies that clones of primitive types can be created</summary>
|
[Test]
|
||||||
[Test]
|
public void PrimitiveTypesCanBeCloned() {
|
||||||
public void PrimitiveTypesCanBeCloned() {
|
int original = 12345;
|
||||||
int original = 12345;
|
int clone = this.cloneFactory.DeepFieldClone(original);
|
||||||
int clone = this.cloneFactory.DeepFieldClone(original);
|
Assert.AreEqual(original, clone);
|
||||||
Assert.AreEqual(original, clone);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Verifies that shallow clones of arrays can be made</summary>
|
||||||
/// <summary>Verifies that shallow clones of arrays can be made</summary>
|
[Test]
|
||||||
[Test]
|
public void ReferenceTypesCanBeCloned() {
|
||||||
public void ReferenceTypesCanBeCloned() {
|
var original = new TestReferenceType() { TestField = 123, TestProperty = 456 };
|
||||||
var original = new TestReferenceType() { TestField = 123, TestProperty = 456 };
|
TestReferenceType clone = this.cloneFactory.DeepFieldClone(original);
|
||||||
TestReferenceType clone = this.cloneFactory.DeepFieldClone(original);
|
|
||||||
|
Assert.AreNotSame(original, clone);
|
||||||
Assert.AreNotSame(original, clone);
|
Assert.AreEqual(original.TestField, clone.TestField);
|
||||||
Assert.AreEqual(original.TestField, clone.TestField);
|
Assert.AreEqual(original.TestProperty, clone.TestProperty);
|
||||||
Assert.AreEqual(original.TestProperty, clone.TestProperty);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Verifies that shallow clones of arrays can be made</summary>
|
||||||
/// <summary>Verifies that shallow clones of arrays can be made</summary>
|
[Test]
|
||||||
[Test]
|
public void PrimitiveArraysCanBeCloned() {
|
||||||
public void PrimitiveArraysCanBeCloned() {
|
var original = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
|
||||||
var original = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
|
int[] clone = this.cloneFactory.DeepFieldClone(original);
|
||||||
int[] clone = this.cloneFactory.DeepFieldClone(original);
|
|
||||||
|
Assert.AreNotSame(original, clone);
|
||||||
Assert.AreNotSame(original, clone);
|
CollectionAssert.AreEqual(original, clone);
|
||||||
CollectionAssert.AreEqual(original, clone);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Verifies that shallow clones of arrays can be made</summary>
|
||||||
/// <summary>Verifies that shallow clones of arrays can be made</summary>
|
[Test]
|
||||||
[Test]
|
public void ShallowClonesOfArraysCanBeMade() {
|
||||||
public void ShallowClonesOfArraysCanBeMade() {
|
var original = new TestReferenceType[] {
|
||||||
var original = new TestReferenceType[] {
|
new TestReferenceType() { TestField = 123, TestProperty = 456 }
|
||||||
new TestReferenceType() { TestField = 123, TestProperty = 456 }
|
};
|
||||||
};
|
TestReferenceType[] clone = this.cloneFactory.ShallowFieldClone(original);
|
||||||
TestReferenceType[] clone = this.cloneFactory.ShallowFieldClone(original);
|
|
||||||
|
Assert.AreSame(original[0], clone[0]);
|
||||||
Assert.AreSame(original[0], clone[0]);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Verifies that deep clones of arrays can be made</summary>
|
||||||
/// <summary>Verifies that deep clones of arrays can be made</summary>
|
[Test]
|
||||||
[Test]
|
public void DeepClonesOfArraysCanBeMade() {
|
||||||
public void DeepClonesOfArraysCanBeMade() {
|
var original = new TestReferenceType[,] {
|
||||||
var original = new TestReferenceType[,] {
|
{
|
||||||
{
|
new TestReferenceType() { TestField = 123, TestProperty = 456 }
|
||||||
new TestReferenceType() { TestField = 123, TestProperty = 456 }
|
}
|
||||||
}
|
};
|
||||||
};
|
TestReferenceType[,] clone = this.cloneFactory.DeepFieldClone(original);
|
||||||
TestReferenceType[,] clone = this.cloneFactory.DeepFieldClone(original);
|
|
||||||
|
Assert.AreNotSame(original[0, 0], clone[0, 0]);
|
||||||
Assert.AreNotSame(original[0, 0], clone[0, 0]);
|
Assert.AreEqual(original[0, 0].TestField, clone[0, 0].TestField);
|
||||||
Assert.AreEqual(original[0, 0].TestField, clone[0, 0].TestField);
|
Assert.AreEqual(original[0, 0].TestProperty, clone[0, 0].TestProperty);
|
||||||
Assert.AreEqual(original[0, 0].TestProperty, clone[0, 0].TestProperty);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Verifies that deep clones of a generic list can be made</summary>
|
||||||
/// <summary>Verifies that deep clones of a generic list can be made</summary>
|
[Test]
|
||||||
[Test]
|
public void GenericListsCanBeCloned() {
|
||||||
public void GenericListsCanBeCloned() {
|
var original = new List<int>(new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 });
|
||||||
var original = new List<int>(new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 });
|
List<int> clone = this.cloneFactory.DeepFieldClone(original);
|
||||||
List<int> clone = this.cloneFactory.DeepFieldClone(original);
|
|
||||||
|
CollectionAssert.AreEqual(original, clone);
|
||||||
CollectionAssert.AreEqual(original, clone);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Verifies that deep clones of a generic dictionary can be made</summary>
|
||||||
/// <summary>Verifies that deep clones of a generic dictionary can be made</summary>
|
[Test]
|
||||||
[Test]
|
public void GenericDictionariesCanBeCloned() {
|
||||||
public void GenericDictionariesCanBeCloned() {
|
var original = new Dictionary<int, string>();
|
||||||
var original = new Dictionary<int, string>();
|
original.Add(1, "one");
|
||||||
original.Add(1, "one");
|
Dictionary<int, string> clone = this.cloneFactory.DeepFieldClone(original);
|
||||||
Dictionary<int, string> clone = this.cloneFactory.DeepFieldClone(original);
|
|
||||||
|
Assert.AreEqual("one", clone[1]);
|
||||||
Assert.AreEqual("one", clone[1]);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Verifies that a field-based shallow clone of a value type can be performed
|
||||||
/// Verifies that a field-based shallow clone of a value type can be performed
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void ShallowFieldBasedClonesOfValueTypesCanBeMade() {
|
||||||
public void ShallowFieldBasedClonesOfValueTypesCanBeMade() {
|
HierarchicalValueType original = CreateValueType();
|
||||||
HierarchicalValueType original = CreateValueType();
|
HierarchicalValueType clone = this.cloneFactory.ShallowFieldClone(original);
|
||||||
HierarchicalValueType clone = this.cloneFactory.ShallowFieldClone(original);
|
VerifyClone(ref original, ref clone, isDeepClone: false, isPropertyBasedClone: false);
|
||||||
VerifyClone(ref original, ref clone, isDeepClone: false, isPropertyBasedClone: false);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Verifies that a field-based shallow clone of a reference type can be performed
|
||||||
/// Verifies that a field-based shallow clone of a reference type can be performed
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void ShallowFieldBasedClonesOfReferenceTypesCanBeMade() {
|
||||||
public void ShallowFieldBasedClonesOfReferenceTypesCanBeMade() {
|
HierarchicalReferenceType original = CreateReferenceType();
|
||||||
HierarchicalReferenceType original = CreateReferenceType();
|
HierarchicalReferenceType clone = this.cloneFactory.ShallowFieldClone(original);
|
||||||
HierarchicalReferenceType clone = this.cloneFactory.ShallowFieldClone(original);
|
VerifyClone(original, clone, isDeepClone: false, isPropertyBasedClone: false);
|
||||||
VerifyClone(original, clone, isDeepClone: false, isPropertyBasedClone: false);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Verifies that a field-based deep clone of a value type can be performed
|
||||||
/// Verifies that a field-based deep clone of a value type can be performed
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void DeepFieldBasedClonesOfValueTypesCanBeMade() {
|
||||||
public void DeepFieldBasedClonesOfValueTypesCanBeMade() {
|
HierarchicalValueType original = CreateValueType();
|
||||||
HierarchicalValueType original = CreateValueType();
|
HierarchicalValueType clone = this.cloneFactory.DeepFieldClone(original);
|
||||||
HierarchicalValueType clone = this.cloneFactory.DeepFieldClone(original);
|
VerifyClone(ref original, ref clone, isDeepClone: true, isPropertyBasedClone: false);
|
||||||
VerifyClone(ref original, ref clone, isDeepClone: true, isPropertyBasedClone: false);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Verifies that a field-based deep clone of a reference type can be performed
|
||||||
/// Verifies that a field-based deep clone of a reference type can be performed
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void DeepFieldBasedClonesOfReferenceTypesCanBeMade() {
|
||||||
public void DeepFieldBasedClonesOfReferenceTypesCanBeMade() {
|
HierarchicalReferenceType original = CreateReferenceType();
|
||||||
HierarchicalReferenceType original = CreateReferenceType();
|
HierarchicalReferenceType clone = this.cloneFactory.DeepFieldClone(original);
|
||||||
HierarchicalReferenceType clone = this.cloneFactory.DeepFieldClone(original);
|
VerifyClone(original, clone, isDeepClone: true, isPropertyBasedClone: false);
|
||||||
VerifyClone(original, clone, isDeepClone: true, isPropertyBasedClone: false);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Verifies that a property-based shallow clone of a value type can be performed
|
||||||
/// Verifies that a property-based shallow clone of a value type can be performed
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void ShallowPropertyBasedClonesOfValueTypesCanBeMade() {
|
||||||
public void ShallowPropertyBasedClonesOfValueTypesCanBeMade() {
|
HierarchicalValueType original = CreateValueType();
|
||||||
HierarchicalValueType original = CreateValueType();
|
HierarchicalValueType clone = this.cloneFactory.ShallowPropertyClone(original);
|
||||||
HierarchicalValueType clone = this.cloneFactory.ShallowPropertyClone(original);
|
VerifyClone(ref original, ref clone, isDeepClone: false, isPropertyBasedClone: true);
|
||||||
VerifyClone(ref original, ref clone, isDeepClone: false, isPropertyBasedClone: true);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Verifies that a property-based shallow clone of a reference type can be performed
|
||||||
/// Verifies that a property-based shallow clone of a reference type can be performed
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void ShallowPropertyBasedClonesOfReferenceTypesCanBeMade() {
|
||||||
public void ShallowPropertyBasedClonesOfReferenceTypesCanBeMade() {
|
HierarchicalReferenceType original = CreateReferenceType();
|
||||||
HierarchicalReferenceType original = CreateReferenceType();
|
HierarchicalReferenceType clone = this.cloneFactory.ShallowPropertyClone(original);
|
||||||
HierarchicalReferenceType clone = this.cloneFactory.ShallowPropertyClone(original);
|
VerifyClone(original, clone, isDeepClone: false, isPropertyBasedClone: true);
|
||||||
VerifyClone(original, clone, isDeepClone: false, isPropertyBasedClone: true);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Verifies that a property-based deep clone of a value type can be performed
|
||||||
/// Verifies that a property-based deep clone of a value type can be performed
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void DeepPropertyBasedClonesOfValueTypesCanBeMade() {
|
||||||
public void DeepPropertyBasedClonesOfValueTypesCanBeMade() {
|
HierarchicalValueType original = CreateValueType();
|
||||||
HierarchicalValueType original = CreateValueType();
|
HierarchicalValueType clone = this.cloneFactory.DeepPropertyClone(original);
|
||||||
HierarchicalValueType clone = this.cloneFactory.DeepPropertyClone(original);
|
VerifyClone(ref original, ref clone, isDeepClone: true, isPropertyBasedClone: true);
|
||||||
VerifyClone(ref original, ref clone, isDeepClone: true, isPropertyBasedClone: true);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Verifies that a property-based deep clone of a reference type can be performed
|
||||||
/// Verifies that a property-based deep clone of a reference type can be performed
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void DeepPropertyBasedClonesOfReferenceTypesCanBeMade() {
|
||||||
public void DeepPropertyBasedClonesOfReferenceTypesCanBeMade() {
|
HierarchicalReferenceType original = CreateReferenceType();
|
||||||
HierarchicalReferenceType original = CreateReferenceType();
|
HierarchicalReferenceType clone = this.cloneFactory.DeepPropertyClone(original);
|
||||||
HierarchicalReferenceType clone = this.cloneFactory.DeepPropertyClone(original);
|
VerifyClone(original, clone, isDeepClone: true, isPropertyBasedClone: true);
|
||||||
VerifyClone(original, clone, isDeepClone: true, isPropertyBasedClone: true);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Clone factory being tested</summary>
|
||||||
/// <summary>Clone factory being tested</summary>
|
private ICloneFactory cloneFactory;
|
||||||
private ICloneFactory cloneFactory;
|
|
||||||
|
}
|
||||||
}
|
|
||||||
|
} // namespace Nuclex.Support.Cloning
|
||||||
} // namespace Nuclex.Support.Cloning
|
|
||||||
|
#endif // UNITTEST
|
||||||
#endif // UNITTEST
|
|
||||||
|
#endif // !NO_SETS
|
||||||
#endif // !NO_SETS
|
|
||||||
|
|
|
@ -1,286 +1,285 @@
|
||||||
#region CPL License
|
#region Apache License 2.0
|
||||||
/*
|
/*
|
||||||
Nuclex Framework
|
Nuclex .NET Framework
|
||||||
Copyright (C) 2002-2017 Nuclex Development Labs
|
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
|
||||||
|
|
||||||
This library is free software; you can redistribute it and/or
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
modify it under the terms of the IBM Common Public License as
|
you may not use this file except in compliance with the License.
|
||||||
published by the IBM Corporation; either version 1.0 of the
|
You may obtain a copy of the License at
|
||||||
License, or (at your option) any later version.
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
This library is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
Unless required by applicable law or agreed to in writing, software
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
IBM Common Public License for more details.
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
You should have received a copy of the IBM Common Public
|
limitations under the License.
|
||||||
License along with this library
|
*/
|
||||||
*/
|
#endregion // Apache License 2.0
|
||||||
#endregion
|
|
||||||
|
#if !NO_SETS
|
||||||
#if !NO_SETS
|
|
||||||
|
using System;
|
||||||
using System;
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.Concurrent;
|
|
||||||
|
namespace Nuclex.Support.Cloning {
|
||||||
namespace Nuclex.Support.Cloning {
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Cloning factory which uses expression trees to improve performance when cloning
|
||||||
/// Cloning factory which uses expression trees to improve performance when cloning
|
/// is a high-frequency action.
|
||||||
/// is a high-frequency action.
|
/// </summary>
|
||||||
/// </summary>
|
public partial class ExpressionTreeCloner : ICloneFactory {
|
||||||
public partial class ExpressionTreeCloner : ICloneFactory {
|
|
||||||
|
/// <summary>Initializes the static members of the expression tree cloner</summary>
|
||||||
/// <summary>Initializes the static members of the expression tree cloner</summary>
|
static ExpressionTreeCloner() {
|
||||||
static ExpressionTreeCloner() {
|
shallowFieldBasedCloners = new ConcurrentDictionary<Type, Func<object, object>>();
|
||||||
shallowFieldBasedCloners = new ConcurrentDictionary<Type, Func<object, object>>();
|
deepFieldBasedCloners = new ConcurrentDictionary<Type, Func<object, object>>();
|
||||||
deepFieldBasedCloners = new ConcurrentDictionary<Type, Func<object, object>>();
|
shallowPropertyBasedCloners = new ConcurrentDictionary<Type, Func<object, object>>();
|
||||||
shallowPropertyBasedCloners = new ConcurrentDictionary<Type, Func<object, object>>();
|
deepPropertyBasedCloners = new ConcurrentDictionary<Type, Func<object, object>>();
|
||||||
deepPropertyBasedCloners = new ConcurrentDictionary<Type, Func<object, object>>();
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Creates a deep clone of the specified object, also creating clones of all
|
||||||
/// Creates a deep clone of the specified object, also creating clones of all
|
/// child objects being referenced
|
||||||
/// child objects being referenced
|
/// </summary>
|
||||||
/// </summary>
|
/// <typeparam name="TCloned">Type of the object that will be cloned</typeparam>
|
||||||
/// <typeparam name="TCloned">Type of the object that will be cloned</typeparam>
|
/// <param name="objectToClone">Object that will be cloned</param>
|
||||||
/// <param name="objectToClone">Object that will be cloned</param>
|
/// <returns>A deep clone of the provided object</returns>
|
||||||
/// <returns>A deep clone of the provided object</returns>
|
public static TCloned DeepFieldClone<TCloned>(TCloned objectToClone) {
|
||||||
public static TCloned DeepFieldClone<TCloned>(TCloned objectToClone) {
|
object objectToCloneAsObject = objectToClone;
|
||||||
object objectToCloneAsObject = objectToClone;
|
if(objectToCloneAsObject == null) {
|
||||||
if(objectToCloneAsObject == null) {
|
return default(TCloned);
|
||||||
return default(TCloned);
|
}
|
||||||
}
|
|
||||||
|
Func<object, object> cloner = getOrCreateDeepFieldBasedCloner(typeof(TCloned));
|
||||||
Func<object, object> cloner = getOrCreateDeepFieldBasedCloner(typeof(TCloned));
|
return (TCloned)cloner(objectToCloneAsObject);
|
||||||
return (TCloned)cloner(objectToCloneAsObject);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Creates a deep clone of the specified object, also creating clones of all
|
||||||
/// Creates a deep clone of the specified object, also creating clones of all
|
/// child objects being referenced
|
||||||
/// child objects being referenced
|
/// </summary>
|
||||||
/// </summary>
|
/// <typeparam name="TCloned">Type of the object that will be cloned</typeparam>
|
||||||
/// <typeparam name="TCloned">Type of the object that will be cloned</typeparam>
|
/// <param name="objectToClone">Object that will be cloned</param>
|
||||||
/// <param name="objectToClone">Object that will be cloned</param>
|
/// <returns>A deep clone of the provided object</returns>
|
||||||
/// <returns>A deep clone of the provided object</returns>
|
public static TCloned DeepPropertyClone<TCloned>(TCloned objectToClone) {
|
||||||
public static TCloned DeepPropertyClone<TCloned>(TCloned objectToClone) {
|
object objectToCloneAsObject = objectToClone;
|
||||||
object objectToCloneAsObject = objectToClone;
|
if(objectToCloneAsObject == null) {
|
||||||
if(objectToCloneAsObject == null) {
|
return default(TCloned);
|
||||||
return default(TCloned);
|
}
|
||||||
}
|
|
||||||
|
Func<object, object> cloner = getOrCreateDeepPropertyBasedCloner(typeof(TCloned));
|
||||||
Func<object, object> cloner = getOrCreateDeepPropertyBasedCloner(typeof(TCloned));
|
return (TCloned)cloner(objectToCloneAsObject);
|
||||||
return (TCloned)cloner(objectToCloneAsObject);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Creates a shallow clone of the specified object, reusing any referenced objects
|
||||||
/// Creates a shallow clone of the specified object, reusing any referenced objects
|
/// </summary>
|
||||||
/// </summary>
|
/// <typeparam name="TCloned">Type of the object that will be cloned</typeparam>
|
||||||
/// <typeparam name="TCloned">Type of the object that will be cloned</typeparam>
|
/// <param name="objectToClone">Object that will be cloned</param>
|
||||||
/// <param name="objectToClone">Object that will be cloned</param>
|
/// <returns>A shallow clone of the provided object</returns>
|
||||||
/// <returns>A shallow clone of the provided object</returns>
|
public static TCloned ShallowFieldClone<TCloned>(TCloned objectToClone) {
|
||||||
public static TCloned ShallowFieldClone<TCloned>(TCloned objectToClone) {
|
object objectToCloneAsObject = objectToClone;
|
||||||
object objectToCloneAsObject = objectToClone;
|
if(objectToCloneAsObject == null) {
|
||||||
if(objectToCloneAsObject == null) {
|
return default(TCloned);
|
||||||
return default(TCloned);
|
}
|
||||||
}
|
|
||||||
|
Func<object, object> cloner = getOrCreateShallowFieldBasedCloner(typeof(TCloned));
|
||||||
Func<object, object> cloner = getOrCreateShallowFieldBasedCloner(typeof(TCloned));
|
return (TCloned)cloner(objectToCloneAsObject);
|
||||||
return (TCloned)cloner(objectToCloneAsObject);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Creates a shallow clone of the specified object, reusing any referenced objects
|
||||||
/// Creates a shallow clone of the specified object, reusing any referenced objects
|
/// </summary>
|
||||||
/// </summary>
|
/// <typeparam name="TCloned">Type of the object that will be cloned</typeparam>
|
||||||
/// <typeparam name="TCloned">Type of the object that will be cloned</typeparam>
|
/// <param name="objectToClone">Object that will be cloned</param>
|
||||||
/// <param name="objectToClone">Object that will be cloned</param>
|
/// <returns>A shallow clone of the provided object</returns>
|
||||||
/// <returns>A shallow clone of the provided object</returns>
|
public static TCloned ShallowPropertyClone<TCloned>(TCloned objectToClone) {
|
||||||
public static TCloned ShallowPropertyClone<TCloned>(TCloned objectToClone) {
|
object objectToCloneAsObject = objectToClone;
|
||||||
object objectToCloneAsObject = objectToClone;
|
if(objectToCloneAsObject == null) {
|
||||||
if(objectToCloneAsObject == null) {
|
return default(TCloned);
|
||||||
return default(TCloned);
|
}
|
||||||
}
|
|
||||||
|
Func<object, object> cloner = getOrCreateShallowPropertyBasedCloner(typeof(TCloned));
|
||||||
Func<object, object> cloner = getOrCreateShallowPropertyBasedCloner(typeof(TCloned));
|
return (TCloned)cloner(objectToCloneAsObject);
|
||||||
return (TCloned)cloner(objectToCloneAsObject);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Creates a shallow clone of the specified object, reusing any referenced objects
|
||||||
/// Creates a shallow clone of the specified object, reusing any referenced objects
|
/// </summary>
|
||||||
/// </summary>
|
/// <typeparam name="TCloned">Type of the object that will be cloned</typeparam>
|
||||||
/// <typeparam name="TCloned">Type of the object that will be cloned</typeparam>
|
/// <param name="objectToClone">Object that will be cloned</param>
|
||||||
/// <param name="objectToClone">Object that will be cloned</param>
|
/// <returns>A shallow clone of the provided object</returns>
|
||||||
/// <returns>A shallow clone of the provided object</returns>
|
TCloned ICloneFactory.ShallowFieldClone<TCloned>(TCloned objectToClone) {
|
||||||
TCloned ICloneFactory.ShallowFieldClone<TCloned>(TCloned objectToClone) {
|
return ExpressionTreeCloner.ShallowFieldClone<TCloned>(objectToClone);
|
||||||
return ExpressionTreeCloner.ShallowFieldClone<TCloned>(objectToClone);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Creates a shallow clone of the specified object, reusing any referenced objects
|
||||||
/// Creates a shallow clone of the specified object, reusing any referenced objects
|
/// </summary>
|
||||||
/// </summary>
|
/// <typeparam name="TCloned">Type of the object that will be cloned</typeparam>
|
||||||
/// <typeparam name="TCloned">Type of the object that will be cloned</typeparam>
|
/// <param name="objectToClone">Object that will be cloned</param>
|
||||||
/// <param name="objectToClone">Object that will be cloned</param>
|
/// <returns>A shallow clone of the provided object</returns>
|
||||||
/// <returns>A shallow clone of the provided object</returns>
|
TCloned ICloneFactory.ShallowPropertyClone<TCloned>(TCloned objectToClone) {
|
||||||
TCloned ICloneFactory.ShallowPropertyClone<TCloned>(TCloned objectToClone) {
|
return ExpressionTreeCloner.ShallowPropertyClone<TCloned>(objectToClone);
|
||||||
return ExpressionTreeCloner.ShallowPropertyClone<TCloned>(objectToClone);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Creates a deep clone of the specified object, also creating clones of all
|
||||||
/// Creates a deep clone of the specified object, also creating clones of all
|
/// child objects being referenced
|
||||||
/// child objects being referenced
|
/// </summary>
|
||||||
/// </summary>
|
/// <typeparam name="TCloned">Type of the object that will be cloned</typeparam>
|
||||||
/// <typeparam name="TCloned">Type of the object that will be cloned</typeparam>
|
/// <param name="objectToClone">Object that will be cloned</param>
|
||||||
/// <param name="objectToClone">Object that will be cloned</param>
|
/// <returns>A deep clone of the provided object</returns>
|
||||||
/// <returns>A deep clone of the provided object</returns>
|
TCloned ICloneFactory.DeepFieldClone<TCloned>(TCloned objectToClone) {
|
||||||
TCloned ICloneFactory.DeepFieldClone<TCloned>(TCloned objectToClone) {
|
return ExpressionTreeCloner.DeepFieldClone<TCloned>(objectToClone);
|
||||||
return ExpressionTreeCloner.DeepFieldClone<TCloned>(objectToClone);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Creates a deep clone of the specified object, also creating clones of all
|
||||||
/// Creates a deep clone of the specified object, also creating clones of all
|
/// child objects being referenced
|
||||||
/// child objects being referenced
|
/// </summary>
|
||||||
/// </summary>
|
/// <typeparam name="TCloned">Type of the object that will be cloned</typeparam>
|
||||||
/// <typeparam name="TCloned">Type of the object that will be cloned</typeparam>
|
/// <param name="objectToClone">Object that will be cloned</param>
|
||||||
/// <param name="objectToClone">Object that will be cloned</param>
|
/// <returns>A deep clone of the provided object</returns>
|
||||||
/// <returns>A deep clone of the provided object</returns>
|
TCloned ICloneFactory.DeepPropertyClone<TCloned>(TCloned objectToClone) {
|
||||||
TCloned ICloneFactory.DeepPropertyClone<TCloned>(TCloned objectToClone) {
|
return ExpressionTreeCloner.DeepPropertyClone<TCloned>(objectToClone);
|
||||||
return ExpressionTreeCloner.DeepPropertyClone<TCloned>(objectToClone);
|
}
|
||||||
}
|
|
||||||
|
#if false
|
||||||
#if false
|
/// <summary>
|
||||||
/// <summary>
|
/// Transfers the state of one object into another, creating clones of referenced objects
|
||||||
/// Transfers the state of one object into another, creating clones of referenced objects
|
/// </summary>
|
||||||
/// </summary>
|
/// <typeparam name="TState">Type of the object whose sate will be transferred</typeparam>
|
||||||
/// <typeparam name="TState">Type of the object whose sate will be transferred</typeparam>
|
/// <param name="original">Original instance the state will be taken from</param>
|
||||||
/// <param name="original">Original instance the state will be taken from</param>
|
/// <param name="target">Target instance the state will be written to</param>
|
||||||
/// <param name="target">Target instance the state will be written to</param>
|
/// <param name="propertyBased">Whether to perform a property-based state copy</param>
|
||||||
/// <param name="propertyBased">Whether to perform a property-based state copy</param>
|
public void DeepCopyState<TState>(TState original, TState target, bool propertyBased)
|
||||||
public void DeepCopyState<TState>(TState original, TState target, bool propertyBased)
|
where TState : class {
|
||||||
where TState : class {
|
throw new NotImplementedException();
|
||||||
throw new NotImplementedException();
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Transfers the state of one object into another, creating clones of referenced objects
|
||||||
/// Transfers the state of one object into another, creating clones of referenced objects
|
/// </summary>
|
||||||
/// </summary>
|
/// <typeparam name="TState">Type of the object whose sate will be transferred</typeparam>
|
||||||
/// <typeparam name="TState">Type of the object whose sate will be transferred</typeparam>
|
/// <param name="original">Original instance the state will be taken from</param>
|
||||||
/// <param name="original">Original instance the state will be taken from</param>
|
/// <param name="target">Target instance the state will be written to</param>
|
||||||
/// <param name="target">Target instance the state will be written to</param>
|
/// <param name="propertyBased">Whether to perform a property-based state copy</param>
|
||||||
/// <param name="propertyBased">Whether to perform a property-based state copy</param>
|
public void DeepCopyState<TState>(ref TState original, ref TState target, bool propertyBased)
|
||||||
public void DeepCopyState<TState>(ref TState original, ref TState target, bool propertyBased)
|
where TState : struct {
|
||||||
where TState : struct {
|
throw new NotImplementedException();
|
||||||
throw new NotImplementedException();
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Transfers the state of one object into another</summary>
|
||||||
/// <summary>Transfers the state of one object into another</summary>
|
/// <typeparam name="TState">Type of the object whose sate will be transferred</typeparam>
|
||||||
/// <typeparam name="TState">Type of the object whose sate will be transferred</typeparam>
|
/// <param name="original">Original instance the state will be taken from</param>
|
||||||
/// <param name="original">Original instance the state will be taken from</param>
|
/// <param name="target">Target instance the state will be written to</param>
|
||||||
/// <param name="target">Target instance the state will be written to</param>
|
/// <param name="propertyBased">Whether to perform a property-based state copy</param>
|
||||||
/// <param name="propertyBased">Whether to perform a property-based state copy</param>
|
public void ShallowCopyState<TState>(TState original, TState target, bool propertyBased)
|
||||||
public void ShallowCopyState<TState>(TState original, TState target, bool propertyBased)
|
where TState : class {
|
||||||
where TState : class {
|
throw new NotImplementedException();
|
||||||
throw new NotImplementedException();
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Transfers the state of one object into another</summary>
|
||||||
/// <summary>Transfers the state of one object into another</summary>
|
/// <typeparam name="TState">Type of the object whose sate will be transferred</typeparam>
|
||||||
/// <typeparam name="TState">Type of the object whose sate will be transferred</typeparam>
|
/// <param name="original">Original instance the state will be taken from</param>
|
||||||
/// <param name="original">Original instance the state will be taken from</param>
|
/// <param name="target">Target instance the state will be written to</param>
|
||||||
/// <param name="target">Target instance the state will be written to</param>
|
/// <param name="propertyBased">Whether to perform a property-based state copy</param>
|
||||||
/// <param name="propertyBased">Whether to perform a property-based state copy</param>
|
public void ShallowCopyState<TState>(ref TState original, ref TState target, bool propertyBased)
|
||||||
public void ShallowCopyState<TState>(ref TState original, ref TState target, bool propertyBased)
|
where TState : struct {
|
||||||
where TState : struct {
|
throw new NotImplementedException();
|
||||||
throw new NotImplementedException();
|
}
|
||||||
}
|
|
||||||
|
#endif
|
||||||
#endif
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Retrieves the existing clone method for the specified type or compiles one if
|
||||||
/// Retrieves the existing clone method for the specified type or compiles one if
|
/// none exists for the type yet
|
||||||
/// none exists for the type yet
|
/// </summary>
|
||||||
/// </summary>
|
/// <param name="clonedType">Type for which a clone method will be retrieved</param>
|
||||||
/// <param name="clonedType">Type for which a clone method will be retrieved</param>
|
/// <returns>The clone method for the specified type</returns>
|
||||||
/// <returns>The clone method for the specified type</returns>
|
private static Func<object, object> getOrCreateShallowFieldBasedCloner(Type clonedType) {
|
||||||
private static Func<object, object> getOrCreateShallowFieldBasedCloner(Type clonedType) {
|
Func<object, object> cloner;
|
||||||
Func<object, object> cloner;
|
|
||||||
|
if(!shallowFieldBasedCloners.TryGetValue(clonedType, out cloner)) {
|
||||||
if(!shallowFieldBasedCloners.TryGetValue(clonedType, out cloner)) {
|
cloner = createShallowFieldBasedCloner(clonedType);
|
||||||
cloner = createShallowFieldBasedCloner(clonedType);
|
shallowFieldBasedCloners.TryAdd(clonedType, cloner);
|
||||||
shallowFieldBasedCloners.TryAdd(clonedType, cloner);
|
}
|
||||||
}
|
|
||||||
|
return cloner;
|
||||||
return cloner;
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Retrieves the existing clone method for the specified type or compiles one if
|
||||||
/// Retrieves the existing clone method for the specified type or compiles one if
|
/// none exists for the type yet
|
||||||
/// none exists for the type yet
|
/// </summary>
|
||||||
/// </summary>
|
/// <param name="clonedType">Type for which a clone method will be retrieved</param>
|
||||||
/// <param name="clonedType">Type for which a clone method will be retrieved</param>
|
/// <returns>The clone method for the specified type</returns>
|
||||||
/// <returns>The clone method for the specified type</returns>
|
private static Func<object, object> getOrCreateDeepFieldBasedCloner(Type clonedType) {
|
||||||
private static Func<object, object> getOrCreateDeepFieldBasedCloner(Type clonedType) {
|
Func<object, object> cloner;
|
||||||
Func<object, object> cloner;
|
|
||||||
|
if(!deepFieldBasedCloners.TryGetValue(clonedType, out cloner)) {
|
||||||
if(!deepFieldBasedCloners.TryGetValue(clonedType, out cloner)) {
|
cloner = createDeepFieldBasedCloner(clonedType);
|
||||||
cloner = createDeepFieldBasedCloner(clonedType);
|
deepFieldBasedCloners.TryAdd(clonedType, cloner);
|
||||||
deepFieldBasedCloners.TryAdd(clonedType, cloner);
|
}
|
||||||
}
|
|
||||||
|
return cloner;
|
||||||
return cloner;
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Retrieves the existing clone method for the specified type or compiles one if
|
||||||
/// Retrieves the existing clone method for the specified type or compiles one if
|
/// none exists for the type yet
|
||||||
/// none exists for the type yet
|
/// </summary>
|
||||||
/// </summary>
|
/// <param name="clonedType">Type for which a clone method will be retrieved</param>
|
||||||
/// <param name="clonedType">Type for which a clone method will be retrieved</param>
|
/// <returns>The clone method for the specified type</returns>
|
||||||
/// <returns>The clone method for the specified type</returns>
|
private static Func<object, object> getOrCreateShallowPropertyBasedCloner(Type clonedType) {
|
||||||
private static Func<object, object> getOrCreateShallowPropertyBasedCloner(Type clonedType) {
|
Func<object, object> cloner;
|
||||||
Func<object, object> cloner;
|
|
||||||
|
if(!shallowPropertyBasedCloners.TryGetValue(clonedType, out cloner)) {
|
||||||
if(!shallowPropertyBasedCloners.TryGetValue(clonedType, out cloner)) {
|
cloner = createShallowPropertyBasedCloner(clonedType);
|
||||||
cloner = createShallowPropertyBasedCloner(clonedType);
|
shallowPropertyBasedCloners.TryAdd(clonedType, cloner);
|
||||||
shallowPropertyBasedCloners.TryAdd(clonedType, cloner);
|
}
|
||||||
}
|
|
||||||
|
return cloner;
|
||||||
return cloner;
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Retrieves the existing clone method for the specified type or compiles one if
|
||||||
/// Retrieves the existing clone method for the specified type or compiles one if
|
/// none exists for the type yet
|
||||||
/// none exists for the type yet
|
/// </summary>
|
||||||
/// </summary>
|
/// <param name="clonedType">Type for which a clone method will be retrieved</param>
|
||||||
/// <param name="clonedType">Type for which a clone method will be retrieved</param>
|
/// <returns>The clone method for the specified type</returns>
|
||||||
/// <returns>The clone method for the specified type</returns>
|
private static Func<object, object> getOrCreateDeepPropertyBasedCloner(Type clonedType) {
|
||||||
private static Func<object, object> getOrCreateDeepPropertyBasedCloner(Type clonedType) {
|
Func<object, object> cloner;
|
||||||
Func<object, object> cloner;
|
|
||||||
|
if(!deepPropertyBasedCloners.TryGetValue(clonedType, out cloner)) {
|
||||||
if(!deepPropertyBasedCloners.TryGetValue(clonedType, out cloner)) {
|
cloner = createDeepPropertyBasedCloner(clonedType);
|
||||||
cloner = createDeepPropertyBasedCloner(clonedType);
|
deepPropertyBasedCloners.TryAdd(clonedType, cloner);
|
||||||
deepPropertyBasedCloners.TryAdd(clonedType, cloner);
|
}
|
||||||
}
|
|
||||||
|
return cloner;
|
||||||
return cloner;
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Compiled cloners that perform shallow clone operations</summary>
|
||||||
/// <summary>Compiled cloners that perform shallow clone operations</summary>
|
private static readonly ConcurrentDictionary<
|
||||||
private static readonly ConcurrentDictionary<
|
Type, Func<object, object>
|
||||||
Type, Func<object, object>
|
> shallowFieldBasedCloners;
|
||||||
> shallowFieldBasedCloners;
|
/// <summary>Compiled cloners that perform deep clone operations</summary>
|
||||||
/// <summary>Compiled cloners that perform deep clone operations</summary>
|
private static readonly ConcurrentDictionary<
|
||||||
private static readonly ConcurrentDictionary<
|
Type, Func<object, object>
|
||||||
Type, Func<object, object>
|
> deepFieldBasedCloners;
|
||||||
> deepFieldBasedCloners;
|
/// <summary>Compiled cloners that perform shallow clone operations</summary>
|
||||||
/// <summary>Compiled cloners that perform shallow clone operations</summary>
|
private static readonly ConcurrentDictionary<
|
||||||
private static readonly ConcurrentDictionary<
|
Type, Func<object, object>
|
||||||
Type, Func<object, object>
|
> shallowPropertyBasedCloners;
|
||||||
> shallowPropertyBasedCloners;
|
/// <summary>Compiled cloners that perform deep clone operations</summary>
|
||||||
/// <summary>Compiled cloners that perform deep clone operations</summary>
|
private static readonly ConcurrentDictionary<
|
||||||
private static readonly ConcurrentDictionary<
|
Type, Func<object, object>
|
||||||
Type, Func<object, object>
|
> deepPropertyBasedCloners;
|
||||||
> deepPropertyBasedCloners;
|
|
||||||
|
}
|
||||||
}
|
|
||||||
|
} // namespace Nuclex.Support.Cloning
|
||||||
} // namespace Nuclex.Support.Cloning
|
|
||||||
|
#endif // !NO_SETS
|
||||||
#endif // !NO_SETS
|
|
||||||
|
|
|
@ -1,100 +1,99 @@
|
||||||
#region CPL License
|
#region Apache License 2.0
|
||||||
/*
|
/*
|
||||||
Nuclex Framework
|
Nuclex .NET Framework
|
||||||
Copyright (C) 2002-2017 Nuclex Development Labs
|
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
|
||||||
|
|
||||||
This library is free software; you can redistribute it and/or
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
modify it under the terms of the IBM Common Public License as
|
you may not use this file except in compliance with the License.
|
||||||
published by the IBM Corporation; either version 1.0 of the
|
You may obtain a copy of the License at
|
||||||
License, or (at your option) any later version.
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
This library is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
Unless required by applicable law or agreed to in writing, software
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
IBM Common Public License for more details.
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
You should have received a copy of the IBM Common Public
|
limitations under the License.
|
||||||
License along with this library
|
*/
|
||||||
*/
|
#endregion // Apache License 2.0
|
||||||
#endregion
|
|
||||||
|
using System;
|
||||||
using System;
|
|
||||||
|
namespace Nuclex.Support.Cloning {
|
||||||
namespace Nuclex.Support.Cloning {
|
|
||||||
|
/// <summary>Constructs new objects by cloning existing objects</summary>
|
||||||
/// <summary>Constructs new objects by cloning existing objects</summary>
|
public interface ICloneFactory {
|
||||||
public interface ICloneFactory {
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Creates a shallow clone of the specified object, reusing any referenced objects
|
||||||
/// Creates a shallow clone of the specified object, reusing any referenced objects
|
/// </summary>
|
||||||
/// </summary>
|
/// <typeparam name="TCloned">Type of the object that will be cloned</typeparam>
|
||||||
/// <typeparam name="TCloned">Type of the object that will be cloned</typeparam>
|
/// <param name="objectToClone">Object that will be cloned</param>
|
||||||
/// <param name="objectToClone">Object that will be cloned</param>
|
/// <returns>A shallow clone of the provided object</returns>
|
||||||
/// <returns>A shallow clone of the provided object</returns>
|
/// <remarks>
|
||||||
/// <remarks>
|
/// Field-based clones are guaranteed to be complete - there will be no missed
|
||||||
/// Field-based clones are guaranteed to be complete - there will be no missed
|
/// members. This type of clone is also able to clone types that do not provide
|
||||||
/// members. This type of clone is also able to clone types that do not provide
|
/// a default constructor.
|
||||||
/// a default constructor.
|
/// </remarks>
|
||||||
/// </remarks>
|
TCloned ShallowFieldClone<TCloned>(TCloned objectToClone);
|
||||||
TCloned ShallowFieldClone<TCloned>(TCloned objectToClone);
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Creates a shallow clone of the specified object, reusing any referenced objects
|
||||||
/// Creates a shallow clone of the specified object, reusing any referenced objects
|
/// </summary>
|
||||||
/// </summary>
|
/// <typeparam name="TCloned">Type of the object that will be cloned</typeparam>
|
||||||
/// <typeparam name="TCloned">Type of the object that will be cloned</typeparam>
|
/// <param name="objectToClone">Object that will be cloned</param>
|
||||||
/// <param name="objectToClone">Object that will be cloned</param>
|
/// <returns>A shallow clone of the provided object</returns>
|
||||||
/// <returns>A shallow clone of the provided object</returns>
|
/// <remarks>
|
||||||
/// <remarks>
|
/// <para>
|
||||||
/// <para>
|
/// A property-based clone is useful if you're using dynamically generated proxies,
|
||||||
/// A property-based clone is useful if you're using dynamically generated proxies,
|
/// such as when working with entities returned by an ORM like NHibernate.
|
||||||
/// such as when working with entities returned by an ORM like NHibernate.
|
/// When not using a property-based clone, internal proxy fields would be cloned
|
||||||
/// When not using a property-based clone, internal proxy fields would be cloned
|
/// and might cause problems with the ORM.
|
||||||
/// and might cause problems with the ORM.
|
/// </para>
|
||||||
/// </para>
|
/// <para>
|
||||||
/// <para>
|
/// Property-based clones require a default constructor because there's no guarantee
|
||||||
/// Property-based clones require a default constructor because there's no guarantee
|
/// that all fields will are assignable through properties and starting with
|
||||||
/// that all fields will are assignable through properties and starting with
|
/// an uninitialized object is likely to end up with a broken clone.
|
||||||
/// an uninitialized object is likely to end up with a broken clone.
|
/// </para>
|
||||||
/// </para>
|
/// </remarks>
|
||||||
/// </remarks>
|
TCloned ShallowPropertyClone<TCloned>(TCloned objectToClone);
|
||||||
TCloned ShallowPropertyClone<TCloned>(TCloned objectToClone);
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Creates a deep clone of the specified object, also creating clones of all
|
||||||
/// Creates a deep clone of the specified object, also creating clones of all
|
/// child objects being referenced
|
||||||
/// child objects being referenced
|
/// </summary>
|
||||||
/// </summary>
|
/// <typeparam name="TCloned">Type of the object that will be cloned</typeparam>
|
||||||
/// <typeparam name="TCloned">Type of the object that will be cloned</typeparam>
|
/// <param name="objectToClone">Object that will be cloned</param>
|
||||||
/// <param name="objectToClone">Object that will be cloned</param>
|
/// <returns>A deep clone of the provided object</returns>
|
||||||
/// <returns>A deep clone of the provided object</returns>
|
/// <remarks>
|
||||||
/// <remarks>
|
/// Field-based clones are guaranteed to be complete - there will be no missed
|
||||||
/// Field-based clones are guaranteed to be complete - there will be no missed
|
/// members. This type of clone is also able to clone types that do not provide
|
||||||
/// members. This type of clone is also able to clone types that do not provide
|
/// a default constructor.
|
||||||
/// a default constructor.
|
/// </remarks>
|
||||||
/// </remarks>
|
TCloned DeepFieldClone<TCloned>(TCloned objectToClone);
|
||||||
TCloned DeepFieldClone<TCloned>(TCloned objectToClone);
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Creates a deep clone of the specified object, also creating clones of all
|
||||||
/// Creates a deep clone of the specified object, also creating clones of all
|
/// child objects being referenced
|
||||||
/// child objects being referenced
|
/// </summary>
|
||||||
/// </summary>
|
/// <typeparam name="TCloned">Type of the object that will be cloned</typeparam>
|
||||||
/// <typeparam name="TCloned">Type of the object that will be cloned</typeparam>
|
/// <param name="objectToClone">Object that will be cloned</param>
|
||||||
/// <param name="objectToClone">Object that will be cloned</param>
|
/// <returns>A deep clone of the provided object</returns>
|
||||||
/// <returns>A deep clone of the provided object</returns>
|
/// <remarks>
|
||||||
/// <remarks>
|
/// <para>
|
||||||
/// <para>
|
/// A property-based clone is useful if you're using dynamically generated proxies,
|
||||||
/// A property-based clone is useful if you're using dynamically generated proxies,
|
/// such as when working with entities returned by an ORM like NHibernate.
|
||||||
/// such as when working with entities returned by an ORM like NHibernate.
|
/// When not using a property-based clone, internal proxy fields would be cloned
|
||||||
/// When not using a property-based clone, internal proxy fields would be cloned
|
/// and might cause problems with the ORM.
|
||||||
/// and might cause problems with the ORM.
|
/// </para>
|
||||||
/// </para>
|
/// <para>
|
||||||
/// <para>
|
/// Property-based clones require a default constructor because there's no guarantee
|
||||||
/// Property-based clones require a default constructor because there's no guarantee
|
/// that all fields will are assignable through properties and starting with
|
||||||
/// that all fields will are assignable through properties and starting with
|
/// an uninitialized object is likely to end up with a broken clone.
|
||||||
/// an uninitialized object is likely to end up with a broken clone.
|
/// </para>
|
||||||
/// </para>
|
/// </remarks>
|
||||||
/// </remarks>
|
TCloned DeepPropertyClone<TCloned>(TCloned objectToClone);
|
||||||
TCloned DeepPropertyClone<TCloned>(TCloned objectToClone);
|
|
||||||
|
}
|
||||||
}
|
|
||||||
|
} // namespace Nuclex.Support.Cloning
|
||||||
} // namespace Nuclex.Support.Cloning
|
|
||||||
|
|
|
@ -1,90 +1,89 @@
|
||||||
#region CPL License
|
#region Apache License 2.0
|
||||||
/*
|
/*
|
||||||
Nuclex Framework
|
Nuclex .NET Framework
|
||||||
Copyright (C) 2002-2017 Nuclex Development Labs
|
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
|
||||||
|
|
||||||
This library is free software; you can redistribute it and/or
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
modify it under the terms of the IBM Common Public License as
|
you may not use this file except in compliance with the License.
|
||||||
published by the IBM Corporation; either version 1.0 of the
|
You may obtain a copy of the License at
|
||||||
License, or (at your option) any later version.
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
This library is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
Unless required by applicable law or agreed to in writing, software
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
IBM Common Public License for more details.
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
You should have received a copy of the IBM Common Public
|
limitations under the License.
|
||||||
License along with this library
|
*/
|
||||||
*/
|
#endregion // Apache License 2.0
|
||||||
#endregion
|
|
||||||
|
using System;
|
||||||
using System;
|
|
||||||
|
namespace Nuclex.Support.Cloning {
|
||||||
namespace Nuclex.Support.Cloning {
|
|
||||||
|
/// <summary>Copies the state of objects</summary>
|
||||||
/// <summary>Copies the state of objects</summary>
|
public interface IStateCopier {
|
||||||
public interface IStateCopier {
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Transfers the state of one object into another, creating clones of referenced objects
|
||||||
/// Transfers the state of one object into another, creating clones of referenced objects
|
/// </summary>
|
||||||
/// </summary>
|
/// <typeparam name="TState">Type of the object whose sate will be transferred</typeparam>
|
||||||
/// <typeparam name="TState">Type of the object whose sate will be transferred</typeparam>
|
/// <param name="original">Original instance the state will be taken from</param>
|
||||||
/// <param name="original">Original instance the state will be taken from</param>
|
/// <param name="target">Target instance the state will be written to</param>
|
||||||
/// <param name="target">Target instance the state will be written to</param>
|
/// <param name="propertyBased">Whether to perform a property-based state copy</param>
|
||||||
/// <param name="propertyBased">Whether to perform a property-based state copy</param>
|
/// <remarks>
|
||||||
/// <remarks>
|
/// A property-based copy is useful if you're using dynamically generated proxies,
|
||||||
/// A property-based copy is useful if you're using dynamically generated proxies,
|
/// such as when working with entities returned by an ORM like NHibernate.
|
||||||
/// such as when working with entities returned by an ORM like NHibernate.
|
/// When not using a property-based copy, internal proxy fields would be copied
|
||||||
/// When not using a property-based copy, internal proxy fields would be copied
|
/// and might cause problems with the ORM.
|
||||||
/// and might cause problems with the ORM.
|
/// </remarks>
|
||||||
/// </remarks>
|
void DeepCopyState<TState>(TState original, TState target, bool propertyBased)
|
||||||
void DeepCopyState<TState>(TState original, TState target, bool propertyBased)
|
where TState : class;
|
||||||
where TState : class;
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Transfers the state of one object into another, creating clones of referenced objects
|
||||||
/// Transfers the state of one object into another, creating clones of referenced objects
|
/// </summary>
|
||||||
/// </summary>
|
/// <typeparam name="TState">Type of the object whose sate will be transferred</typeparam>
|
||||||
/// <typeparam name="TState">Type of the object whose sate will be transferred</typeparam>
|
/// <param name="original">Original instance the state will be taken from</param>
|
||||||
/// <param name="original">Original instance the state will be taken from</param>
|
/// <param name="target">Target instance the state will be written to</param>
|
||||||
/// <param name="target">Target instance the state will be written to</param>
|
/// <param name="propertyBased">Whether to perform a property-based state copy</param>
|
||||||
/// <param name="propertyBased">Whether to perform a property-based state copy</param>
|
/// <remarks>
|
||||||
/// <remarks>
|
/// A property-based copy is useful if you're using dynamically generated proxies,
|
||||||
/// A property-based copy is useful if you're using dynamically generated proxies,
|
/// such as when working with entities returned by an ORM like NHibernate.
|
||||||
/// such as when working with entities returned by an ORM like NHibernate.
|
/// When not using a property-based copy, internal proxy fields would be copied
|
||||||
/// When not using a property-based copy, internal proxy fields would be copied
|
/// and might cause problems with the ORM.
|
||||||
/// and might cause problems with the ORM.
|
/// </remarks>
|
||||||
/// </remarks>
|
void DeepCopyState<TState>(ref TState original, ref TState target, bool propertyBased)
|
||||||
void DeepCopyState<TState>(ref TState original, ref TState target, bool propertyBased)
|
where TState : struct;
|
||||||
where TState : struct;
|
|
||||||
|
/// <summary>Transfers the state of one object into another</summary>
|
||||||
/// <summary>Transfers the state of one object into another</summary>
|
/// <typeparam name="TState">Type of the object whose sate will be transferred</typeparam>
|
||||||
/// <typeparam name="TState">Type of the object whose sate will be transferred</typeparam>
|
/// <param name="original">Original instance the state will be taken from</param>
|
||||||
/// <param name="original">Original instance the state will be taken from</param>
|
/// <param name="target">Target instance the state will be written to</param>
|
||||||
/// <param name="target">Target instance the state will be written to</param>
|
/// <param name="propertyBased">Whether to perform a property-based state copy</param>
|
||||||
/// <param name="propertyBased">Whether to perform a property-based state copy</param>
|
/// <remarks>
|
||||||
/// <remarks>
|
/// A property-based copy is useful if you're using dynamically generated proxies,
|
||||||
/// A property-based copy is useful if you're using dynamically generated proxies,
|
/// such as when working with entities returned by an ORM like NHibernate.
|
||||||
/// such as when working with entities returned by an ORM like NHibernate.
|
/// When not using a property-based copy, internal proxy fields would be copied
|
||||||
/// When not using a property-based copy, internal proxy fields would be copied
|
/// and might cause problems with the ORM.
|
||||||
/// and might cause problems with the ORM.
|
/// </remarks>
|
||||||
/// </remarks>
|
void ShallowCopyState<TState>(TState original, TState target, bool propertyBased)
|
||||||
void ShallowCopyState<TState>(TState original, TState target, bool propertyBased)
|
where TState : class;
|
||||||
where TState : class;
|
|
||||||
|
/// <summary>Transfers the state of one object into another</summary>
|
||||||
/// <summary>Transfers the state of one object into another</summary>
|
/// <typeparam name="TState">Type of the object whose sate will be transferred</typeparam>
|
||||||
/// <typeparam name="TState">Type of the object whose sate will be transferred</typeparam>
|
/// <param name="original">Original instance the state will be taken from</param>
|
||||||
/// <param name="original">Original instance the state will be taken from</param>
|
/// <param name="target">Target instance the state will be written to</param>
|
||||||
/// <param name="target">Target instance the state will be written to</param>
|
/// <param name="propertyBased">Whether to perform a property-based state copy</param>
|
||||||
/// <param name="propertyBased">Whether to perform a property-based state copy</param>
|
/// <remarks>
|
||||||
/// <remarks>
|
/// A property-based copy is useful if you're using dynamically generated proxies,
|
||||||
/// A property-based copy is useful if you're using dynamically generated proxies,
|
/// such as when working with entities returned by an ORM like NHibernate.
|
||||||
/// such as when working with entities returned by an ORM like NHibernate.
|
/// When not using a property-based copy, internal proxy fields would be copied
|
||||||
/// When not using a property-based copy, internal proxy fields would be copied
|
/// and might cause problems with the ORM.
|
||||||
/// and might cause problems with the ORM.
|
/// </remarks>
|
||||||
/// </remarks>
|
void ShallowCopyState<TState>(ref TState original, ref TState target, bool propertyBased)
|
||||||
void ShallowCopyState<TState>(ref TState original, ref TState target, bool propertyBased)
|
where TState : struct;
|
||||||
where TState : struct;
|
|
||||||
|
}
|
||||||
}
|
|
||||||
|
} // namespace Nuclex.Support.Cloning
|
||||||
} // namespace Nuclex.Support.Cloning
|
|
||||||
|
|
|
@ -1,199 +1,198 @@
|
||||||
#region CPL License
|
#region Apache License 2.0
|
||||||
/*
|
/*
|
||||||
Nuclex Framework
|
Nuclex .NET Framework
|
||||||
Copyright (C) 2002-2017 Nuclex Development Labs
|
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
|
||||||
|
|
||||||
This library is free software; you can redistribute it and/or
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
modify it under the terms of the IBM Common Public License as
|
you may not use this file except in compliance with the License.
|
||||||
published by the IBM Corporation; either version 1.0 of the
|
You may obtain a copy of the License at
|
||||||
License, or (at your option) any later version.
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
This library is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
Unless required by applicable law or agreed to in writing, software
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
IBM Common Public License for more details.
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
You should have received a copy of the IBM Common Public
|
limitations under the License.
|
||||||
License along with this library
|
*/
|
||||||
*/
|
#endregion // Apache License 2.0
|
||||||
#endregion
|
|
||||||
|
#if UNITTEST
|
||||||
#if UNITTEST
|
|
||||||
|
using System;
|
||||||
using System;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Generic;
|
|
||||||
|
using NUnit.Framework;
|
||||||
using NUnit.Framework;
|
|
||||||
|
namespace Nuclex.Support.Cloning {
|
||||||
namespace Nuclex.Support.Cloning {
|
|
||||||
|
/// <summary>Unit Test for the reflection-based cloner</summary>
|
||||||
/// <summary>Unit Test for the reflection-based cloner</summary>
|
[TestFixture]
|
||||||
[TestFixture]
|
internal class ReflectionClonerTest : CloneFactoryTest {
|
||||||
internal class ReflectionClonerTest : CloneFactoryTest {
|
|
||||||
|
/// <summary>Initializes a new unit test suite for the reflection cloner</summary>
|
||||||
/// <summary>Initializes a new unit test suite for the reflection cloner</summary>
|
public ReflectionClonerTest() {
|
||||||
public ReflectionClonerTest() {
|
this.cloneFactory = new ReflectionCloner();
|
||||||
this.cloneFactory = new ReflectionCloner();
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Verifies that cloning a null object simply returns null</summary>
|
||||||
/// <summary>Verifies that cloning a null object simply returns null</summary>
|
[Test]
|
||||||
[Test]
|
public void CloningNullYieldsNull() {
|
||||||
public void CloningNullYieldsNull() {
|
Assert.IsNull(this.cloneFactory.DeepFieldClone<object>(null));
|
||||||
Assert.IsNull(this.cloneFactory.DeepFieldClone<object>(null));
|
Assert.IsNull(this.cloneFactory.DeepPropertyClone<object>(null));
|
||||||
Assert.IsNull(this.cloneFactory.DeepPropertyClone<object>(null));
|
Assert.IsNull(this.cloneFactory.ShallowFieldClone<object>(null));
|
||||||
Assert.IsNull(this.cloneFactory.ShallowFieldClone<object>(null));
|
Assert.IsNull(this.cloneFactory.ShallowPropertyClone<object>(null));
|
||||||
Assert.IsNull(this.cloneFactory.ShallowPropertyClone<object>(null));
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Verifies that clones of objects whose class doesn't possess a default constructor
|
||||||
/// Verifies that clones of objects whose class doesn't possess a default constructor
|
/// can be made
|
||||||
/// can be made
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void ClassWithoutDefaultConstructorCanBeCloned() {
|
||||||
public void ClassWithoutDefaultConstructorCanBeCloned() {
|
var original = new ClassWithoutDefaultConstructor(1234);
|
||||||
var original = new ClassWithoutDefaultConstructor(1234);
|
ClassWithoutDefaultConstructor clone = this.cloneFactory.DeepFieldClone(original);
|
||||||
ClassWithoutDefaultConstructor clone = this.cloneFactory.DeepFieldClone(original);
|
|
||||||
|
Assert.AreNotSame(original, clone);
|
||||||
Assert.AreNotSame(original, clone);
|
Assert.AreEqual(original.Dummy, clone.Dummy);
|
||||||
Assert.AreEqual(original.Dummy, clone.Dummy);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Verifies that clones of primitive types can be created</summary>
|
||||||
/// <summary>Verifies that clones of primitive types can be created</summary>
|
[Test]
|
||||||
[Test]
|
public void PrimitiveTypesCanBeCloned() {
|
||||||
public void PrimitiveTypesCanBeCloned() {
|
int original = 12345;
|
||||||
int original = 12345;
|
int clone = this.cloneFactory.ShallowFieldClone(original);
|
||||||
int clone = this.cloneFactory.ShallowFieldClone(original);
|
Assert.AreEqual(original, clone);
|
||||||
Assert.AreEqual(original, clone);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Verifies that shallow clones of arrays can be made</summary>
|
||||||
/// <summary>Verifies that shallow clones of arrays can be made</summary>
|
[Test]
|
||||||
[Test]
|
public void ShallowClonesOfArraysCanBeMade() {
|
||||||
public void ShallowClonesOfArraysCanBeMade() {
|
var original = new TestReferenceType[] {
|
||||||
var original = new TestReferenceType[] {
|
new TestReferenceType() { TestField = 123, TestProperty = 456 }
|
||||||
new TestReferenceType() { TestField = 123, TestProperty = 456 }
|
};
|
||||||
};
|
TestReferenceType[] clone = this.cloneFactory.ShallowFieldClone(original);
|
||||||
TestReferenceType[] clone = this.cloneFactory.ShallowFieldClone(original);
|
|
||||||
|
Assert.AreSame(original[0], clone[0]);
|
||||||
Assert.AreSame(original[0], clone[0]);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Verifies that deep clones of arrays can be made</summary>
|
||||||
/// <summary>Verifies that deep clones of arrays can be made</summary>
|
[Test]
|
||||||
[Test]
|
public void DeepClonesOfArraysCanBeMade() {
|
||||||
public void DeepClonesOfArraysCanBeMade() {
|
var original = new TestReferenceType[] {
|
||||||
var original = new TestReferenceType[] {
|
new TestReferenceType() { TestField = 123, TestProperty = 456 }
|
||||||
new TestReferenceType() { TestField = 123, TestProperty = 456 }
|
};
|
||||||
};
|
TestReferenceType[] clone = this.cloneFactory.DeepFieldClone(original);
|
||||||
TestReferenceType[] clone = this.cloneFactory.DeepFieldClone(original);
|
|
||||||
|
Assert.AreNotSame(original[0], clone[0]);
|
||||||
Assert.AreNotSame(original[0], clone[0]);
|
Assert.AreEqual(original[0].TestField, clone[0].TestField);
|
||||||
Assert.AreEqual(original[0].TestField, clone[0].TestField);
|
Assert.AreEqual(original[0].TestProperty, clone[0].TestProperty);
|
||||||
Assert.AreEqual(original[0].TestProperty, clone[0].TestProperty);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Verifies that deep clones of a generic list can be made</summary>
|
||||||
/// <summary>Verifies that deep clones of a generic list can be made</summary>
|
[Test]
|
||||||
[Test]
|
public void GenericListsCanBeCloned() {
|
||||||
public void GenericListsCanBeCloned() {
|
var original = new List<int>(new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 });
|
||||||
var original = new List<int>(new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 });
|
List<int> clone = this.cloneFactory.DeepFieldClone(original);
|
||||||
List<int> clone = this.cloneFactory.DeepFieldClone(original);
|
|
||||||
|
CollectionAssert.AreEqual(original, clone);
|
||||||
CollectionAssert.AreEqual(original, clone);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Verifies that deep clones of a generic dictionary can be made</summary>
|
||||||
/// <summary>Verifies that deep clones of a generic dictionary can be made</summary>
|
[Test]
|
||||||
[Test]
|
public void GenericDictionariesCanBeCloned() {
|
||||||
public void GenericDictionariesCanBeCloned() {
|
var original = new Dictionary<int, string>();
|
||||||
var original = new Dictionary<int, string>();
|
original.Add(1, "one");
|
||||||
original.Add(1, "one");
|
Dictionary<int, string> clone = this.cloneFactory.DeepFieldClone(original);
|
||||||
Dictionary<int, string> clone = this.cloneFactory.DeepFieldClone(original);
|
|
||||||
|
Assert.AreEqual("one", clone[1]);
|
||||||
Assert.AreEqual("one", clone[1]);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Verifies that a field-based shallow clone of a value type can be performed
|
||||||
/// Verifies that a field-based shallow clone of a value type can be performed
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void ShallowFieldBasedClonesOfValueTypesCanBeMade() {
|
||||||
public void ShallowFieldBasedClonesOfValueTypesCanBeMade() {
|
HierarchicalValueType original = CreateValueType();
|
||||||
HierarchicalValueType original = CreateValueType();
|
HierarchicalValueType clone = this.cloneFactory.ShallowFieldClone(original);
|
||||||
HierarchicalValueType clone = this.cloneFactory.ShallowFieldClone(original);
|
VerifyClone(ref original, ref clone, isDeepClone: false, isPropertyBasedClone: false);
|
||||||
VerifyClone(ref original, ref clone, isDeepClone: false, isPropertyBasedClone: false);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Verifies that a field-based shallow clone of a reference type can be performed
|
||||||
/// Verifies that a field-based shallow clone of a reference type can be performed
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void ShallowFieldBasedClonesOfReferenceTypesCanBeMade() {
|
||||||
public void ShallowFieldBasedClonesOfReferenceTypesCanBeMade() {
|
HierarchicalReferenceType original = CreateReferenceType();
|
||||||
HierarchicalReferenceType original = CreateReferenceType();
|
HierarchicalReferenceType clone = this.cloneFactory.ShallowFieldClone(original);
|
||||||
HierarchicalReferenceType clone = this.cloneFactory.ShallowFieldClone(original);
|
VerifyClone(original, clone, isDeepClone: false, isPropertyBasedClone: false);
|
||||||
VerifyClone(original, clone, isDeepClone: false, isPropertyBasedClone: false);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Verifies that a field-based deep clone of a value type can be performed
|
||||||
/// Verifies that a field-based deep clone of a value type can be performed
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void DeepFieldBasedClonesOfValueTypesCanBeMade() {
|
||||||
public void DeepFieldBasedClonesOfValueTypesCanBeMade() {
|
HierarchicalValueType original = CreateValueType();
|
||||||
HierarchicalValueType original = CreateValueType();
|
HierarchicalValueType clone = this.cloneFactory.DeepFieldClone(original);
|
||||||
HierarchicalValueType clone = this.cloneFactory.DeepFieldClone(original);
|
VerifyClone(ref original, ref clone, isDeepClone: true, isPropertyBasedClone: false);
|
||||||
VerifyClone(ref original, ref clone, isDeepClone: true, isPropertyBasedClone: false);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Verifies that a field-based deep clone of a reference type can be performed
|
||||||
/// Verifies that a field-based deep clone of a reference type can be performed
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void DeepFieldBasedClonesOfReferenceTypesCanBeMade() {
|
||||||
public void DeepFieldBasedClonesOfReferenceTypesCanBeMade() {
|
HierarchicalReferenceType original = CreateReferenceType();
|
||||||
HierarchicalReferenceType original = CreateReferenceType();
|
HierarchicalReferenceType clone = this.cloneFactory.DeepFieldClone(original);
|
||||||
HierarchicalReferenceType clone = this.cloneFactory.DeepFieldClone(original);
|
VerifyClone(original, clone, isDeepClone: true, isPropertyBasedClone: false);
|
||||||
VerifyClone(original, clone, isDeepClone: true, isPropertyBasedClone: false);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Verifies that a property-based shallow clone of a value type can be performed
|
||||||
/// Verifies that a property-based shallow clone of a value type can be performed
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void ShallowPropertyBasedClonesOfValueTypesCanBeMade() {
|
||||||
public void ShallowPropertyBasedClonesOfValueTypesCanBeMade() {
|
HierarchicalValueType original = CreateValueType();
|
||||||
HierarchicalValueType original = CreateValueType();
|
HierarchicalValueType clone = this.cloneFactory.ShallowPropertyClone(original);
|
||||||
HierarchicalValueType clone = this.cloneFactory.ShallowPropertyClone(original);
|
VerifyClone(ref original, ref clone, isDeepClone: false, isPropertyBasedClone: true);
|
||||||
VerifyClone(ref original, ref clone, isDeepClone: false, isPropertyBasedClone: true);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Verifies that a property-based shallow clone of a reference type can be performed
|
||||||
/// Verifies that a property-based shallow clone of a reference type can be performed
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void ShallowPropertyBasedClonesOfReferenceTypesCanBeMade() {
|
||||||
public void ShallowPropertyBasedClonesOfReferenceTypesCanBeMade() {
|
HierarchicalReferenceType original = CreateReferenceType();
|
||||||
HierarchicalReferenceType original = CreateReferenceType();
|
HierarchicalReferenceType clone = this.cloneFactory.ShallowPropertyClone(original);
|
||||||
HierarchicalReferenceType clone = this.cloneFactory.ShallowPropertyClone(original);
|
VerifyClone(original, clone, isDeepClone: false, isPropertyBasedClone: true);
|
||||||
VerifyClone(original, clone, isDeepClone: false, isPropertyBasedClone: true);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Verifies that a property-based deep clone of a value type can be performed
|
||||||
/// Verifies that a property-based deep clone of a value type can be performed
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void DeepPropertyBasedClonesOfValueTypesCanBeMade() {
|
||||||
public void DeepPropertyBasedClonesOfValueTypesCanBeMade() {
|
HierarchicalValueType original = CreateValueType();
|
||||||
HierarchicalValueType original = CreateValueType();
|
HierarchicalValueType clone = this.cloneFactory.DeepPropertyClone(original);
|
||||||
HierarchicalValueType clone = this.cloneFactory.DeepPropertyClone(original);
|
VerifyClone(ref original, ref clone, isDeepClone: true, isPropertyBasedClone: true);
|
||||||
VerifyClone(ref original, ref clone, isDeepClone: true, isPropertyBasedClone: true);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Verifies that a property-based deep clone of a reference type can be performed
|
||||||
/// Verifies that a property-based deep clone of a reference type can be performed
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void DeepPropertyBasedClonesOfReferenceTypesCanBeMade() {
|
||||||
public void DeepPropertyBasedClonesOfReferenceTypesCanBeMade() {
|
HierarchicalReferenceType original = CreateReferenceType();
|
||||||
HierarchicalReferenceType original = CreateReferenceType();
|
HierarchicalReferenceType clone = this.cloneFactory.DeepPropertyClone(original);
|
||||||
HierarchicalReferenceType clone = this.cloneFactory.DeepPropertyClone(original);
|
VerifyClone(original, clone, isDeepClone: true, isPropertyBasedClone: true);
|
||||||
VerifyClone(original, clone, isDeepClone: true, isPropertyBasedClone: true);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Clone factory being tested</summary>
|
||||||
/// <summary>Clone factory being tested</summary>
|
private ICloneFactory cloneFactory;
|
||||||
private ICloneFactory cloneFactory;
|
|
||||||
|
}
|
||||||
}
|
|
||||||
|
} // namespace Nuclex.Support.Cloning
|
||||||
} // namespace Nuclex.Support.Cloning
|
|
||||||
|
#endif // UNITTEST
|
||||||
#endif // UNITTEST
|
|
||||||
|
|
|
@ -1,451 +1,450 @@
|
||||||
#region CPL License
|
#region Apache License 2.0
|
||||||
/*
|
/*
|
||||||
Nuclex Framework
|
Nuclex .NET Framework
|
||||||
Copyright (C) 2002-2017 Nuclex Development Labs
|
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
|
||||||
|
|
||||||
This library is free software; you can redistribute it and/or
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
modify it under the terms of the IBM Common Public License as
|
you may not use this file except in compliance with the License.
|
||||||
published by the IBM Corporation; either version 1.0 of the
|
You may obtain a copy of the License at
|
||||||
License, or (at your option) any later version.
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
This library is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
Unless required by applicable law or agreed to in writing, software
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
IBM Common Public License for more details.
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
You should have received a copy of the IBM Common Public
|
limitations under the License.
|
||||||
License along with this library
|
*/
|
||||||
*/
|
#endregion // Apache License 2.0
|
||||||
#endregion
|
|
||||||
|
using System;
|
||||||
using System;
|
using System.Reflection;
|
||||||
using System.Reflection;
|
using System.Runtime.Serialization;
|
||||||
using System.Runtime.Serialization;
|
|
||||||
|
namespace Nuclex.Support.Cloning {
|
||||||
namespace Nuclex.Support.Cloning {
|
|
||||||
|
/// <summary>Clones objects using reflection</summary>
|
||||||
/// <summary>Clones objects using reflection</summary>
|
/// <remarks>
|
||||||
/// <remarks>
|
/// <para>
|
||||||
/// <para>
|
/// This type of cloning is a lot faster than cloning by serialization and
|
||||||
/// This type of cloning is a lot faster than cloning by serialization and
|
/// incurs no set-up cost, but requires cloned types to provide a default
|
||||||
/// incurs no set-up cost, but requires cloned types to provide a default
|
/// constructor in order to work.
|
||||||
/// constructor in order to work.
|
/// </para>
|
||||||
/// </para>
|
/// </remarks>
|
||||||
/// </remarks>
|
public class ReflectionCloner : ICloneFactory {
|
||||||
public class ReflectionCloner : ICloneFactory {
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Creates a shallow clone of the specified object, reusing any referenced objects
|
||||||
/// Creates a shallow clone of the specified object, reusing any referenced objects
|
/// </summary>
|
||||||
/// </summary>
|
/// <typeparam name="TCloned">Type of the object that will be cloned</typeparam>
|
||||||
/// <typeparam name="TCloned">Type of the object that will be cloned</typeparam>
|
/// <param name="objectToClone">Object that will be cloned</param>
|
||||||
/// <param name="objectToClone">Object that will be cloned</param>
|
/// <returns>A shallow clone of the provided object</returns>
|
||||||
/// <returns>A shallow clone of the provided object</returns>
|
public static TCloned ShallowFieldClone<TCloned>(TCloned objectToClone) {
|
||||||
public static TCloned ShallowFieldClone<TCloned>(TCloned objectToClone) {
|
Type originalType = objectToClone.GetType();
|
||||||
Type originalType = objectToClone.GetType();
|
if(originalType.IsPrimitive || (originalType == typeof(string))) {
|
||||||
if(originalType.IsPrimitive || (originalType == typeof(string))) {
|
return objectToClone; // Being value types, primitives are copied by default
|
||||||
return objectToClone; // Being value types, primitives are copied by default
|
} else if(originalType.IsArray) {
|
||||||
} else if(originalType.IsArray) {
|
return (TCloned)shallowCloneArray(objectToClone);
|
||||||
return (TCloned)shallowCloneArray(objectToClone);
|
} else if(originalType.IsValueType) {
|
||||||
} else if(originalType.IsValueType) {
|
return objectToClone; // Value types can be copied directly
|
||||||
return objectToClone; // Value types can be copied directly
|
} else {
|
||||||
} else {
|
return (TCloned)shallowCloneComplexFieldBased(objectToClone);
|
||||||
return (TCloned)shallowCloneComplexFieldBased(objectToClone);
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Creates a shallow clone of the specified object, reusing any referenced objects
|
||||||
/// Creates a shallow clone of the specified object, reusing any referenced objects
|
/// </summary>
|
||||||
/// </summary>
|
/// <typeparam name="TCloned">Type of the object that will be cloned</typeparam>
|
||||||
/// <typeparam name="TCloned">Type of the object that will be cloned</typeparam>
|
/// <param name="objectToClone">Object that will be cloned</param>
|
||||||
/// <param name="objectToClone">Object that will be cloned</param>
|
/// <returns>A shallow clone of the provided object</returns>
|
||||||
/// <returns>A shallow clone of the provided object</returns>
|
public static TCloned ShallowPropertyClone<TCloned>(TCloned objectToClone) {
|
||||||
public static TCloned ShallowPropertyClone<TCloned>(TCloned objectToClone) {
|
Type originalType = objectToClone.GetType();
|
||||||
Type originalType = objectToClone.GetType();
|
if(originalType.IsPrimitive || (originalType == typeof(string))) {
|
||||||
if(originalType.IsPrimitive || (originalType == typeof(string))) {
|
return objectToClone; // Being value types, primitives are copied by default
|
||||||
return objectToClone; // Being value types, primitives are copied by default
|
} else if(originalType.IsArray) {
|
||||||
} else if(originalType.IsArray) {
|
return (TCloned)shallowCloneArray(objectToClone);
|
||||||
return (TCloned)shallowCloneArray(objectToClone);
|
} else if(originalType.IsValueType) {
|
||||||
} else if(originalType.IsValueType) {
|
return (TCloned)shallowCloneComplexPropertyBased(objectToClone);
|
||||||
return (TCloned)shallowCloneComplexPropertyBased(objectToClone);
|
} else {
|
||||||
} else {
|
return (TCloned)shallowCloneComplexPropertyBased(objectToClone);
|
||||||
return (TCloned)shallowCloneComplexPropertyBased(objectToClone);
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Creates a deep clone of the specified object, also creating clones of all
|
||||||
/// Creates a deep clone of the specified object, also creating clones of all
|
/// child objects being referenced
|
||||||
/// child objects being referenced
|
/// </summary>
|
||||||
/// </summary>
|
/// <typeparam name="TCloned">Type of the object that will be cloned</typeparam>
|
||||||
/// <typeparam name="TCloned">Type of the object that will be cloned</typeparam>
|
/// <param name="objectToClone">Object that will be cloned</param>
|
||||||
/// <param name="objectToClone">Object that will be cloned</param>
|
/// <returns>A deep clone of the provided object</returns>
|
||||||
/// <returns>A deep clone of the provided object</returns>
|
public static TCloned DeepFieldClone<TCloned>(TCloned objectToClone) {
|
||||||
public static TCloned DeepFieldClone<TCloned>(TCloned objectToClone) {
|
object objectToCloneAsObject = objectToClone;
|
||||||
object objectToCloneAsObject = objectToClone;
|
if(objectToClone == null) {
|
||||||
if(objectToClone == null) {
|
return default(TCloned);
|
||||||
return default(TCloned);
|
} else {
|
||||||
} else {
|
return (TCloned)deepCloneSingleFieldBased(objectToCloneAsObject);
|
||||||
return (TCloned)deepCloneSingleFieldBased(objectToCloneAsObject);
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Creates a deep clone of the specified object, also creating clones of all
|
||||||
/// Creates a deep clone of the specified object, also creating clones of all
|
/// child objects being referenced
|
||||||
/// child objects being referenced
|
/// </summary>
|
||||||
/// </summary>
|
/// <typeparam name="TCloned">Type of the object that will be cloned</typeparam>
|
||||||
/// <typeparam name="TCloned">Type of the object that will be cloned</typeparam>
|
/// <param name="objectToClone">Object that will be cloned</param>
|
||||||
/// <param name="objectToClone">Object that will be cloned</param>
|
/// <returns>A deep clone of the provided object</returns>
|
||||||
/// <returns>A deep clone of the provided object</returns>
|
public static TCloned DeepPropertyClone<TCloned>(TCloned objectToClone) {
|
||||||
public static TCloned DeepPropertyClone<TCloned>(TCloned objectToClone) {
|
object objectToCloneAsObject = objectToClone;
|
||||||
object objectToCloneAsObject = objectToClone;
|
if(objectToClone == null) {
|
||||||
if(objectToClone == null) {
|
return default(TCloned);
|
||||||
return default(TCloned);
|
} else {
|
||||||
} else {
|
return (TCloned)deepCloneSinglePropertyBased(objectToCloneAsObject);
|
||||||
return (TCloned)deepCloneSinglePropertyBased(objectToCloneAsObject);
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Creates a shallow clone of the specified object, reusing any referenced objects
|
||||||
/// Creates a shallow clone of the specified object, reusing any referenced objects
|
/// </summary>
|
||||||
/// </summary>
|
/// <typeparam name="TCloned">Type of the object that will be cloned</typeparam>
|
||||||
/// <typeparam name="TCloned">Type of the object that will be cloned</typeparam>
|
/// <param name="objectToClone">Object that will be cloned</param>
|
||||||
/// <param name="objectToClone">Object that will be cloned</param>
|
/// <returns>A shallow clone of the provided object</returns>
|
||||||
/// <returns>A shallow clone of the provided object</returns>
|
TCloned ICloneFactory.ShallowFieldClone<TCloned>(TCloned objectToClone) {
|
||||||
TCloned ICloneFactory.ShallowFieldClone<TCloned>(TCloned objectToClone) {
|
if(typeof(TCloned).IsClass || typeof(TCloned).IsArray) {
|
||||||
if(typeof(TCloned).IsClass || typeof(TCloned).IsArray) {
|
if(ReferenceEquals(objectToClone, null)) {
|
||||||
if(ReferenceEquals(objectToClone, null)) {
|
return default(TCloned);
|
||||||
return default(TCloned);
|
}
|
||||||
}
|
}
|
||||||
}
|
return ReflectionCloner.ShallowFieldClone<TCloned>(objectToClone);
|
||||||
return ReflectionCloner.ShallowFieldClone<TCloned>(objectToClone);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Creates a shallow clone of the specified object, reusing any referenced objects
|
||||||
/// Creates a shallow clone of the specified object, reusing any referenced objects
|
/// </summary>
|
||||||
/// </summary>
|
/// <typeparam name="TCloned">Type of the object that will be cloned</typeparam>
|
||||||
/// <typeparam name="TCloned">Type of the object that will be cloned</typeparam>
|
/// <param name="objectToClone">Object that will be cloned</param>
|
||||||
/// <param name="objectToClone">Object that will be cloned</param>
|
/// <returns>A shallow clone of the provided object</returns>
|
||||||
/// <returns>A shallow clone of the provided object</returns>
|
TCloned ICloneFactory.ShallowPropertyClone<TCloned>(TCloned objectToClone) {
|
||||||
TCloned ICloneFactory.ShallowPropertyClone<TCloned>(TCloned objectToClone) {
|
if(typeof(TCloned).IsClass || typeof(TCloned).IsArray) {
|
||||||
if(typeof(TCloned).IsClass || typeof(TCloned).IsArray) {
|
if(ReferenceEquals(objectToClone, null)) {
|
||||||
if(ReferenceEquals(objectToClone, null)) {
|
return default(TCloned);
|
||||||
return default(TCloned);
|
}
|
||||||
}
|
}
|
||||||
}
|
return ReflectionCloner.ShallowPropertyClone<TCloned>(objectToClone);
|
||||||
return ReflectionCloner.ShallowPropertyClone<TCloned>(objectToClone);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Creates a deep clone of the specified object, also creating clones of all
|
||||||
/// Creates a deep clone of the specified object, also creating clones of all
|
/// child objects being referenced
|
||||||
/// child objects being referenced
|
/// </summary>
|
||||||
/// </summary>
|
/// <typeparam name="TCloned">Type of the object that will be cloned</typeparam>
|
||||||
/// <typeparam name="TCloned">Type of the object that will be cloned</typeparam>
|
/// <param name="objectToClone">Object that will be cloned</param>
|
||||||
/// <param name="objectToClone">Object that will be cloned</param>
|
/// <returns>A deep clone of the provided object</returns>
|
||||||
/// <returns>A deep clone of the provided object</returns>
|
TCloned ICloneFactory.DeepFieldClone<TCloned>(TCloned objectToClone) {
|
||||||
TCloned ICloneFactory.DeepFieldClone<TCloned>(TCloned objectToClone) {
|
return ReflectionCloner.DeepFieldClone<TCloned>(objectToClone);
|
||||||
return ReflectionCloner.DeepFieldClone<TCloned>(objectToClone);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Creates a deep clone of the specified object, also creating clones of all
|
||||||
/// Creates a deep clone of the specified object, also creating clones of all
|
/// child objects being referenced
|
||||||
/// child objects being referenced
|
/// </summary>
|
||||||
/// </summary>
|
/// <typeparam name="TCloned">Type of the object that will be cloned</typeparam>
|
||||||
/// <typeparam name="TCloned">Type of the object that will be cloned</typeparam>
|
/// <param name="objectToClone">Object that will be cloned</param>
|
||||||
/// <param name="objectToClone">Object that will be cloned</param>
|
/// <returns>A deep clone of the provided object</returns>
|
||||||
/// <returns>A deep clone of the provided object</returns>
|
TCloned ICloneFactory.DeepPropertyClone<TCloned>(TCloned objectToClone) {
|
||||||
TCloned ICloneFactory.DeepPropertyClone<TCloned>(TCloned objectToClone) {
|
return ReflectionCloner.DeepPropertyClone<TCloned>(objectToClone);
|
||||||
return ReflectionCloner.DeepPropertyClone<TCloned>(objectToClone);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Clones a complex type using field-based value transfer</summary>
|
||||||
/// <summary>Clones a complex type using field-based value transfer</summary>
|
/// <param name="original">Original instance that will be cloned</param>
|
||||||
/// <param name="original">Original instance that will be cloned</param>
|
/// <returns>A clone of the original instance</returns>
|
||||||
/// <returns>A clone of the original instance</returns>
|
private static object shallowCloneComplexFieldBased(object original) {
|
||||||
private static object shallowCloneComplexFieldBased(object original) {
|
Type originalType = original.GetType();
|
||||||
Type originalType = original.GetType();
|
object clone = FormatterServices.GetUninitializedObject(originalType);
|
||||||
object clone = FormatterServices.GetUninitializedObject(originalType);
|
|
||||||
|
FieldInfo[] fieldInfos = originalType.GetFieldInfosIncludingBaseClasses(
|
||||||
FieldInfo[] fieldInfos = originalType.GetFieldInfosIncludingBaseClasses(
|
BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance
|
||||||
BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance
|
);
|
||||||
);
|
for(int index = 0; index < fieldInfos.Length; ++index) {
|
||||||
for(int index = 0; index < fieldInfos.Length; ++index) {
|
FieldInfo fieldInfo = fieldInfos[index];
|
||||||
FieldInfo fieldInfo = fieldInfos[index];
|
object originalValue = fieldInfo.GetValue(original);
|
||||||
object originalValue = fieldInfo.GetValue(original);
|
if(originalValue != null) {
|
||||||
if(originalValue != null) {
|
// Everything's just directly assigned in a shallow clone
|
||||||
// Everything's just directly assigned in a shallow clone
|
fieldInfo.SetValue(clone, originalValue);
|
||||||
fieldInfo.SetValue(clone, originalValue);
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
return clone;
|
||||||
return clone;
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Clones a complex type using property-based value transfer</summary>
|
||||||
/// <summary>Clones a complex type using property-based value transfer</summary>
|
/// <param name="original">Original instance that will be cloned</param>
|
||||||
/// <param name="original">Original instance that will be cloned</param>
|
/// <returns>A clone of the original instance</returns>
|
||||||
/// <returns>A clone of the original instance</returns>
|
private static object shallowCloneComplexPropertyBased(object original) {
|
||||||
private static object shallowCloneComplexPropertyBased(object original) {
|
Type originalType = original.GetType();
|
||||||
Type originalType = original.GetType();
|
object clone = Activator.CreateInstance(originalType);
|
||||||
object clone = Activator.CreateInstance(originalType);
|
|
||||||
|
PropertyInfo[] propertyInfos = originalType.GetProperties(
|
||||||
PropertyInfo[] propertyInfos = originalType.GetProperties(
|
BindingFlags.Public | BindingFlags.NonPublic |
|
||||||
BindingFlags.Public | BindingFlags.NonPublic |
|
BindingFlags.Instance | BindingFlags.FlattenHierarchy
|
||||||
BindingFlags.Instance | BindingFlags.FlattenHierarchy
|
);
|
||||||
);
|
for(int index = 0; index < propertyInfos.Length; ++index) {
|
||||||
for(int index = 0; index < propertyInfos.Length; ++index) {
|
PropertyInfo propertyInfo = propertyInfos[index];
|
||||||
PropertyInfo propertyInfo = propertyInfos[index];
|
if(propertyInfo.CanRead && propertyInfo.CanWrite) {
|
||||||
if(propertyInfo.CanRead && propertyInfo.CanWrite) {
|
Type propertyType = propertyInfo.PropertyType;
|
||||||
Type propertyType = propertyInfo.PropertyType;
|
object originalValue = propertyInfo.GetValue(original, null);
|
||||||
object originalValue = propertyInfo.GetValue(original, null);
|
if(originalValue != null) {
|
||||||
if(originalValue != null) {
|
if(propertyType.IsPrimitive || (propertyType == typeof(string))) {
|
||||||
if(propertyType.IsPrimitive || (propertyType == typeof(string))) {
|
// Primitive types can be assigned directly
|
||||||
// Primitive types can be assigned directly
|
propertyInfo.SetValue(clone, originalValue, null);
|
||||||
propertyInfo.SetValue(clone, originalValue, null);
|
} else if(propertyType.IsValueType) {
|
||||||
} else if(propertyType.IsValueType) {
|
// Value types are seen as part of the original type and are thus recursed into
|
||||||
// Value types are seen as part of the original type and are thus recursed into
|
propertyInfo.SetValue(clone, shallowCloneComplexPropertyBased(originalValue), null);
|
||||||
propertyInfo.SetValue(clone, shallowCloneComplexPropertyBased(originalValue), null);
|
} else if(propertyType.IsArray) { // Arrays are assigned directly in a shallow clone
|
||||||
} else if(propertyType.IsArray) { // Arrays are assigned directly in a shallow clone
|
propertyInfo.SetValue(clone, originalValue, null);
|
||||||
propertyInfo.SetValue(clone, originalValue, null);
|
} else { // Complex types are directly assigned without creating a copy
|
||||||
} else { // Complex types are directly assigned without creating a copy
|
propertyInfo.SetValue(clone, originalValue, null);
|
||||||
propertyInfo.SetValue(clone, originalValue, null);
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
return clone;
|
||||||
return clone;
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Clones an array using field-based value transfer</summary>
|
||||||
/// <summary>Clones an array using field-based value transfer</summary>
|
/// <param name="original">Original array that will be cloned</param>
|
||||||
/// <param name="original">Original array that will be cloned</param>
|
/// <returns>A clone of the original array</returns>
|
||||||
/// <returns>A clone of the original array</returns>
|
private static object shallowCloneArray(object original) {
|
||||||
private static object shallowCloneArray(object original) {
|
return ((Array)original).Clone();
|
||||||
return ((Array)original).Clone();
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Copies a single object using field-based value transfer</summary>
|
||||||
/// <summary>Copies a single object using field-based value transfer</summary>
|
/// <param name="original">Original object that will be cloned</param>
|
||||||
/// <param name="original">Original object that will be cloned</param>
|
/// <returns>A clone of the original object</returns>
|
||||||
/// <returns>A clone of the original object</returns>
|
private static object deepCloneSingleFieldBased(object original) {
|
||||||
private static object deepCloneSingleFieldBased(object original) {
|
Type originalType = original.GetType();
|
||||||
Type originalType = original.GetType();
|
if(originalType.IsPrimitive || (originalType == typeof(string))) {
|
||||||
if(originalType.IsPrimitive || (originalType == typeof(string))) {
|
return original; // Creates another box, does not reference boxed primitive
|
||||||
return original; // Creates another box, does not reference boxed primitive
|
} else if(originalType.IsArray) {
|
||||||
} else if(originalType.IsArray) {
|
return deepCloneArrayFieldBased((Array)original, originalType.GetElementType());
|
||||||
return deepCloneArrayFieldBased((Array)original, originalType.GetElementType());
|
} else {
|
||||||
} else {
|
return deepCloneComplexFieldBased(original);
|
||||||
return deepCloneComplexFieldBased(original);
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Clones a complex type using field-based value transfer</summary>
|
||||||
/// <summary>Clones a complex type using field-based value transfer</summary>
|
/// <param name="original">Original instance that will be cloned</param>
|
||||||
/// <param name="original">Original instance that will be cloned</param>
|
/// <returns>A clone of the original instance</returns>
|
||||||
/// <returns>A clone of the original instance</returns>
|
private static object deepCloneComplexFieldBased(object original) {
|
||||||
private static object deepCloneComplexFieldBased(object original) {
|
Type originalType = original.GetType();
|
||||||
Type originalType = original.GetType();
|
object clone = FormatterServices.GetUninitializedObject(originalType);
|
||||||
object clone = FormatterServices.GetUninitializedObject(originalType);
|
|
||||||
|
FieldInfo[] fieldInfos = originalType.GetFieldInfosIncludingBaseClasses(
|
||||||
FieldInfo[] fieldInfos = originalType.GetFieldInfosIncludingBaseClasses(
|
BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance
|
||||||
BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance
|
);
|
||||||
);
|
for(int index = 0; index < fieldInfos.Length; ++index) {
|
||||||
for(int index = 0; index < fieldInfos.Length; ++index) {
|
FieldInfo fieldInfo = fieldInfos[index];
|
||||||
FieldInfo fieldInfo = fieldInfos[index];
|
Type fieldType = fieldInfo.FieldType;
|
||||||
Type fieldType = fieldInfo.FieldType;
|
object originalValue = fieldInfo.GetValue(original);
|
||||||
object originalValue = fieldInfo.GetValue(original);
|
if(originalValue != null) {
|
||||||
if(originalValue != null) {
|
// Primitive types can be assigned directly
|
||||||
// Primitive types can be assigned directly
|
if(fieldType.IsPrimitive || (fieldType == typeof(string))) {
|
||||||
if(fieldType.IsPrimitive || (fieldType == typeof(string))) {
|
fieldInfo.SetValue(clone, originalValue);
|
||||||
fieldInfo.SetValue(clone, originalValue);
|
} else if(fieldType.IsArray) { // Arrays need to be cloned element-by-element
|
||||||
} else if(fieldType.IsArray) { // Arrays need to be cloned element-by-element
|
fieldInfo.SetValue(
|
||||||
fieldInfo.SetValue(
|
clone,
|
||||||
clone,
|
deepCloneArrayFieldBased((Array)originalValue, fieldType.GetElementType())
|
||||||
deepCloneArrayFieldBased((Array)originalValue, fieldType.GetElementType())
|
);
|
||||||
);
|
} else { // Complex types need to be cloned member-by-member
|
||||||
} else { // Complex types need to be cloned member-by-member
|
fieldInfo.SetValue(clone, deepCloneSingleFieldBased(originalValue));
|
||||||
fieldInfo.SetValue(clone, deepCloneSingleFieldBased(originalValue));
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
return clone;
|
||||||
return clone;
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Clones an array using field-based value transfer</summary>
|
||||||
/// <summary>Clones an array using field-based value transfer</summary>
|
/// <param name="original">Original array that will be cloned</param>
|
||||||
/// <param name="original">Original array that will be cloned</param>
|
/// <param name="elementType">Type of elements the original array contains</param>
|
||||||
/// <param name="elementType">Type of elements the original array contains</param>
|
/// <returns>A clone of the original array</returns>
|
||||||
/// <returns>A clone of the original array</returns>
|
private static object deepCloneArrayFieldBased(Array original, Type elementType) {
|
||||||
private static object deepCloneArrayFieldBased(Array original, Type elementType) {
|
if(elementType.IsPrimitive || (elementType == typeof(string))) {
|
||||||
if(elementType.IsPrimitive || (elementType == typeof(string))) {
|
return original.Clone();
|
||||||
return original.Clone();
|
}
|
||||||
}
|
|
||||||
|
int dimensionCount = original.Rank;
|
||||||
int dimensionCount = original.Rank;
|
|
||||||
|
// Find out the length of each of the array's dimensions, also calculate how
|
||||||
// Find out the length of each of the array's dimensions, also calculate how
|
// many elements there are in the array in total.
|
||||||
// many elements there are in the array in total.
|
var lengths = new int[dimensionCount];
|
||||||
var lengths = new int[dimensionCount];
|
int totalElementCount = 0;
|
||||||
int totalElementCount = 0;
|
for(int index = 0; index < dimensionCount; ++index) {
|
||||||
for(int index = 0; index < dimensionCount; ++index) {
|
lengths[index] = original.GetLength(index);
|
||||||
lengths[index] = original.GetLength(index);
|
if(index == 0) {
|
||||||
if(index == 0) {
|
totalElementCount = lengths[index];
|
||||||
totalElementCount = lengths[index];
|
} else {
|
||||||
} else {
|
totalElementCount *= lengths[index];
|
||||||
totalElementCount *= lengths[index];
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
// Knowing the number of dimensions and the length of each dimension, we can
|
||||||
// Knowing the number of dimensions and the length of each dimension, we can
|
// create another array of the exact same sizes.
|
||||||
// create another array of the exact same sizes.
|
Array clone = Array.CreateInstance(elementType, lengths);
|
||||||
Array clone = Array.CreateInstance(elementType, lengths);
|
|
||||||
|
// If this is a one-dimensional array (most common type), do an optimized copy
|
||||||
// If this is a one-dimensional array (most common type), do an optimized copy
|
// directly specifying the indices
|
||||||
// directly specifying the indices
|
if(dimensionCount == 1) {
|
||||||
if(dimensionCount == 1) {
|
|
||||||
|
// Clone each element of the array directly
|
||||||
// Clone each element of the array directly
|
for(int index = 0; index < totalElementCount; ++index) {
|
||||||
for(int index = 0; index < totalElementCount; ++index) {
|
object originalElement = original.GetValue(index);
|
||||||
object originalElement = original.GetValue(index);
|
if(originalElement != null) {
|
||||||
if(originalElement != null) {
|
clone.SetValue(deepCloneSingleFieldBased(originalElement), index);
|
||||||
clone.SetValue(deepCloneSingleFieldBased(originalElement), index);
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
} else { // Otherwise use the generic code for multi-dimensional arrays
|
||||||
} else { // Otherwise use the generic code for multi-dimensional arrays
|
|
||||||
|
var indices = new int[dimensionCount];
|
||||||
var indices = new int[dimensionCount];
|
for(int index = 0; index < totalElementCount; ++index) {
|
||||||
for(int index = 0; index < totalElementCount; ++index) {
|
|
||||||
|
// Determine the index for each of the array's dimensions
|
||||||
// Determine the index for each of the array's dimensions
|
int elementIndex = index;
|
||||||
int elementIndex = index;
|
for(int dimensionIndex = dimensionCount - 1; dimensionIndex >= 0; --dimensionIndex) {
|
||||||
for(int dimensionIndex = dimensionCount - 1; dimensionIndex >= 0; --dimensionIndex) {
|
indices[dimensionIndex] = elementIndex % lengths[dimensionIndex];
|
||||||
indices[dimensionIndex] = elementIndex % lengths[dimensionIndex];
|
elementIndex /= lengths[dimensionIndex];
|
||||||
elementIndex /= lengths[dimensionIndex];
|
}
|
||||||
}
|
|
||||||
|
// Clone the current array element
|
||||||
// Clone the current array element
|
object originalElement = original.GetValue(indices);
|
||||||
object originalElement = original.GetValue(indices);
|
if(originalElement != null) {
|
||||||
if(originalElement != null) {
|
clone.SetValue(deepCloneSingleFieldBased(originalElement), indices);
|
||||||
clone.SetValue(deepCloneSingleFieldBased(originalElement), indices);
|
}
|
||||||
}
|
|
||||||
|
}
|
||||||
}
|
|
||||||
|
}
|
||||||
}
|
|
||||||
|
return clone;
|
||||||
return clone;
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Copies a single object using property-based value transfer</summary>
|
||||||
/// <summary>Copies a single object using property-based value transfer</summary>
|
/// <param name="original">Original object that will be cloned</param>
|
||||||
/// <param name="original">Original object that will be cloned</param>
|
/// <returns>A clone of the original object</returns>
|
||||||
/// <returns>A clone of the original object</returns>
|
private static object deepCloneSinglePropertyBased(object original) {
|
||||||
private static object deepCloneSinglePropertyBased(object original) {
|
Type originalType = original.GetType();
|
||||||
Type originalType = original.GetType();
|
if(originalType.IsPrimitive || (originalType == typeof(string))) {
|
||||||
if(originalType.IsPrimitive || (originalType == typeof(string))) {
|
return original; // Creates another box, does not reference boxed primitive
|
||||||
return original; // Creates another box, does not reference boxed primitive
|
} else if(originalType.IsArray) {
|
||||||
} else if(originalType.IsArray) {
|
return deepCloneArrayPropertyBased((Array)original, originalType.GetElementType());
|
||||||
return deepCloneArrayPropertyBased((Array)original, originalType.GetElementType());
|
} else {
|
||||||
} else {
|
return deepCloneComplexPropertyBased(original);
|
||||||
return deepCloneComplexPropertyBased(original);
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Clones a complex type using property-based value transfer</summary>
|
||||||
/// <summary>Clones a complex type using property-based value transfer</summary>
|
/// <param name="original">Original instance that will be cloned</param>
|
||||||
/// <param name="original">Original instance that will be cloned</param>
|
/// <returns>A clone of the original instance</returns>
|
||||||
/// <returns>A clone of the original instance</returns>
|
private static object deepCloneComplexPropertyBased(object original) {
|
||||||
private static object deepCloneComplexPropertyBased(object original) {
|
Type originalType = original.GetType();
|
||||||
Type originalType = original.GetType();
|
object clone = Activator.CreateInstance(originalType);
|
||||||
object clone = Activator.CreateInstance(originalType);
|
|
||||||
|
PropertyInfo[] propertyInfos = originalType.GetProperties(
|
||||||
PropertyInfo[] propertyInfos = originalType.GetProperties(
|
BindingFlags.Public | BindingFlags.NonPublic |
|
||||||
BindingFlags.Public | BindingFlags.NonPublic |
|
BindingFlags.Instance | BindingFlags.FlattenHierarchy
|
||||||
BindingFlags.Instance | BindingFlags.FlattenHierarchy
|
);
|
||||||
);
|
for(int index = 0; index < propertyInfos.Length; ++index) {
|
||||||
for(int index = 0; index < propertyInfos.Length; ++index) {
|
PropertyInfo propertyInfo = propertyInfos[index];
|
||||||
PropertyInfo propertyInfo = propertyInfos[index];
|
if(propertyInfo.CanRead && propertyInfo.CanWrite) {
|
||||||
if(propertyInfo.CanRead && propertyInfo.CanWrite) {
|
Type propertyType = propertyInfo.PropertyType;
|
||||||
Type propertyType = propertyInfo.PropertyType;
|
object originalValue = propertyInfo.GetValue(original, null);
|
||||||
object originalValue = propertyInfo.GetValue(original, null);
|
if(originalValue != null) {
|
||||||
if(originalValue != null) {
|
if(propertyType.IsPrimitive || (propertyType == typeof(string))) {
|
||||||
if(propertyType.IsPrimitive || (propertyType == typeof(string))) {
|
// Primitive types can be assigned directly
|
||||||
// Primitive types can be assigned directly
|
propertyInfo.SetValue(clone, originalValue, null);
|
||||||
propertyInfo.SetValue(clone, originalValue, null);
|
} else if(propertyType.IsArray) { // Arrays need to be cloned element-by-element
|
||||||
} else if(propertyType.IsArray) { // Arrays need to be cloned element-by-element
|
propertyInfo.SetValue(
|
||||||
propertyInfo.SetValue(
|
clone,
|
||||||
clone,
|
deepCloneArrayPropertyBased((Array)originalValue, propertyType.GetElementType()),
|
||||||
deepCloneArrayPropertyBased((Array)originalValue, propertyType.GetElementType()),
|
null
|
||||||
null
|
);
|
||||||
);
|
} else { // Complex types need to be cloned member-by-member
|
||||||
} else { // Complex types need to be cloned member-by-member
|
propertyInfo.SetValue(clone, deepCloneSinglePropertyBased(originalValue), null);
|
||||||
propertyInfo.SetValue(clone, deepCloneSinglePropertyBased(originalValue), null);
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
return clone;
|
||||||
return clone;
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Clones an array using property-based value transfer</summary>
|
||||||
/// <summary>Clones an array using property-based value transfer</summary>
|
/// <param name="original">Original array that will be cloned</param>
|
||||||
/// <param name="original">Original array that will be cloned</param>
|
/// <param name="elementType">Type of elements the original array contains</param>
|
||||||
/// <param name="elementType">Type of elements the original array contains</param>
|
/// <returns>A clone of the original array</returns>
|
||||||
/// <returns>A clone of the original array</returns>
|
private static object deepCloneArrayPropertyBased(Array original, Type elementType) {
|
||||||
private static object deepCloneArrayPropertyBased(Array original, Type elementType) {
|
if(elementType.IsPrimitive || (elementType == typeof(string))) {
|
||||||
if(elementType.IsPrimitive || (elementType == typeof(string))) {
|
return original.Clone();
|
||||||
return original.Clone();
|
}
|
||||||
}
|
|
||||||
|
int dimensionCount = original.Rank;
|
||||||
int dimensionCount = original.Rank;
|
|
||||||
|
// Find out the length of each of the array's dimensions, also calculate how
|
||||||
// Find out the length of each of the array's dimensions, also calculate how
|
// many elements there are in the array in total.
|
||||||
// many elements there are in the array in total.
|
var lengths = new int[dimensionCount];
|
||||||
var lengths = new int[dimensionCount];
|
int totalElementCount = 0;
|
||||||
int totalElementCount = 0;
|
for(int index = 0; index < dimensionCount; ++index) {
|
||||||
for(int index = 0; index < dimensionCount; ++index) {
|
lengths[index] = original.GetLength(index);
|
||||||
lengths[index] = original.GetLength(index);
|
if(index == 0) {
|
||||||
if(index == 0) {
|
totalElementCount = lengths[index];
|
||||||
totalElementCount = lengths[index];
|
} else {
|
||||||
} else {
|
totalElementCount *= lengths[index];
|
||||||
totalElementCount *= lengths[index];
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
// Knowing the number of dimensions and the length of each dimension, we can
|
||||||
// Knowing the number of dimensions and the length of each dimension, we can
|
// create another array of the exact same sizes.
|
||||||
// create another array of the exact same sizes.
|
Array clone = Array.CreateInstance(elementType, lengths);
|
||||||
Array clone = Array.CreateInstance(elementType, lengths);
|
|
||||||
|
// If this is a one-dimensional array (most common type), do an optimized copy
|
||||||
// If this is a one-dimensional array (most common type), do an optimized copy
|
// directly specifying the indices
|
||||||
// directly specifying the indices
|
if(dimensionCount == 1) {
|
||||||
if(dimensionCount == 1) {
|
|
||||||
|
// Clone each element of the array directly
|
||||||
// Clone each element of the array directly
|
for(int index = 0; index < totalElementCount; ++index) {
|
||||||
for(int index = 0; index < totalElementCount; ++index) {
|
object originalElement = original.GetValue(index);
|
||||||
object originalElement = original.GetValue(index);
|
if(originalElement != null) {
|
||||||
if(originalElement != null) {
|
clone.SetValue(deepCloneSinglePropertyBased(originalElement), index);
|
||||||
clone.SetValue(deepCloneSinglePropertyBased(originalElement), index);
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
} else { // Otherwise use the generic code for multi-dimensional arrays
|
||||||
} else { // Otherwise use the generic code for multi-dimensional arrays
|
|
||||||
|
var indices = new int[dimensionCount];
|
||||||
var indices = new int[dimensionCount];
|
for(int index = 0; index < totalElementCount; ++index) {
|
||||||
for(int index = 0; index < totalElementCount; ++index) {
|
|
||||||
|
// Determine the index for each of the array's dimensions
|
||||||
// Determine the index for each of the array's dimensions
|
int elementIndex = index;
|
||||||
int elementIndex = index;
|
for(int dimensionIndex = dimensionCount - 1; dimensionIndex >= 0; --dimensionIndex) {
|
||||||
for(int dimensionIndex = dimensionCount - 1; dimensionIndex >= 0; --dimensionIndex) {
|
indices[dimensionIndex] = elementIndex % lengths[dimensionIndex];
|
||||||
indices[dimensionIndex] = elementIndex % lengths[dimensionIndex];
|
elementIndex /= lengths[dimensionIndex];
|
||||||
elementIndex /= lengths[dimensionIndex];
|
}
|
||||||
}
|
|
||||||
|
// Clone the current array element
|
||||||
// Clone the current array element
|
object originalElement = original.GetValue(indices);
|
||||||
object originalElement = original.GetValue(indices);
|
if(originalElement != null) {
|
||||||
if(originalElement != null) {
|
clone.SetValue(deepCloneSinglePropertyBased(originalElement), indices);
|
||||||
clone.SetValue(deepCloneSinglePropertyBased(originalElement), indices);
|
}
|
||||||
}
|
|
||||||
|
}
|
||||||
}
|
|
||||||
|
}
|
||||||
}
|
|
||||||
|
return clone;
|
||||||
return clone;
|
}
|
||||||
}
|
|
||||||
|
}
|
||||||
}
|
|
||||||
|
} // namespace Nuclex.Support.Cloning
|
||||||
} // namespace Nuclex.Support.Cloning
|
|
||||||
|
|
|
@ -1,146 +1,145 @@
|
||||||
#region CPL License
|
#region Apache License 2.0
|
||||||
/*
|
/*
|
||||||
Nuclex Framework
|
Nuclex .NET Framework
|
||||||
Copyright (C) 2002-2017 Nuclex Development Labs
|
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
|
||||||
|
|
||||||
This library is free software; you can redistribute it and/or
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
modify it under the terms of the IBM Common Public License as
|
you may not use this file except in compliance with the License.
|
||||||
published by the IBM Corporation; either version 1.0 of the
|
You may obtain a copy of the License at
|
||||||
License, or (at your option) any later version.
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
This library is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
Unless required by applicable law or agreed to in writing, software
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
IBM Common Public License for more details.
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
You should have received a copy of the IBM Common Public
|
limitations under the License.
|
||||||
License along with this library
|
*/
|
||||||
*/
|
#endregion // Apache License 2.0
|
||||||
#endregion
|
|
||||||
|
#if UNITTEST
|
||||||
#if UNITTEST
|
|
||||||
|
using System;
|
||||||
using System;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Generic;
|
|
||||||
|
using NUnit.Framework;
|
||||||
using NUnit.Framework;
|
|
||||||
|
namespace Nuclex.Support.Cloning {
|
||||||
namespace Nuclex.Support.Cloning {
|
|
||||||
|
/// <summary>Unit Test for the binary serializer-based cloner</summary>
|
||||||
/// <summary>Unit Test for the binary serializer-based cloner</summary>
|
[TestFixture]
|
||||||
[TestFixture]
|
internal class SerializationClonerTest : CloneFactoryTest {
|
||||||
internal class SerializationClonerTest : CloneFactoryTest {
|
|
||||||
|
/// <summary>Initializes a new unit test suite for the reflection cloner</summary>
|
||||||
/// <summary>Initializes a new unit test suite for the reflection cloner</summary>
|
public SerializationClonerTest() {
|
||||||
public SerializationClonerTest() {
|
this.cloneFactory = new SerializationCloner();
|
||||||
this.cloneFactory = new SerializationCloner();
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Verifies that cloning a null object simply returns null</summary>
|
||||||
/// <summary>Verifies that cloning a null object simply returns null</summary>
|
[Test]
|
||||||
[Test]
|
public void CloningNullYieldsNull() {
|
||||||
public void CloningNullYieldsNull() {
|
Assert.IsNull(this.cloneFactory.DeepFieldClone<object>(null));
|
||||||
Assert.IsNull(this.cloneFactory.DeepFieldClone<object>(null));
|
Assert.IsNull(this.cloneFactory.DeepPropertyClone<object>(null));
|
||||||
Assert.IsNull(this.cloneFactory.DeepPropertyClone<object>(null));
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Verifies that clones of objects whose class doesn't possess a default constructor
|
||||||
/// Verifies that clones of objects whose class doesn't possess a default constructor
|
/// can be made
|
||||||
/// can be made
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void ClassWithoutDefaultConstructorCanBeCloned() {
|
||||||
public void ClassWithoutDefaultConstructorCanBeCloned() {
|
var original = new ClassWithoutDefaultConstructor(1234);
|
||||||
var original = new ClassWithoutDefaultConstructor(1234);
|
ClassWithoutDefaultConstructor clone = this.cloneFactory.DeepFieldClone(original);
|
||||||
ClassWithoutDefaultConstructor clone = this.cloneFactory.DeepFieldClone(original);
|
|
||||||
|
Assert.AreNotSame(original, clone);
|
||||||
Assert.AreNotSame(original, clone);
|
Assert.AreEqual(original.Dummy, clone.Dummy);
|
||||||
Assert.AreEqual(original.Dummy, clone.Dummy);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Verifies that clones of primitive types can be created</summary>
|
||||||
/// <summary>Verifies that clones of primitive types can be created</summary>
|
[Test]
|
||||||
[Test]
|
public void PrimitiveTypesCanBeCloned() {
|
||||||
public void PrimitiveTypesCanBeCloned() {
|
int original = 12345;
|
||||||
int original = 12345;
|
int clone = this.cloneFactory.DeepFieldClone(original);
|
||||||
int clone = this.cloneFactory.DeepFieldClone(original);
|
Assert.AreEqual(original, clone);
|
||||||
Assert.AreEqual(original, clone);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Verifies that deep clones of arrays can be made</summary>
|
||||||
/// <summary>Verifies that deep clones of arrays can be made</summary>
|
[Test]
|
||||||
[Test]
|
public void DeepClonesOfArraysCanBeMade() {
|
||||||
public void DeepClonesOfArraysCanBeMade() {
|
var original = new TestReferenceType[] {
|
||||||
var original = new TestReferenceType[] {
|
new TestReferenceType() { TestField = 123, TestProperty = 456 }
|
||||||
new TestReferenceType() { TestField = 123, TestProperty = 456 }
|
};
|
||||||
};
|
TestReferenceType[] clone = this.cloneFactory.DeepFieldClone(original);
|
||||||
TestReferenceType[] clone = this.cloneFactory.DeepFieldClone(original);
|
|
||||||
|
Assert.AreNotSame(original[0], clone[0]);
|
||||||
Assert.AreNotSame(original[0], clone[0]);
|
Assert.AreEqual(original[0].TestField, clone[0].TestField);
|
||||||
Assert.AreEqual(original[0].TestField, clone[0].TestField);
|
Assert.AreEqual(original[0].TestProperty, clone[0].TestProperty);
|
||||||
Assert.AreEqual(original[0].TestProperty, clone[0].TestProperty);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Verifies that deep clones of a generic list can be made</summary>
|
||||||
/// <summary>Verifies that deep clones of a generic list can be made</summary>
|
[Test]
|
||||||
[Test]
|
public void GenericListsCanBeCloned() {
|
||||||
public void GenericListsCanBeCloned() {
|
var original = new List<int>(new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 });
|
||||||
var original = new List<int>(new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 });
|
List<int> clone = this.cloneFactory.DeepFieldClone(original);
|
||||||
List<int> clone = this.cloneFactory.DeepFieldClone(original);
|
|
||||||
|
CollectionAssert.AreEqual(original, clone);
|
||||||
CollectionAssert.AreEqual(original, clone);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Verifies that deep clones of a generic dictionary can be made</summary>
|
||||||
/// <summary>Verifies that deep clones of a generic dictionary can be made</summary>
|
[Test]
|
||||||
[Test]
|
public void GenericDictionariesCanBeCloned() {
|
||||||
public void GenericDictionariesCanBeCloned() {
|
var original = new Dictionary<int, string>();
|
||||||
var original = new Dictionary<int, string>();
|
original.Add(1, "one");
|
||||||
original.Add(1, "one");
|
Dictionary<int, string> clone = this.cloneFactory.DeepFieldClone(original);
|
||||||
Dictionary<int, string> clone = this.cloneFactory.DeepFieldClone(original);
|
|
||||||
|
Assert.AreEqual("one", clone[1]);
|
||||||
Assert.AreEqual("one", clone[1]);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Verifies that a field-based deep clone of a value type can be performed
|
||||||
/// Verifies that a field-based deep clone of a value type can be performed
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void DeepFieldBasedClonesOfValueTypesCanBeMade() {
|
||||||
public void DeepFieldBasedClonesOfValueTypesCanBeMade() {
|
HierarchicalValueType original = CreateValueType();
|
||||||
HierarchicalValueType original = CreateValueType();
|
HierarchicalValueType clone = this.cloneFactory.DeepFieldClone(original);
|
||||||
HierarchicalValueType clone = this.cloneFactory.DeepFieldClone(original);
|
VerifyClone(ref original, ref clone, isDeepClone: true, isPropertyBasedClone: false);
|
||||||
VerifyClone(ref original, ref clone, isDeepClone: true, isPropertyBasedClone: false);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Verifies that a field-based deep clone of a reference type can be performed
|
||||||
/// Verifies that a field-based deep clone of a reference type can be performed
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void DeepFieldBasedClonesOfReferenceTypesCanBeMade() {
|
||||||
public void DeepFieldBasedClonesOfReferenceTypesCanBeMade() {
|
HierarchicalReferenceType original = CreateReferenceType();
|
||||||
HierarchicalReferenceType original = CreateReferenceType();
|
HierarchicalReferenceType clone = this.cloneFactory.DeepFieldClone(original);
|
||||||
HierarchicalReferenceType clone = this.cloneFactory.DeepFieldClone(original);
|
VerifyClone(original, clone, isDeepClone: true, isPropertyBasedClone: false);
|
||||||
VerifyClone(original, clone, isDeepClone: true, isPropertyBasedClone: false);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Verifies that a property-based deep clone of a value type can be performed
|
||||||
/// Verifies that a property-based deep clone of a value type can be performed
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void DeepPropertyBasedClonesOfValueTypesCanBeMade() {
|
||||||
public void DeepPropertyBasedClonesOfValueTypesCanBeMade() {
|
HierarchicalValueType original = CreateValueType();
|
||||||
HierarchicalValueType original = CreateValueType();
|
HierarchicalValueType clone = this.cloneFactory.DeepPropertyClone(original);
|
||||||
HierarchicalValueType clone = this.cloneFactory.DeepPropertyClone(original);
|
VerifyClone(ref original, ref clone, isDeepClone: true, isPropertyBasedClone: true);
|
||||||
VerifyClone(ref original, ref clone, isDeepClone: true, isPropertyBasedClone: true);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Verifies that a property-based deep clone of a reference type can be performed
|
||||||
/// Verifies that a property-based deep clone of a reference type can be performed
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void DeepPropertyBasedClonesOfReferenceTypesCanBeMade() {
|
||||||
public void DeepPropertyBasedClonesOfReferenceTypesCanBeMade() {
|
HierarchicalReferenceType original = CreateReferenceType();
|
||||||
HierarchicalReferenceType original = CreateReferenceType();
|
HierarchicalReferenceType clone = this.cloneFactory.DeepPropertyClone(original);
|
||||||
HierarchicalReferenceType clone = this.cloneFactory.DeepPropertyClone(original);
|
VerifyClone(original, clone, isDeepClone: true, isPropertyBasedClone: true);
|
||||||
VerifyClone(original, clone, isDeepClone: true, isPropertyBasedClone: true);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Clone factory being tested</summary>
|
||||||
/// <summary>Clone factory being tested</summary>
|
private ICloneFactory cloneFactory;
|
||||||
private ICloneFactory cloneFactory;
|
|
||||||
|
}
|
||||||
}
|
|
||||||
|
} // namespace Nuclex.Support.Cloning
|
||||||
} // namespace Nuclex.Support.Cloning
|
|
||||||
|
#endif // UNITTEST
|
||||||
#endif // UNITTEST
|
|
||||||
|
|
|
@ -1,328 +1,327 @@
|
||||||
#region CPL License
|
#region Apache License 2.0
|
||||||
/*
|
/*
|
||||||
Nuclex Framework
|
Nuclex .NET Framework
|
||||||
Copyright (C) 2002-2017 Nuclex Development Labs
|
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
|
||||||
|
|
||||||
This library is free software; you can redistribute it and/or
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
modify it under the terms of the IBM Common Public License as
|
you may not use this file except in compliance with the License.
|
||||||
published by the IBM Corporation; either version 1.0 of the
|
You may obtain a copy of the License at
|
||||||
License, or (at your option) any later version.
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
This library is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
Unless required by applicable law or agreed to in writing, software
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
IBM Common Public License for more details.
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
You should have received a copy of the IBM Common Public
|
limitations under the License.
|
||||||
License along with this library
|
*/
|
||||||
*/
|
#endregion // Apache License 2.0
|
||||||
#endregion
|
|
||||||
|
using System;
|
||||||
using System;
|
using System.IO;
|
||||||
using System.IO;
|
using System.Reflection;
|
||||||
using System.Reflection;
|
using System.Runtime.Serialization;
|
||||||
using System.Runtime.Serialization;
|
using System.Runtime.Serialization.Formatters.Binary;
|
||||||
using System.Runtime.Serialization.Formatters.Binary;
|
|
||||||
|
namespace Nuclex.Support.Cloning {
|
||||||
namespace Nuclex.Support.Cloning {
|
|
||||||
|
/// <summary>Clones objects via serialization</summary>
|
||||||
/// <summary>Clones objects via serialization</summary>
|
/// <remarks>
|
||||||
/// <remarks>
|
/// <para>
|
||||||
/// <para>
|
/// This type of cloning uses the binary formatter to persist the state of
|
||||||
/// This type of cloning uses the binary formatter to persist the state of
|
/// an object and then restores it into a clone. It has the advantage of even
|
||||||
/// an object and then restores it into a clone. It has the advantage of even
|
/// working with types that don't provide a default constructor, but is
|
||||||
/// working with types that don't provide a default constructor, but is
|
/// terribly slow.
|
||||||
/// terribly slow.
|
/// </para>
|
||||||
/// </para>
|
/// <para>
|
||||||
/// <para>
|
/// Inspired by the "A Generic Method for Deep Cloning in C# 3.0" article
|
||||||
/// Inspired by the "A Generic Method for Deep Cloning in C# 3.0" article
|
/// on CodeProject: http://www.codeproject.com/KB/cs/generic_deep_cloning.aspx
|
||||||
/// on CodeProject: http://www.codeproject.com/KB/cs/generic_deep_cloning.aspx
|
/// </para>
|
||||||
/// </para>
|
/// </remarks>
|
||||||
/// </remarks>
|
public class SerializationCloner : ICloneFactory {
|
||||||
public class SerializationCloner : ICloneFactory {
|
|
||||||
|
#region class StaticSurrogateSelector
|
||||||
#region class StaticSurrogateSelector
|
|
||||||
|
/// <summary>Selects a static surrogate for any non-primitive types</summary>
|
||||||
/// <summary>Selects a static surrogate for any non-primitive types</summary>
|
private class StaticSurrogateSelector : ISurrogateSelector {
|
||||||
private class StaticSurrogateSelector : ISurrogateSelector {
|
|
||||||
|
/// <summary>Initializes a new static surrogate selector</summary>
|
||||||
/// <summary>Initializes a new static surrogate selector</summary>
|
/// <param name="staticSurrogate">Surrogate that will be selected</param>
|
||||||
/// <param name="staticSurrogate">Surrogate that will be selected</param>
|
public StaticSurrogateSelector(ISerializationSurrogate staticSurrogate) {
|
||||||
public StaticSurrogateSelector(ISerializationSurrogate staticSurrogate) {
|
this.staticSurrogate = staticSurrogate;
|
||||||
this.staticSurrogate = staticSurrogate;
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Sets the next selector to escalate to if this one can't provide a surrogate
|
||||||
/// Sets the next selector to escalate to if this one can't provide a surrogate
|
/// </summary>
|
||||||
/// </summary>
|
/// <param name="selector">Selector to escalate to</param>
|
||||||
/// <param name="selector">Selector to escalate to</param>
|
public void ChainSelector(ISurrogateSelector selector) {
|
||||||
public void ChainSelector(ISurrogateSelector selector) {
|
this.chainedSelector = selector;
|
||||||
this.chainedSelector = selector;
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Returns the selector this one will escalate to if it can't provide a surrogate
|
||||||
/// Returns the selector this one will escalate to if it can't provide a surrogate
|
/// </summary>
|
||||||
/// </summary>
|
/// <returns>The selector this one will escalate to</returns>
|
||||||
/// <returns>The selector this one will escalate to</returns>
|
public ISurrogateSelector GetNextSelector() {
|
||||||
public ISurrogateSelector GetNextSelector() {
|
return this.chainedSelector;
|
||||||
return this.chainedSelector;
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Attempts to provides a surrogate for the specified type</summary>
|
||||||
/// <summary>Attempts to provides a surrogate for the specified type</summary>
|
/// <param name="type">Type a surrogate will be provided for</param>
|
||||||
/// <param name="type">Type a surrogate will be provided for</param>
|
/// <param name="context">Context </param>
|
||||||
/// <param name="context">Context </param>
|
/// <param name="selector"></param>
|
||||||
/// <param name="selector"></param>
|
/// <returns></returns>
|
||||||
/// <returns></returns>
|
public ISerializationSurrogate GetSurrogate(
|
||||||
public ISerializationSurrogate GetSurrogate(
|
Type type, StreamingContext context, out ISurrogateSelector selector
|
||||||
Type type, StreamingContext context, out ISurrogateSelector selector
|
) {
|
||||||
) {
|
if(type.IsPrimitive || type.IsArray || (type == typeof(string))) {
|
||||||
if(type.IsPrimitive || type.IsArray || (type == typeof(string))) {
|
if(this.chainedSelector == null) {
|
||||||
if(this.chainedSelector == null) {
|
selector = null;
|
||||||
selector = null;
|
return null;
|
||||||
return null;
|
} else {
|
||||||
} else {
|
return this.chainedSelector.GetSurrogate(type, context, out selector);
|
||||||
return this.chainedSelector.GetSurrogate(type, context, out selector);
|
}
|
||||||
}
|
} else {
|
||||||
} else {
|
selector = this;
|
||||||
selector = this;
|
return this.staticSurrogate;
|
||||||
return this.staticSurrogate;
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Surrogate the that will be selected for any non-primitive types</summary>
|
||||||
/// <summary>Surrogate the that will be selected for any non-primitive types</summary>
|
private readonly ISerializationSurrogate staticSurrogate;
|
||||||
private readonly ISerializationSurrogate staticSurrogate;
|
/// <summary>Surrogate selector to escalate to if no surrogate can be provided</summary>
|
||||||
/// <summary>Surrogate selector to escalate to if no surrogate can be provided</summary>
|
private ISurrogateSelector chainedSelector;
|
||||||
private ISurrogateSelector chainedSelector;
|
|
||||||
|
}
|
||||||
}
|
|
||||||
|
#endregion // class StaticSurrogateSelector
|
||||||
#endregion // class StaticSurrogateSelector
|
|
||||||
|
#region class FieldSerializationSurrogate
|
||||||
#region class FieldSerializationSurrogate
|
|
||||||
|
/// <summary>Serializes a type based on its fields</summary>
|
||||||
/// <summary>Serializes a type based on its fields</summary>
|
private class FieldSerializationSurrogate : ISerializationSurrogate {
|
||||||
private class FieldSerializationSurrogate : ISerializationSurrogate {
|
|
||||||
|
/// <summary>Extracts the data to be serialized from an object</summary>
|
||||||
/// <summary>Extracts the data to be serialized from an object</summary>
|
/// <param name="objectToSerialize">Object that is being serialized</param>
|
||||||
/// <param name="objectToSerialize">Object that is being serialized</param>
|
/// <param name="info">Stores the serialized informations</param>
|
||||||
/// <param name="info">Stores the serialized informations</param>
|
/// <param name="context">
|
||||||
/// <param name="context">
|
/// Provides additional informations about the serialization process
|
||||||
/// Provides additional informations about the serialization process
|
/// </param>
|
||||||
/// </param>
|
public void GetObjectData(
|
||||||
public void GetObjectData(
|
object objectToSerialize,
|
||||||
object objectToSerialize,
|
SerializationInfo info,
|
||||||
SerializationInfo info,
|
StreamingContext context
|
||||||
StreamingContext context
|
) {
|
||||||
) {
|
Type originalType = objectToSerialize.GetType();
|
||||||
Type originalType = objectToSerialize.GetType();
|
|
||||||
|
FieldInfo[] fieldInfos = originalType.GetFieldInfosIncludingBaseClasses(
|
||||||
FieldInfo[] fieldInfos = originalType.GetFieldInfosIncludingBaseClasses(
|
BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance
|
||||||
BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance
|
);
|
||||||
);
|
for(int index = 0; index < fieldInfos.Length; ++index) {
|
||||||
for(int index = 0; index < fieldInfos.Length; ++index) {
|
FieldInfo fieldInfo = fieldInfos[index];
|
||||||
FieldInfo fieldInfo = fieldInfos[index];
|
info.AddValue(fieldInfo.Name, fieldInfo.GetValue(objectToSerialize));
|
||||||
info.AddValue(fieldInfo.Name, fieldInfo.GetValue(objectToSerialize));
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Reinserts saved data into a deserializd object</summary>
|
||||||
/// <summary>Reinserts saved data into a deserializd object</summary>
|
/// <param name="deserializedObject">Object the saved data will be inserted into</param>
|
||||||
/// <param name="deserializedObject">Object the saved data will be inserted into</param>
|
/// <param name="info">Contains the serialized informations</param>
|
||||||
/// <param name="info">Contains the serialized informations</param>
|
/// <param name="context">
|
||||||
/// <param name="context">
|
/// Provides additional informations about the serialization process
|
||||||
/// Provides additional informations about the serialization process
|
/// </param>
|
||||||
/// </param>
|
/// <param name="selector">Surrogate selector that specified this surrogate</param>
|
||||||
/// <param name="selector">Surrogate selector that specified this surrogate</param>
|
/// <returns>The deserialized object</returns>
|
||||||
/// <returns>The deserialized object</returns>
|
public object SetObjectData(
|
||||||
public object SetObjectData(
|
object deserializedObject,
|
||||||
object deserializedObject,
|
SerializationInfo info,
|
||||||
SerializationInfo info,
|
StreamingContext context,
|
||||||
StreamingContext context,
|
ISurrogateSelector selector
|
||||||
ISurrogateSelector selector
|
) {
|
||||||
) {
|
Type originalType = deserializedObject.GetType();
|
||||||
Type originalType = deserializedObject.GetType();
|
|
||||||
|
FieldInfo[] fieldInfos = originalType.GetFieldInfosIncludingBaseClasses(
|
||||||
FieldInfo[] fieldInfos = originalType.GetFieldInfosIncludingBaseClasses(
|
BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance
|
||||||
BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance
|
);
|
||||||
);
|
for(int index = 0; index < fieldInfos.Length; ++index) {
|
||||||
for(int index = 0; index < fieldInfos.Length; ++index) {
|
FieldInfo fieldInfo = fieldInfos[index];
|
||||||
FieldInfo fieldInfo = fieldInfos[index];
|
fieldInfo.SetValue(deserializedObject, info.GetValue(fieldInfo.Name, fieldInfo.FieldType));
|
||||||
fieldInfo.SetValue(deserializedObject, info.GetValue(fieldInfo.Name, fieldInfo.FieldType));
|
}
|
||||||
}
|
|
||||||
|
return deserializedObject;
|
||||||
return deserializedObject;
|
}
|
||||||
}
|
|
||||||
|
}
|
||||||
}
|
|
||||||
|
#endregion // class FieldSerializationSurrogate
|
||||||
#endregion // class FieldSerializationSurrogate
|
|
||||||
|
#region class PropertySerializationSurrogate
|
||||||
#region class PropertySerializationSurrogate
|
|
||||||
|
/// <summary>Serializes a type based on its properties</summary>
|
||||||
/// <summary>Serializes a type based on its properties</summary>
|
private class PropertySerializationSurrogate : ISerializationSurrogate {
|
||||||
private class PropertySerializationSurrogate : ISerializationSurrogate {
|
|
||||||
|
/// <summary>Extracts the data to be serialized from an object</summary>
|
||||||
/// <summary>Extracts the data to be serialized from an object</summary>
|
/// <param name="objectToSerialize">Object that is being serialized</param>
|
||||||
/// <param name="objectToSerialize">Object that is being serialized</param>
|
/// <param name="info">Stores the serialized informations</param>
|
||||||
/// <param name="info">Stores the serialized informations</param>
|
/// <param name="context">
|
||||||
/// <param name="context">
|
/// Provides additional informations about the serialization process
|
||||||
/// Provides additional informations about the serialization process
|
/// </param>
|
||||||
/// </param>
|
public void GetObjectData(
|
||||||
public void GetObjectData(
|
object objectToSerialize,
|
||||||
object objectToSerialize,
|
SerializationInfo info,
|
||||||
SerializationInfo info,
|
StreamingContext context
|
||||||
StreamingContext context
|
) {
|
||||||
) {
|
Type originalType = objectToSerialize.GetType();
|
||||||
Type originalType = objectToSerialize.GetType();
|
|
||||||
|
PropertyInfo[] propertyInfos = originalType.GetProperties(
|
||||||
PropertyInfo[] propertyInfos = originalType.GetProperties(
|
BindingFlags.Public | BindingFlags.NonPublic |
|
||||||
BindingFlags.Public | BindingFlags.NonPublic |
|
BindingFlags.Instance | BindingFlags.FlattenHierarchy
|
||||||
BindingFlags.Instance | BindingFlags.FlattenHierarchy
|
);
|
||||||
);
|
for(int index = 0; index < propertyInfos.Length; ++index) {
|
||||||
for(int index = 0; index < propertyInfos.Length; ++index) {
|
PropertyInfo propertyInfo = propertyInfos[index];
|
||||||
PropertyInfo propertyInfo = propertyInfos[index];
|
if(propertyInfo.CanRead && propertyInfo.CanWrite) {
|
||||||
if(propertyInfo.CanRead && propertyInfo.CanWrite) {
|
info.AddValue(propertyInfo.Name, propertyInfo.GetValue(objectToSerialize, null));
|
||||||
info.AddValue(propertyInfo.Name, propertyInfo.GetValue(objectToSerialize, null));
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Reinserts saved data into a deserializd object</summary>
|
||||||
/// <summary>Reinserts saved data into a deserializd object</summary>
|
/// <param name="deserializedObject">Object the saved data will be inserted into</param>
|
||||||
/// <param name="deserializedObject">Object the saved data will be inserted into</param>
|
/// <param name="info">Contains the serialized informations</param>
|
||||||
/// <param name="info">Contains the serialized informations</param>
|
/// <param name="context">
|
||||||
/// <param name="context">
|
/// Provides additional informations about the serialization process
|
||||||
/// Provides additional informations about the serialization process
|
/// </param>
|
||||||
/// </param>
|
/// <param name="selector">Surrogate selector that specified this surrogate</param>
|
||||||
/// <param name="selector">Surrogate selector that specified this surrogate</param>
|
/// <returns>The deserialized object</returns>
|
||||||
/// <returns>The deserialized object</returns>
|
public object SetObjectData(
|
||||||
public object SetObjectData(
|
object deserializedObject,
|
||||||
object deserializedObject,
|
SerializationInfo info,
|
||||||
SerializationInfo info,
|
StreamingContext context,
|
||||||
StreamingContext context,
|
ISurrogateSelector selector
|
||||||
ISurrogateSelector selector
|
) {
|
||||||
) {
|
Type originalType = deserializedObject.GetType();
|
||||||
Type originalType = deserializedObject.GetType();
|
|
||||||
|
PropertyInfo[] propertyInfos = originalType.GetProperties(
|
||||||
PropertyInfo[] propertyInfos = originalType.GetProperties(
|
BindingFlags.Public | BindingFlags.NonPublic |
|
||||||
BindingFlags.Public | BindingFlags.NonPublic |
|
BindingFlags.Instance | BindingFlags.FlattenHierarchy
|
||||||
BindingFlags.Instance | BindingFlags.FlattenHierarchy
|
);
|
||||||
);
|
for(int index = 0; index < propertyInfos.Length; ++index) {
|
||||||
for(int index = 0; index < propertyInfos.Length; ++index) {
|
PropertyInfo propertyInfo = propertyInfos[index];
|
||||||
PropertyInfo propertyInfo = propertyInfos[index];
|
if(propertyInfo.CanRead && propertyInfo.CanWrite) {
|
||||||
if(propertyInfo.CanRead && propertyInfo.CanWrite) {
|
propertyInfo.SetValue(
|
||||||
propertyInfo.SetValue(
|
deserializedObject,
|
||||||
deserializedObject,
|
info.GetValue(propertyInfo.Name, propertyInfo.PropertyType),
|
||||||
info.GetValue(propertyInfo.Name, propertyInfo.PropertyType),
|
null
|
||||||
null
|
);
|
||||||
);
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
return deserializedObject;
|
||||||
return deserializedObject;
|
}
|
||||||
}
|
|
||||||
|
}
|
||||||
}
|
|
||||||
|
#endregion // class PropertySerializationSurrogate
|
||||||
#endregion // class PropertySerializationSurrogate
|
|
||||||
|
/// <summary>Initializes the static members of the serialization-based cloner</summary>
|
||||||
/// <summary>Initializes the static members of the serialization-based cloner</summary>
|
static SerializationCloner() {
|
||||||
static SerializationCloner() {
|
fieldBasedFormatter = new BinaryFormatter(
|
||||||
fieldBasedFormatter = new BinaryFormatter(
|
new StaticSurrogateSelector(new FieldSerializationSurrogate()),
|
||||||
new StaticSurrogateSelector(new FieldSerializationSurrogate()),
|
new StreamingContext(StreamingContextStates.All)
|
||||||
new StreamingContext(StreamingContextStates.All)
|
);
|
||||||
);
|
propertyBasedFormatter = new BinaryFormatter(
|
||||||
propertyBasedFormatter = new BinaryFormatter(
|
new StaticSurrogateSelector(new PropertySerializationSurrogate()),
|
||||||
new StaticSurrogateSelector(new PropertySerializationSurrogate()),
|
new StreamingContext(StreamingContextStates.All)
|
||||||
new StreamingContext(StreamingContextStates.All)
|
);
|
||||||
);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Creates a deep clone of the specified object, also creating clones of all
|
||||||
/// Creates a deep clone of the specified object, also creating clones of all
|
/// child objects being referenced
|
||||||
/// child objects being referenced
|
/// </summary>
|
||||||
/// </summary>
|
/// <typeparam name="TCloned">Type of the object that will be cloned</typeparam>
|
||||||
/// <typeparam name="TCloned">Type of the object that will be cloned</typeparam>
|
/// <param name="objectToClone">Object that will be cloned</param>
|
||||||
/// <param name="objectToClone">Object that will be cloned</param>
|
/// <returns>A deep clone of the provided object</returns>
|
||||||
/// <returns>A deep clone of the provided object</returns>
|
public static TCloned DeepFieldClone<TCloned>(TCloned objectToClone) {
|
||||||
public static TCloned DeepFieldClone<TCloned>(TCloned objectToClone) {
|
if(typeof(TCloned).IsClass || typeof(TCloned).IsArray) {
|
||||||
if(typeof(TCloned).IsClass || typeof(TCloned).IsArray) {
|
if(ReferenceEquals(objectToClone, null)) {
|
||||||
if(ReferenceEquals(objectToClone, null)) {
|
return default(TCloned);
|
||||||
return default(TCloned);
|
}
|
||||||
}
|
}
|
||||||
}
|
using(var memoryStream = new MemoryStream()) {
|
||||||
using(var memoryStream = new MemoryStream()) {
|
fieldBasedFormatter.Serialize(memoryStream, objectToClone);
|
||||||
fieldBasedFormatter.Serialize(memoryStream, objectToClone);
|
memoryStream.Position = 0;
|
||||||
memoryStream.Position = 0;
|
return (TCloned)fieldBasedFormatter.Deserialize(memoryStream);
|
||||||
return (TCloned)fieldBasedFormatter.Deserialize(memoryStream);
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Creates a deep clone of the specified object, also creating clones of all
|
||||||
/// Creates a deep clone of the specified object, also creating clones of all
|
/// child objects being referenced
|
||||||
/// child objects being referenced
|
/// </summary>
|
||||||
/// </summary>
|
/// <typeparam name="TCloned">Type of the object that will be cloned</typeparam>
|
||||||
/// <typeparam name="TCloned">Type of the object that will be cloned</typeparam>
|
/// <param name="objectToClone">Object that will be cloned</param>
|
||||||
/// <param name="objectToClone">Object that will be cloned</param>
|
/// <returns>A deep clone of the provided object</returns>
|
||||||
/// <returns>A deep clone of the provided object</returns>
|
public static TCloned DeepPropertyClone<TCloned>(TCloned objectToClone) {
|
||||||
public static TCloned DeepPropertyClone<TCloned>(TCloned objectToClone) {
|
if(typeof(TCloned).IsClass || typeof(TCloned).IsArray) {
|
||||||
if(typeof(TCloned).IsClass || typeof(TCloned).IsArray) {
|
if(ReferenceEquals(objectToClone, null)) {
|
||||||
if(ReferenceEquals(objectToClone, null)) {
|
return default(TCloned);
|
||||||
return default(TCloned);
|
}
|
||||||
}
|
}
|
||||||
}
|
using(var memoryStream = new MemoryStream()) {
|
||||||
using(var memoryStream = new MemoryStream()) {
|
propertyBasedFormatter.Serialize(memoryStream, objectToClone);
|
||||||
propertyBasedFormatter.Serialize(memoryStream, objectToClone);
|
memoryStream.Position = 0;
|
||||||
memoryStream.Position = 0;
|
return (TCloned)propertyBasedFormatter.Deserialize(memoryStream);
|
||||||
return (TCloned)propertyBasedFormatter.Deserialize(memoryStream);
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Creates a shallow clone of the specified object, reusing any referenced objects
|
||||||
/// Creates a shallow clone of the specified object, reusing any referenced objects
|
/// </summary>
|
||||||
/// </summary>
|
/// <typeparam name="TCloned">Type of the object that will be cloned</typeparam>
|
||||||
/// <typeparam name="TCloned">Type of the object that will be cloned</typeparam>
|
/// <param name="objectToClone">Object that will be cloned</param>
|
||||||
/// <param name="objectToClone">Object that will be cloned</param>
|
/// <returns>A shallow clone of the provided object</returns>
|
||||||
/// <returns>A shallow clone of the provided object</returns>
|
TCloned ICloneFactory.ShallowFieldClone<TCloned>(TCloned objectToClone) {
|
||||||
TCloned ICloneFactory.ShallowFieldClone<TCloned>(TCloned objectToClone) {
|
throw new NotSupportedException("The serialization cloner cannot create shallow clones");
|
||||||
throw new NotSupportedException("The serialization cloner cannot create shallow clones");
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Creates a shallow clone of the specified object, reusing any referenced objects
|
||||||
/// Creates a shallow clone of the specified object, reusing any referenced objects
|
/// </summary>
|
||||||
/// </summary>
|
/// <typeparam name="TCloned">Type of the object that will be cloned</typeparam>
|
||||||
/// <typeparam name="TCloned">Type of the object that will be cloned</typeparam>
|
/// <param name="objectToClone">Object that will be cloned</param>
|
||||||
/// <param name="objectToClone">Object that will be cloned</param>
|
/// <returns>A shallow clone of the provided object</returns>
|
||||||
/// <returns>A shallow clone of the provided object</returns>
|
TCloned ICloneFactory.ShallowPropertyClone<TCloned>(TCloned objectToClone) {
|
||||||
TCloned ICloneFactory.ShallowPropertyClone<TCloned>(TCloned objectToClone) {
|
throw new NotSupportedException("The serialization cloner cannot create shallow clones");
|
||||||
throw new NotSupportedException("The serialization cloner cannot create shallow clones");
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Creates a deep clone of the specified object, also creating clones of all
|
||||||
/// Creates a deep clone of the specified object, also creating clones of all
|
/// child objects being referenced
|
||||||
/// child objects being referenced
|
/// </summary>
|
||||||
/// </summary>
|
/// <typeparam name="TCloned">Type of the object that will be cloned</typeparam>
|
||||||
/// <typeparam name="TCloned">Type of the object that will be cloned</typeparam>
|
/// <param name="objectToClone">Object that will be cloned</param>
|
||||||
/// <param name="objectToClone">Object that will be cloned</param>
|
/// <returns>A deep clone of the provided object</returns>
|
||||||
/// <returns>A deep clone of the provided object</returns>
|
TCloned ICloneFactory.DeepFieldClone<TCloned>(TCloned objectToClone) {
|
||||||
TCloned ICloneFactory.DeepFieldClone<TCloned>(TCloned objectToClone) {
|
return SerializationCloner.DeepFieldClone<TCloned>(objectToClone);
|
||||||
return SerializationCloner.DeepFieldClone<TCloned>(objectToClone);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Creates a deep clone of the specified object, also creating clones of all
|
||||||
/// Creates a deep clone of the specified object, also creating clones of all
|
/// child objects being referenced
|
||||||
/// child objects being referenced
|
/// </summary>
|
||||||
/// </summary>
|
/// <typeparam name="TCloned">Type of the object that will be cloned</typeparam>
|
||||||
/// <typeparam name="TCloned">Type of the object that will be cloned</typeparam>
|
/// <param name="objectToClone">Object that will be cloned</param>
|
||||||
/// <param name="objectToClone">Object that will be cloned</param>
|
/// <returns>A deep clone of the provided object</returns>
|
||||||
/// <returns>A deep clone of the provided object</returns>
|
TCloned ICloneFactory.DeepPropertyClone<TCloned>(TCloned objectToClone) {
|
||||||
TCloned ICloneFactory.DeepPropertyClone<TCloned>(TCloned objectToClone) {
|
return SerializationCloner.DeepPropertyClone<TCloned>(objectToClone);
|
||||||
return SerializationCloner.DeepPropertyClone<TCloned>(objectToClone);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Serializes objects by storing their fields</summary>
|
||||||
/// <summary>Serializes objects by storing their fields</summary>
|
private static readonly BinaryFormatter fieldBasedFormatter;
|
||||||
private static readonly BinaryFormatter fieldBasedFormatter;
|
/// <summary>Serializes objects by storing their properties</summary>
|
||||||
/// <summary>Serializes objects by storing their properties</summary>
|
private static readonly BinaryFormatter propertyBasedFormatter;
|
||||||
private static readonly BinaryFormatter propertyBasedFormatter;
|
|
||||||
|
}
|
||||||
}
|
|
||||||
|
} // namespace Nuclex.Support.Cloning
|
||||||
} // namespace Nuclex.Support.Cloning
|
|
||||||
|
|
|
@ -1,53 +1,52 @@
|
||||||
#region CPL License
|
#region Apache License 2.0
|
||||||
/*
|
/*
|
||||||
Nuclex Framework
|
Nuclex .NET Framework
|
||||||
Copyright (C) 2002-2017 Nuclex Development Labs
|
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
|
||||||
|
|
||||||
This library is free software; you can redistribute it and/or
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
modify it under the terms of the IBM Common Public License as
|
you may not use this file except in compliance with the License.
|
||||||
published by the IBM Corporation; either version 1.0 of the
|
You may obtain a copy of the License at
|
||||||
License, or (at your option) any later version.
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
This library is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
Unless required by applicable law or agreed to in writing, software
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
IBM Common Public License for more details.
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
You should have received a copy of the IBM Common Public
|
limitations under the License.
|
||||||
License along with this library
|
*/
|
||||||
*/
|
#endregion // Apache License 2.0
|
||||||
#endregion
|
|
||||||
|
#if UNITTEST
|
||||||
#if UNITTEST
|
|
||||||
|
using System;
|
||||||
using System;
|
using System.Collections.Specialized;
|
||||||
using System.Collections.Specialized;
|
|
||||||
|
using NUnit.Framework;
|
||||||
using NUnit.Framework;
|
|
||||||
|
namespace Nuclex.Support.Collections {
|
||||||
namespace Nuclex.Support.Collections {
|
|
||||||
|
/// <summary>Unit Test for the collection constants</summary>
|
||||||
/// <summary>Unit Test for the collection constants</summary>
|
[TestFixture]
|
||||||
[TestFixture]
|
internal class ConstantsTest {
|
||||||
internal class ConstantsTest {
|
|
||||||
|
#if !NO_SPECIALIZED_COLLECTIONS
|
||||||
#if !NO_SPECIALIZED_COLLECTIONS
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Verifies that the collection reset event arguments have 'reset' specified as
|
||||||
/// Verifies that the collection reset event arguments have 'reset' specified as
|
/// their action
|
||||||
/// their action
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void CollectionResetEventArgsHaveResetActionSet() {
|
||||||
public void CollectionResetEventArgsHaveResetActionSet() {
|
Assert.AreEqual(
|
||||||
Assert.AreEqual(
|
NotifyCollectionChangedAction.Reset, Constants.NotifyCollectionResetEventArgs.Action
|
||||||
NotifyCollectionChangedAction.Reset, Constants.NotifyCollectionResetEventArgs.Action
|
);
|
||||||
);
|
}
|
||||||
}
|
|
||||||
|
#endif // !NO_SPECIALIZED_COLLECTIONS
|
||||||
#endif // !NO_SPECIALIZED_COLLECTIONS
|
|
||||||
|
}
|
||||||
}
|
|
||||||
|
} // namespace Nuclex.Support.Collections
|
||||||
} // namespace Nuclex.Support.Collections
|
|
||||||
|
#endif // UNITTEST
|
||||||
#endif // UNITTEST
|
|
||||||
|
|
|
@ -1,39 +1,38 @@
|
||||||
#region CPL License
|
#region Apache License 2.0
|
||||||
/*
|
/*
|
||||||
Nuclex Framework
|
Nuclex .NET Framework
|
||||||
Copyright (C) 2002-2017 Nuclex Development Labs
|
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
|
||||||
|
|
||||||
This library is free software; you can redistribute it and/or
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
modify it under the terms of the IBM Common Public License as
|
you may not use this file except in compliance with the License.
|
||||||
published by the IBM Corporation; either version 1.0 of the
|
You may obtain a copy of the License at
|
||||||
License, or (at your option) any later version.
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
This library is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
Unless required by applicable law or agreed to in writing, software
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
IBM Common Public License for more details.
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
You should have received a copy of the IBM Common Public
|
limitations under the License.
|
||||||
License along with this library
|
*/
|
||||||
*/
|
#endregion // Apache License 2.0
|
||||||
#endregion
|
|
||||||
|
using System;
|
||||||
using System;
|
#if !NO_SPECIALIZED_COLLECTIONS
|
||||||
#if !NO_SPECIALIZED_COLLECTIONS
|
using System.Collections.Specialized;
|
||||||
using System.Collections.Specialized;
|
#endif
|
||||||
#endif
|
|
||||||
|
namespace Nuclex.Support.Collections {
|
||||||
namespace Nuclex.Support.Collections {
|
|
||||||
|
/// <summary>Contains fixed constants used by some collections</summary>
|
||||||
/// <summary>Contains fixed constants used by some collections</summary>
|
public static class Constants {
|
||||||
public static class Constants {
|
|
||||||
|
#if !NO_SPECIALIZED_COLLECTIONS
|
||||||
#if !NO_SPECIALIZED_COLLECTIONS
|
/// <summary>Fixed event args used to notify that the collection has reset</summary>
|
||||||
/// <summary>Fixed event args used to notify that the collection has reset</summary>
|
public static readonly NotifyCollectionChangedEventArgs NotifyCollectionResetEventArgs =
|
||||||
public static readonly NotifyCollectionChangedEventArgs NotifyCollectionResetEventArgs =
|
new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset);
|
||||||
new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset);
|
#endif
|
||||||
#endif
|
|
||||||
|
}
|
||||||
}
|
|
||||||
|
} // namespace Nuclex.Support.Collections
|
||||||
} // namespace Nuclex.Support.Collections
|
|
||||||
|
|
|
@ -1,211 +1,210 @@
|
||||||
#region CPL License
|
#region Apache License 2.0
|
||||||
/*
|
/*
|
||||||
Nuclex Framework
|
Nuclex .NET Framework
|
||||||
Copyright (C) 2002-2017 Nuclex Development Labs
|
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
|
||||||
|
|
||||||
This library is free software; you can redistribute it and/or
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
modify it under the terms of the IBM Common Public License as
|
you may not use this file except in compliance with the License.
|
||||||
published by the IBM Corporation; either version 1.0 of the
|
You may obtain a copy of the License at
|
||||||
License, or (at your option) any later version.
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
This library is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
Unless required by applicable law or agreed to in writing, software
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
IBM Common Public License for more details.
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
You should have received a copy of the IBM Common Public
|
limitations under the License.
|
||||||
License along with this library
|
*/
|
||||||
*/
|
#endregion // Apache License 2.0
|
||||||
#endregion
|
|
||||||
|
using System;
|
||||||
using System;
|
|
||||||
|
namespace Nuclex.Support.Collections {
|
||||||
namespace Nuclex.Support.Collections {
|
|
||||||
|
partial class Deque<TItem> {
|
||||||
partial class Deque<TItem> {
|
|
||||||
|
/// <summary>Inserts an item at the beginning of the double-ended queue</summary>
|
||||||
/// <summary>Inserts an item at the beginning of the double-ended queue</summary>
|
/// <param name="item">Item that will be inserted into the queue</param>
|
||||||
/// <param name="item">Item that will be inserted into the queue</param>
|
public void AddFirst(TItem item) {
|
||||||
public void AddFirst(TItem item) {
|
if(this.firstBlockStartIndex > 0) {
|
||||||
if(this.firstBlockStartIndex > 0) {
|
--this.firstBlockStartIndex;
|
||||||
--this.firstBlockStartIndex;
|
} else { // Need to allocate a new block
|
||||||
} else { // Need to allocate a new block
|
this.blocks.Insert(0, new TItem[this.blockSize]);
|
||||||
this.blocks.Insert(0, new TItem[this.blockSize]);
|
this.firstBlockStartIndex = this.blockSize - 1;
|
||||||
this.firstBlockStartIndex = this.blockSize - 1;
|
}
|
||||||
}
|
|
||||||
|
this.blocks[0][this.firstBlockStartIndex] = item;
|
||||||
this.blocks[0][this.firstBlockStartIndex] = item;
|
++this.count;
|
||||||
++this.count;
|
#if DEBUG
|
||||||
#if DEBUG
|
++this.version;
|
||||||
++this.version;
|
#endif
|
||||||
#endif
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Appends an item to the end of the double-ended queue</summary>
|
||||||
/// <summary>Appends an item to the end of the double-ended queue</summary>
|
/// <param name="item">Item that will be appended to the queue</param>
|
||||||
/// <param name="item">Item that will be appended to the queue</param>
|
public void AddLast(TItem item) {
|
||||||
public void AddLast(TItem item) {
|
if(this.lastBlockEndIndex < this.blockSize) {
|
||||||
if(this.lastBlockEndIndex < this.blockSize) {
|
++this.lastBlockEndIndex;
|
||||||
++this.lastBlockEndIndex;
|
} else { // Need to allocate a new block
|
||||||
} else { // Need to allocate a new block
|
this.blocks.Add(new TItem[this.blockSize]);
|
||||||
this.blocks.Add(new TItem[this.blockSize]);
|
this.lastBlockEndIndex = 1;
|
||||||
this.lastBlockEndIndex = 1;
|
}
|
||||||
}
|
|
||||||
|
this.blocks[this.blocks.Count - 1][this.lastBlockEndIndex - 1] = item;
|
||||||
this.blocks[this.blocks.Count - 1][this.lastBlockEndIndex - 1] = item;
|
++this.count;
|
||||||
++this.count;
|
#if DEBUG
|
||||||
#if DEBUG
|
++this.version;
|
||||||
++this.version;
|
#endif
|
||||||
#endif
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Inserts the item at the specified index</summary>
|
||||||
/// <summary>Inserts the item at the specified index</summary>
|
/// <param name="index">Index the item will be inserted at</param>
|
||||||
/// <param name="index">Index the item will be inserted at</param>
|
/// <param name="item">Item that will be inserted</param>
|
||||||
/// <param name="item">Item that will be inserted</param>
|
public void Insert(int index, TItem item) {
|
||||||
public void Insert(int index, TItem item) {
|
int distanceToRightEnd = this.count - index;
|
||||||
int distanceToRightEnd = this.count - index;
|
if(index < distanceToRightEnd) { // Are we closer to the left end?
|
||||||
if(index < distanceToRightEnd) { // Are we closer to the left end?
|
shiftLeftAndInsert(index, item);
|
||||||
shiftLeftAndInsert(index, item);
|
} else { // Nope, we're closer to the right end
|
||||||
} else { // Nope, we're closer to the right end
|
shiftRightAndInsert(index, item);
|
||||||
shiftRightAndInsert(index, item);
|
}
|
||||||
}
|
#if DEBUG
|
||||||
#if DEBUG
|
++this.version;
|
||||||
++this.version;
|
#endif
|
||||||
#endif
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Shifts all items before the insertion point to the left and inserts
|
||||||
/// Shifts all items before the insertion point to the left and inserts
|
/// the item at the specified index
|
||||||
/// the item at the specified index
|
/// </summary>
|
||||||
/// </summary>
|
/// <param name="index">Index the item will be inserted at</param>
|
||||||
/// <param name="index">Index the item will be inserted at</param>
|
/// <param name="item">Item that will be inserted</param>
|
||||||
/// <param name="item">Item that will be inserted</param>
|
private void shiftLeftAndInsert(int index, TItem item) {
|
||||||
private void shiftLeftAndInsert(int index, TItem item) {
|
if(index == 0) {
|
||||||
if(index == 0) {
|
AddFirst(item);
|
||||||
AddFirst(item);
|
} else {
|
||||||
} else {
|
int blockIndex, subIndex;
|
||||||
int blockIndex, subIndex;
|
findIndex(index, out blockIndex, out subIndex);
|
||||||
findIndex(index, out blockIndex, out subIndex);
|
|
||||||
|
int firstBlock = 0;
|
||||||
int firstBlock = 0;
|
int blockStart;
|
||||||
int blockStart;
|
|
||||||
|
// If the first block is full, we need to add another block
|
||||||
// If the first block is full, we need to add another block
|
if(this.firstBlockStartIndex == 0) {
|
||||||
if(this.firstBlockStartIndex == 0) {
|
this.blocks.Insert(0, new TItem[this.blockSize]);
|
||||||
this.blocks.Insert(0, new TItem[this.blockSize]);
|
this.blocks[0][this.blockSize - 1] = this.blocks[1][0];
|
||||||
this.blocks[0][this.blockSize - 1] = this.blocks[1][0];
|
this.firstBlockStartIndex = this.blockSize - 1;
|
||||||
this.firstBlockStartIndex = this.blockSize - 1;
|
|
||||||
|
blockStart = 1;
|
||||||
blockStart = 1;
|
--subIndex;
|
||||||
--subIndex;
|
if(subIndex < 0) {
|
||||||
if(subIndex < 0) {
|
subIndex = this.blockSize - 1;
|
||||||
subIndex = this.blockSize - 1;
|
} else {
|
||||||
} else {
|
++blockIndex;
|
||||||
++blockIndex;
|
}
|
||||||
}
|
++firstBlock;
|
||||||
++firstBlock;
|
} else {
|
||||||
} else {
|
blockStart = this.firstBlockStartIndex;
|
||||||
blockStart = this.firstBlockStartIndex;
|
--this.firstBlockStartIndex;
|
||||||
--this.firstBlockStartIndex;
|
|
||||||
|
--subIndex;
|
||||||
--subIndex;
|
if(subIndex < 0) {
|
||||||
if(subIndex < 0) {
|
subIndex = this.blockSize - 1;
|
||||||
subIndex = this.blockSize - 1;
|
--blockIndex;
|
||||||
--blockIndex;
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
// If the insertion point is not in the first block
|
||||||
// If the insertion point is not in the first block
|
if(blockIndex != firstBlock) {
|
||||||
if(blockIndex != firstBlock) {
|
Array.Copy(
|
||||||
Array.Copy(
|
this.blocks[firstBlock], blockStart,
|
||||||
this.blocks[firstBlock], blockStart,
|
this.blocks[firstBlock], blockStart - 1,
|
||||||
this.blocks[firstBlock], blockStart - 1,
|
this.blockSize - blockStart
|
||||||
this.blockSize - blockStart
|
);
|
||||||
);
|
this.blocks[firstBlock][this.blockSize - 1] = this.blocks[firstBlock + 1][0];
|
||||||
this.blocks[firstBlock][this.blockSize - 1] = this.blocks[firstBlock + 1][0];
|
|
||||||
|
// Move all the blocks following the insertion point to the right by one item.
|
||||||
// Move all the blocks following the insertion point to the right by one item.
|
// If there are no blocks inbetween, this for loop will not run.
|
||||||
// If there are no blocks inbetween, this for loop will not run.
|
for(int tempIndex = firstBlock + 1; tempIndex < blockIndex; ++tempIndex) {
|
||||||
for(int tempIndex = firstBlock + 1; tempIndex < blockIndex; ++tempIndex) {
|
Array.Copy(
|
||||||
Array.Copy(
|
this.blocks[tempIndex], 1, this.blocks[tempIndex], 0, this.blockSize - 1
|
||||||
this.blocks[tempIndex], 1, this.blocks[tempIndex], 0, this.blockSize - 1
|
);
|
||||||
);
|
this.blocks[tempIndex][this.blockSize - 1] = this.blocks[tempIndex + 1][0];
|
||||||
this.blocks[tempIndex][this.blockSize - 1] = this.blocks[tempIndex + 1][0];
|
}
|
||||||
}
|
|
||||||
|
blockStart = 1;
|
||||||
blockStart = 1;
|
}
|
||||||
}
|
|
||||||
|
// Finally, move the items in the block the insertion takes place in
|
||||||
// Finally, move the items in the block the insertion takes place in
|
Array.Copy(
|
||||||
Array.Copy(
|
this.blocks[blockIndex], blockStart,
|
||||||
this.blocks[blockIndex], blockStart,
|
this.blocks[blockIndex], blockStart - 1,
|
||||||
this.blocks[blockIndex], blockStart - 1,
|
subIndex - blockStart + 1
|
||||||
subIndex - blockStart + 1
|
);
|
||||||
);
|
|
||||||
|
this.blocks[blockIndex][subIndex] = item;
|
||||||
this.blocks[blockIndex][subIndex] = item;
|
++this.count;
|
||||||
++this.count;
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Shifts all items after the insertion point to the right and inserts
|
||||||
/// Shifts all items after the insertion point to the right and inserts
|
/// the item at the specified index
|
||||||
/// the item at the specified index
|
/// </summary>
|
||||||
/// </summary>
|
/// <param name="index">Index the item will be inserted at</param>
|
||||||
/// <param name="index">Index the item will be inserted at</param>
|
/// <param name="item">Item that will be inserted</param>
|
||||||
/// <param name="item">Item that will be inserted</param>
|
private void shiftRightAndInsert(int index, TItem item) {
|
||||||
private void shiftRightAndInsert(int index, TItem item) {
|
if(index == this.count) {
|
||||||
if(index == this.count) {
|
AddLast(item);
|
||||||
AddLast(item);
|
} else {
|
||||||
} else {
|
int blockIndex, subIndex;
|
||||||
int blockIndex, subIndex;
|
findIndex(index, out blockIndex, out subIndex);
|
||||||
findIndex(index, out blockIndex, out subIndex);
|
|
||||||
|
int lastBlock = this.blocks.Count - 1;
|
||||||
int lastBlock = this.blocks.Count - 1;
|
int blockLength;
|
||||||
int blockLength;
|
|
||||||
|
// If the lastmost block is full, we need to add another block
|
||||||
// If the lastmost block is full, we need to add another block
|
if(this.lastBlockEndIndex == this.blockSize) {
|
||||||
if(this.lastBlockEndIndex == this.blockSize) {
|
this.blocks.Add(new TItem[this.blockSize]);
|
||||||
this.blocks.Add(new TItem[this.blockSize]);
|
this.blocks[lastBlock + 1][0] = this.blocks[lastBlock][this.blockSize - 1];
|
||||||
this.blocks[lastBlock + 1][0] = this.blocks[lastBlock][this.blockSize - 1];
|
this.lastBlockEndIndex = 1;
|
||||||
this.lastBlockEndIndex = 1;
|
|
||||||
|
blockLength = this.blockSize - 1;
|
||||||
blockLength = this.blockSize - 1;
|
} else {
|
||||||
} else {
|
blockLength = this.lastBlockEndIndex;
|
||||||
blockLength = this.lastBlockEndIndex;
|
++this.lastBlockEndIndex;
|
||||||
++this.lastBlockEndIndex;
|
}
|
||||||
}
|
|
||||||
|
// If the insertion point is not in the lastmost block
|
||||||
// If the insertion point is not in the lastmost block
|
if(blockIndex != lastBlock) {
|
||||||
if(blockIndex != lastBlock) {
|
Array.Copy(
|
||||||
Array.Copy(
|
this.blocks[lastBlock], 0, this.blocks[lastBlock], 1, blockLength
|
||||||
this.blocks[lastBlock], 0, this.blocks[lastBlock], 1, blockLength
|
);
|
||||||
);
|
this.blocks[lastBlock][0] = this.blocks[lastBlock - 1][this.blockSize - 1];
|
||||||
this.blocks[lastBlock][0] = this.blocks[lastBlock - 1][this.blockSize - 1];
|
|
||||||
|
// Move all the blocks following the insertion point to the right by one item.
|
||||||
// Move all the blocks following the insertion point to the right by one item.
|
// If there are no blocks inbetween, this for loop will not run.
|
||||||
// If there are no blocks inbetween, this for loop will not run.
|
for(int tempIndex = lastBlock - 1; tempIndex > blockIndex; --tempIndex) {
|
||||||
for(int tempIndex = lastBlock - 1; tempIndex > blockIndex; --tempIndex) {
|
Array.Copy(
|
||||||
Array.Copy(
|
this.blocks[tempIndex], 0, this.blocks[tempIndex], 1, this.blockSize - 1
|
||||||
this.blocks[tempIndex], 0, this.blocks[tempIndex], 1, this.blockSize - 1
|
);
|
||||||
);
|
this.blocks[tempIndex][0] = this.blocks[tempIndex - 1][this.blockSize - 1];
|
||||||
this.blocks[tempIndex][0] = this.blocks[tempIndex - 1][this.blockSize - 1];
|
}
|
||||||
}
|
|
||||||
|
blockLength = this.blockSize - 1;
|
||||||
blockLength = this.blockSize - 1;
|
}
|
||||||
}
|
|
||||||
|
// Finally, move the items in the block the insertion takes place in
|
||||||
// Finally, move the items in the block the insertion takes place in
|
Array.Copy(
|
||||||
Array.Copy(
|
this.blocks[blockIndex], subIndex,
|
||||||
this.blocks[blockIndex], subIndex,
|
this.blocks[blockIndex], subIndex + 1,
|
||||||
this.blocks[blockIndex], subIndex + 1,
|
blockLength - subIndex
|
||||||
blockLength - subIndex
|
);
|
||||||
);
|
|
||||||
|
this.blocks[blockIndex][subIndex] = item;
|
||||||
this.blocks[blockIndex][subIndex] = item;
|
++this.count;
|
||||||
++this.count;
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
}
|
||||||
}
|
|
||||||
|
} // namespace Nuclex.Support.Collections
|
||||||
} // namespace Nuclex.Support.Collections
|
|
||||||
|
|
|
@ -1,150 +1,149 @@
|
||||||
#region CPL License
|
#region Apache License 2.0
|
||||||
/*
|
/*
|
||||||
Nuclex Framework
|
Nuclex .NET Framework
|
||||||
Copyright (C) 2002-2017 Nuclex Development Labs
|
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
|
||||||
|
|
||||||
This library is free software; you can redistribute it and/or
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
modify it under the terms of the IBM Common Public License as
|
you may not use this file except in compliance with the License.
|
||||||
published by the IBM Corporation; either version 1.0 of the
|
You may obtain a copy of the License at
|
||||||
License, or (at your option) any later version.
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
This library is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
Unless required by applicable law or agreed to in writing, software
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
IBM Common Public License for more details.
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
You should have received a copy of the IBM Common Public
|
limitations under the License.
|
||||||
License along with this library
|
*/
|
||||||
*/
|
#endregion // Apache License 2.0
|
||||||
#endregion
|
|
||||||
|
using System;
|
||||||
using System;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Generic;
|
using System.Collections;
|
||||||
using System.Collections;
|
|
||||||
|
namespace Nuclex.Support.Collections {
|
||||||
namespace Nuclex.Support.Collections {
|
|
||||||
|
partial class Deque<TItem> {
|
||||||
partial class Deque<TItem> {
|
|
||||||
|
#region IEnumerable members
|
||||||
#region IEnumerable members
|
|
||||||
|
/// <summary>Obtains a new enumerator for the contents of the deque</summary>
|
||||||
/// <summary>Obtains a new enumerator for the contents of the deque</summary>
|
/// <returns>The new enumerator</returns>
|
||||||
/// <returns>The new enumerator</returns>
|
IEnumerator IEnumerable.GetEnumerator() {
|
||||||
IEnumerator IEnumerable.GetEnumerator() {
|
return new Enumerator(this);
|
||||||
return new Enumerator(this);
|
}
|
||||||
}
|
|
||||||
|
#endregion
|
||||||
#endregion
|
|
||||||
|
#region IList Members
|
||||||
#region IList Members
|
|
||||||
|
/// <summary>Adds an item to the deque</summary>
|
||||||
/// <summary>Adds an item to the deque</summary>
|
/// <param name="value">Item that will be added to the deque</param>
|
||||||
/// <param name="value">Item that will be added to the deque</param>
|
/// <returns>The index at which the new item was added</returns>
|
||||||
/// <returns>The index at which the new item was added</returns>
|
int IList.Add(object value) {
|
||||||
int IList.Add(object value) {
|
verifyCompatibleObject(value);
|
||||||
verifyCompatibleObject(value);
|
|
||||||
|
AddLast((TItem)value);
|
||||||
AddLast((TItem)value);
|
return this.count - 1;
|
||||||
return this.count - 1;
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Checks whether the deque contains the specified item</summary>
|
||||||
/// <summary>Checks whether the deque contains the specified item</summary>
|
/// <param name="value">Item the deque will be scanned for</param>
|
||||||
/// <param name="value">Item the deque will be scanned for</param>
|
/// <returns>True if the deque contained the specified item</returns>
|
||||||
/// <returns>True if the deque contained the specified item</returns>
|
bool IList.Contains(object value) {
|
||||||
bool IList.Contains(object value) {
|
return isCompatibleObject(value) && Contains((TItem)value);
|
||||||
return isCompatibleObject(value) && Contains((TItem)value);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Determines the index of the item in the deque</summary>
|
||||||
/// <summary>Determines the index of the item in the deque</summary>
|
/// <param name="value">Item whose index will be determined</param>
|
||||||
/// <param name="value">Item whose index will be determined</param>
|
/// <returns>The index of the specified item in the deque</returns>
|
||||||
/// <returns>The index of the specified item in the deque</returns>
|
int IList.IndexOf(object value) {
|
||||||
int IList.IndexOf(object value) {
|
if(isCompatibleObject(value)) {
|
||||||
if(isCompatibleObject(value)) {
|
return IndexOf((TItem)value);
|
||||||
return IndexOf((TItem)value);
|
} else {
|
||||||
} else {
|
return -1;
|
||||||
return -1;
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Inserts an item into the deque at the specified location</summary>
|
||||||
/// <summary>Inserts an item into the deque at the specified location</summary>
|
/// <param name="index">Index at which the item will be inserted</param>
|
||||||
/// <param name="index">Index at which the item will be inserted</param>
|
/// <param name="value">Item that will be inserted</param>
|
||||||
/// <param name="value">Item that will be inserted</param>
|
void IList.Insert(int index, object value) {
|
||||||
void IList.Insert(int index, object value) {
|
verifyCompatibleObject(value);
|
||||||
verifyCompatibleObject(value);
|
Insert(index, (TItem)value);
|
||||||
Insert(index, (TItem)value);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Whether the deque has a fixed size</summary>
|
||||||
/// <summary>Whether the deque has a fixed size</summary>
|
bool IList.IsFixedSize {
|
||||||
bool IList.IsFixedSize {
|
get { return false; }
|
||||||
get { return false; }
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Whether the deque is read-only</summary>
|
||||||
/// <summary>Whether the deque is read-only</summary>
|
bool IList.IsReadOnly {
|
||||||
bool IList.IsReadOnly {
|
get { return false; }
|
||||||
get { return false; }
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Removes the specified item from the deque</summary>
|
||||||
/// <summary>Removes the specified item from the deque</summary>
|
/// <param name="value">Item that will be removed from the deque</param>
|
||||||
/// <param name="value">Item that will be removed from the deque</param>
|
void IList.Remove(object value) {
|
||||||
void IList.Remove(object value) {
|
if(isCompatibleObject(value)) {
|
||||||
if(isCompatibleObject(value)) {
|
Remove((TItem)value);
|
||||||
Remove((TItem)value);
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Accesses an item in the deque by its index</summary>
|
||||||
/// <summary>Accesses an item in the deque by its index</summary>
|
/// <param name="index">Index of the item that will be accessed</param>
|
||||||
/// <param name="index">Index of the item that will be accessed</param>
|
/// <returns>The item at the specified index</returns>
|
||||||
/// <returns>The item at the specified index</returns>
|
object IList.this[int index] {
|
||||||
object IList.this[int index] {
|
get { return this[index]; }
|
||||||
get { return this[index]; }
|
set {
|
||||||
set {
|
verifyCompatibleObject(value);
|
||||||
verifyCompatibleObject(value);
|
this[index] = (TItem)value;
|
||||||
this[index] = (TItem)value;
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
#endregion
|
||||||
#endregion
|
|
||||||
|
#region ICollection<ItemType> Members
|
||||||
#region ICollection<ItemType> Members
|
|
||||||
|
/// <summary>Adds an item into the deque</summary>
|
||||||
/// <summary>Adds an item into the deque</summary>
|
/// <param name="item">Item that will be added to the deque</param>
|
||||||
/// <param name="item">Item that will be added to the deque</param>
|
void ICollection<TItem>.Add(TItem item) {
|
||||||
void ICollection<TItem>.Add(TItem item) {
|
AddLast(item);
|
||||||
AddLast(item);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Whether the collection is read-only</summary>
|
||||||
/// <summary>Whether the collection is read-only</summary>
|
bool ICollection<TItem>.IsReadOnly {
|
||||||
bool ICollection<TItem>.IsReadOnly {
|
get { return false; }
|
||||||
get { return false; }
|
}
|
||||||
}
|
|
||||||
|
#endregion
|
||||||
#endregion
|
|
||||||
|
#region ICollection Members
|
||||||
#region ICollection Members
|
|
||||||
|
/// <summary>Copies the contents of the deque into an array</summary>
|
||||||
/// <summary>Copies the contents of the deque into an array</summary>
|
/// <param name="array">Array the contents of the deque will be copied into</param>
|
||||||
/// <param name="array">Array the contents of the deque will be copied into</param>
|
/// <param name="index">Index at which writing into the array will begin</param>
|
||||||
/// <param name="index">Index at which writing into the array will begin</param>
|
void ICollection.CopyTo(Array array, int index) {
|
||||||
void ICollection.CopyTo(Array array, int index) {
|
if(!(array is TItem[])) {
|
||||||
if(!(array is TItem[])) {
|
throw new ArgumentException("Incompatible array type", "array");
|
||||||
throw new ArgumentException("Incompatible array type", "array");
|
}
|
||||||
}
|
|
||||||
|
CopyTo((TItem[])array, index);
|
||||||
CopyTo((TItem[])array, index);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Whether the deque is thread-synchronized</summary>
|
||||||
/// <summary>Whether the deque is thread-synchronized</summary>
|
bool ICollection.IsSynchronized {
|
||||||
bool ICollection.IsSynchronized {
|
get { return false; }
|
||||||
get { return false; }
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Synchronization root of the instance</summary>
|
||||||
/// <summary>Synchronization root of the instance</summary>
|
object ICollection.SyncRoot {
|
||||||
object ICollection.SyncRoot {
|
get { return this; }
|
||||||
get { return this; }
|
}
|
||||||
}
|
|
||||||
|
#endregion
|
||||||
#endregion
|
|
||||||
|
}
|
||||||
}
|
|
||||||
|
} // namespace Nuclex.Support.Collections
|
||||||
} // namespace Nuclex.Support.Collections
|
|
||||||
|
|
|
@ -1,259 +1,258 @@
|
||||||
#region CPL License
|
#region Apache License 2.0
|
||||||
/*
|
/*
|
||||||
Nuclex Framework
|
Nuclex .NET Framework
|
||||||
Copyright (C) 2002-2017 Nuclex Development Labs
|
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
|
||||||
|
|
||||||
This library is free software; you can redistribute it and/or
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
modify it under the terms of the IBM Common Public License as
|
you may not use this file except in compliance with the License.
|
||||||
published by the IBM Corporation; either version 1.0 of the
|
You may obtain a copy of the License at
|
||||||
License, or (at your option) any later version.
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
This library is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
Unless required by applicable law or agreed to in writing, software
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
IBM Common Public License for more details.
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
You should have received a copy of the IBM Common Public
|
limitations under the License.
|
||||||
License along with this library
|
*/
|
||||||
*/
|
#endregion // Apache License 2.0
|
||||||
#endregion
|
|
||||||
|
using System;
|
||||||
using System;
|
|
||||||
|
namespace Nuclex.Support.Collections {
|
||||||
namespace Nuclex.Support.Collections {
|
|
||||||
|
partial class Deque<TItem> {
|
||||||
partial class Deque<TItem> {
|
|
||||||
|
/// <summary>Removes all items from the deque</summary>
|
||||||
/// <summary>Removes all items from the deque</summary>
|
public void Clear() {
|
||||||
public void Clear() {
|
if(this.blocks.Count > 1) { // Are there multiple blocks?
|
||||||
if(this.blocks.Count > 1) { // Are there multiple blocks?
|
|
||||||
|
// Clear the items in the first block to avoid holding on to references
|
||||||
// Clear the items in the first block to avoid holding on to references
|
// in memory unreachable to the user
|
||||||
// in memory unreachable to the user
|
for(int index = this.firstBlockStartIndex; index < this.blockSize; ++index) {
|
||||||
for(int index = this.firstBlockStartIndex; index < this.blockSize; ++index) {
|
this.blocks[0][index] = default(TItem);
|
||||||
this.blocks[0][index] = default(TItem);
|
}
|
||||||
}
|
|
||||||
|
// Remove any other blocks
|
||||||
// Remove any other blocks
|
this.blocks.RemoveRange(1, this.blocks.Count - 1);
|
||||||
this.blocks.RemoveRange(1, this.blocks.Count - 1);
|
|
||||||
|
} else { // Nope, only a single block exists
|
||||||
} else { // Nope, only a single block exists
|
|
||||||
|
// Clear the items in the block to release any reference we may be keeping alive
|
||||||
// Clear the items in the block to release any reference we may be keeping alive
|
for(
|
||||||
for(
|
int index = this.firstBlockStartIndex; index < this.lastBlockEndIndex; ++index
|
||||||
int index = this.firstBlockStartIndex; index < this.lastBlockEndIndex; ++index
|
) {
|
||||||
) {
|
this.blocks[0][index] = default(TItem);
|
||||||
this.blocks[0][index] = default(TItem);
|
}
|
||||||
}
|
|
||||||
|
}
|
||||||
}
|
|
||||||
|
// Reset the counters to restart the deque from scratch
|
||||||
// Reset the counters to restart the deque from scratch
|
this.firstBlockStartIndex = 0;
|
||||||
this.firstBlockStartIndex = 0;
|
this.lastBlockEndIndex = 0;
|
||||||
this.lastBlockEndIndex = 0;
|
this.count = 0;
|
||||||
this.count = 0;
|
#if DEBUG
|
||||||
#if DEBUG
|
++this.version;
|
||||||
++this.version;
|
#endif
|
||||||
#endif
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Removes the specified item from the deque</summary>
|
||||||
/// <summary>Removes the specified item from the deque</summary>
|
/// <param name="item">Item that will be removed from the deque</param>
|
||||||
/// <param name="item">Item that will be removed from the deque</param>
|
/// <returns>True if the item was found and removed</returns>
|
||||||
/// <returns>True if the item was found and removed</returns>
|
public bool Remove(TItem item) {
|
||||||
public bool Remove(TItem item) {
|
int index = IndexOf(item);
|
||||||
int index = IndexOf(item);
|
if(index == -1) {
|
||||||
if(index == -1) {
|
return false;
|
||||||
return false;
|
}
|
||||||
}
|
|
||||||
|
RemoveAt(index);
|
||||||
RemoveAt(index);
|
#if DEBUG
|
||||||
#if DEBUG
|
++this.version;
|
||||||
++this.version;
|
#endif
|
||||||
#endif
|
return true;
|
||||||
return true;
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Removes the first item in the double-ended queue</summary>
|
||||||
/// <summary>Removes the first item in the double-ended queue</summary>
|
public void RemoveFirst() {
|
||||||
public void RemoveFirst() {
|
if(this.count == 0) {
|
||||||
if(this.count == 0) {
|
throw new InvalidOperationException("Cannot remove items from empty deque");
|
||||||
throw new InvalidOperationException("Cannot remove items from empty deque");
|
}
|
||||||
}
|
|
||||||
|
// This is necessary to make sure the deque doesn't hold dead objects alive
|
||||||
// This is necessary to make sure the deque doesn't hold dead objects alive
|
// in unreachable spaces of its memory.
|
||||||
// in unreachable spaces of its memory.
|
this.blocks[0][this.firstBlockStartIndex] = default(TItem);
|
||||||
this.blocks[0][this.firstBlockStartIndex] = default(TItem);
|
|
||||||
|
// Cut off the item from the first block. If the block became empty and it's
|
||||||
// Cut off the item from the first block. If the block became empty and it's
|
// not the last remaining block, remove it as well.
|
||||||
// not the last remaining block, remove it as well.
|
++this.firstBlockStartIndex;
|
||||||
++this.firstBlockStartIndex;
|
if(this.firstBlockStartIndex >= this.blockSize) { // Block became empty
|
||||||
if(this.firstBlockStartIndex >= this.blockSize) { // Block became empty
|
if(this.count > 1) { // Still more blocks in queue, remove block
|
||||||
if(this.count > 1) { // Still more blocks in queue, remove block
|
this.blocks.RemoveAt(0);
|
||||||
this.blocks.RemoveAt(0);
|
this.firstBlockStartIndex = 0;
|
||||||
this.firstBlockStartIndex = 0;
|
} else { // Last block - do not remove
|
||||||
} else { // Last block - do not remove
|
this.firstBlockStartIndex = 0;
|
||||||
this.firstBlockStartIndex = 0;
|
this.lastBlockEndIndex = 0;
|
||||||
this.lastBlockEndIndex = 0;
|
}
|
||||||
}
|
}
|
||||||
}
|
--this.count;
|
||||||
--this.count;
|
#if DEBUG
|
||||||
#if DEBUG
|
++this.version;
|
||||||
++this.version;
|
#endif
|
||||||
#endif
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Removes the last item in the double-ended queue</summary>
|
||||||
/// <summary>Removes the last item in the double-ended queue</summary>
|
public void RemoveLast() {
|
||||||
public void RemoveLast() {
|
if(this.count == 0) {
|
||||||
if(this.count == 0) {
|
throw new InvalidOperationException("Cannot remove items from empty deque");
|
||||||
throw new InvalidOperationException("Cannot remove items from empty deque");
|
}
|
||||||
}
|
|
||||||
|
// This is necessary to make sure the deque doesn't hold dead objects alive
|
||||||
// This is necessary to make sure the deque doesn't hold dead objects alive
|
// in unreachable spaces of its memory.
|
||||||
// in unreachable spaces of its memory.
|
int lastBlock = this.blocks.Count - 1;
|
||||||
int lastBlock = this.blocks.Count - 1;
|
this.blocks[lastBlock][this.lastBlockEndIndex - 1] = default(TItem);
|
||||||
this.blocks[lastBlock][this.lastBlockEndIndex - 1] = default(TItem);
|
|
||||||
|
// Cut off the last item in the last block. If the block became empty and it's
|
||||||
// Cut off the last item in the last block. If the block became empty and it's
|
// not the last remaining block, remove it as well.
|
||||||
// not the last remaining block, remove it as well.
|
--this.lastBlockEndIndex;
|
||||||
--this.lastBlockEndIndex;
|
if(this.lastBlockEndIndex == 0) { // Block became empty
|
||||||
if(this.lastBlockEndIndex == 0) { // Block became empty
|
if(this.count > 1) {
|
||||||
if(this.count > 1) {
|
this.blocks.RemoveAt(lastBlock);
|
||||||
this.blocks.RemoveAt(lastBlock);
|
this.lastBlockEndIndex = this.blockSize;
|
||||||
this.lastBlockEndIndex = this.blockSize;
|
} else { // Last block - do not remove
|
||||||
} else { // Last block - do not remove
|
this.firstBlockStartIndex = 0;
|
||||||
this.firstBlockStartIndex = 0;
|
this.lastBlockEndIndex = 0;
|
||||||
this.lastBlockEndIndex = 0;
|
}
|
||||||
}
|
}
|
||||||
}
|
--this.count;
|
||||||
--this.count;
|
#if DEBUG
|
||||||
#if DEBUG
|
++this.version;
|
||||||
++this.version;
|
#endif
|
||||||
#endif
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Removes the item at the specified index</summary>
|
||||||
/// <summary>Removes the item at the specified index</summary>
|
/// <param name="index">Index of the item that will be removed</param>
|
||||||
/// <param name="index">Index of the item that will be removed</param>
|
public void RemoveAt(int index) {
|
||||||
public void RemoveAt(int index) {
|
int distanceToRightEnd = this.count - index;
|
||||||
int distanceToRightEnd = this.count - index;
|
if(index < distanceToRightEnd) { // Are we closer to the left end?
|
||||||
if(index < distanceToRightEnd) { // Are we closer to the left end?
|
removeFromLeft(index);
|
||||||
removeFromLeft(index);
|
} else { // Nope, we're closer to the right end
|
||||||
} else { // Nope, we're closer to the right end
|
removeFromRight(index);
|
||||||
removeFromRight(index);
|
}
|
||||||
}
|
#if DEBUG
|
||||||
#if DEBUG
|
++this.version;
|
||||||
++this.version;
|
#endif
|
||||||
#endif
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Removes an item from the left side of the queue by shifting all items that
|
||||||
/// Removes an item from the left side of the queue by shifting all items that
|
/// come before it to the right by one
|
||||||
/// come before it to the right by one
|
/// </summary>
|
||||||
/// </summary>
|
/// <param name="index">Index of the item that will be removed</param>
|
||||||
/// <param name="index">Index of the item that will be removed</param>
|
private void removeFromLeft(int index) {
|
||||||
private void removeFromLeft(int index) {
|
if(index == 0) {
|
||||||
if(index == 0) {
|
RemoveFirst();
|
||||||
RemoveFirst();
|
} else {
|
||||||
} else {
|
int blockIndex, subIndex;
|
||||||
int blockIndex, subIndex;
|
findIndex(index, out blockIndex, out subIndex);
|
||||||
findIndex(index, out blockIndex, out subIndex);
|
|
||||||
|
int firstBlock = 0;
|
||||||
int firstBlock = 0;
|
int endIndex;
|
||||||
int endIndex;
|
|
||||||
|
if(blockIndex > firstBlock) {
|
||||||
if(blockIndex > firstBlock) {
|
Array.Copy(
|
||||||
Array.Copy(
|
this.blocks[blockIndex], 0,
|
||||||
this.blocks[blockIndex], 0,
|
this.blocks[blockIndex], 1,
|
||||||
this.blocks[blockIndex], 1,
|
subIndex
|
||||||
subIndex
|
);
|
||||||
);
|
this.blocks[blockIndex][0] = this.blocks[blockIndex - 1][this.blockSize - 1];
|
||||||
this.blocks[blockIndex][0] = this.blocks[blockIndex - 1][this.blockSize - 1];
|
|
||||||
|
for(int tempIndex = blockIndex - 1; tempIndex > firstBlock; --tempIndex) {
|
||||||
for(int tempIndex = blockIndex - 1; tempIndex > firstBlock; --tempIndex) {
|
Array.Copy(
|
||||||
Array.Copy(
|
this.blocks[tempIndex], 0,
|
||||||
this.blocks[tempIndex], 0,
|
this.blocks[tempIndex], 1,
|
||||||
this.blocks[tempIndex], 1,
|
this.blockSize - 1
|
||||||
this.blockSize - 1
|
);
|
||||||
);
|
this.blocks[tempIndex][0] = this.blocks[tempIndex - 1][this.blockSize - 1];
|
||||||
this.blocks[tempIndex][0] = this.blocks[tempIndex - 1][this.blockSize - 1];
|
}
|
||||||
}
|
|
||||||
|
endIndex = this.blockSize - 1;
|
||||||
endIndex = this.blockSize - 1;
|
} else {
|
||||||
} else {
|
endIndex = subIndex;
|
||||||
endIndex = subIndex;
|
}
|
||||||
}
|
|
||||||
|
Array.Copy(
|
||||||
Array.Copy(
|
this.blocks[firstBlock], this.firstBlockStartIndex,
|
||||||
this.blocks[firstBlock], this.firstBlockStartIndex,
|
this.blocks[firstBlock], this.firstBlockStartIndex + 1,
|
||||||
this.blocks[firstBlock], this.firstBlockStartIndex + 1,
|
endIndex - this.firstBlockStartIndex
|
||||||
endIndex - this.firstBlockStartIndex
|
);
|
||||||
);
|
|
||||||
|
if(this.firstBlockStartIndex == this.blockSize - 1) {
|
||||||
if(this.firstBlockStartIndex == this.blockSize - 1) {
|
this.blocks.RemoveAt(0);
|
||||||
this.blocks.RemoveAt(0);
|
this.firstBlockStartIndex = 0;
|
||||||
this.firstBlockStartIndex = 0;
|
} else {
|
||||||
} else {
|
this.blocks[0][this.firstBlockStartIndex] = default(TItem);
|
||||||
this.blocks[0][this.firstBlockStartIndex] = default(TItem);
|
++this.firstBlockStartIndex;
|
||||||
++this.firstBlockStartIndex;
|
}
|
||||||
}
|
|
||||||
|
--this.count;
|
||||||
--this.count;
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Removes an item from the right side of the queue by shifting all items that
|
||||||
/// Removes an item from the right side of the queue by shifting all items that
|
/// come after it to the left by one
|
||||||
/// come after it to the left by one
|
/// </summary>
|
||||||
/// </summary>
|
/// <param name="index">Index of the item that will be removed</param>
|
||||||
/// <param name="index">Index of the item that will be removed</param>
|
private void removeFromRight(int index) {
|
||||||
private void removeFromRight(int index) {
|
if(index == this.count - 1) {
|
||||||
if(index == this.count - 1) {
|
RemoveLast();
|
||||||
RemoveLast();
|
} else {
|
||||||
} else {
|
int blockIndex, subIndex;
|
||||||
int blockIndex, subIndex;
|
findIndex(index, out blockIndex, out subIndex);
|
||||||
findIndex(index, out blockIndex, out subIndex);
|
|
||||||
|
int lastBlock = this.blocks.Count - 1;
|
||||||
int lastBlock = this.blocks.Count - 1;
|
int startIndex;
|
||||||
int startIndex;
|
|
||||||
|
if(blockIndex < lastBlock) {
|
||||||
if(blockIndex < lastBlock) {
|
Array.Copy(
|
||||||
Array.Copy(
|
this.blocks[blockIndex], subIndex + 1,
|
||||||
this.blocks[blockIndex], subIndex + 1,
|
this.blocks[blockIndex], subIndex,
|
||||||
this.blocks[blockIndex], subIndex,
|
this.blockSize - subIndex - 1
|
||||||
this.blockSize - subIndex - 1
|
);
|
||||||
);
|
this.blocks[blockIndex][this.blockSize - 1] = this.blocks[blockIndex + 1][0];
|
||||||
this.blocks[blockIndex][this.blockSize - 1] = this.blocks[blockIndex + 1][0];
|
|
||||||
|
for(int tempIndex = blockIndex + 1; tempIndex < lastBlock; ++tempIndex) {
|
||||||
for(int tempIndex = blockIndex + 1; tempIndex < lastBlock; ++tempIndex) {
|
Array.Copy(
|
||||||
Array.Copy(
|
this.blocks[tempIndex], 1,
|
||||||
this.blocks[tempIndex], 1,
|
this.blocks[tempIndex], 0,
|
||||||
this.blocks[tempIndex], 0,
|
this.blockSize - 1
|
||||||
this.blockSize - 1
|
);
|
||||||
);
|
this.blocks[tempIndex][this.blockSize - 1] = this.blocks[tempIndex + 1][0];
|
||||||
this.blocks[tempIndex][this.blockSize - 1] = this.blocks[tempIndex + 1][0];
|
}
|
||||||
}
|
|
||||||
|
startIndex = 0;
|
||||||
startIndex = 0;
|
} else {
|
||||||
} else {
|
startIndex = subIndex;
|
||||||
startIndex = subIndex;
|
}
|
||||||
}
|
|
||||||
|
Array.Copy(
|
||||||
Array.Copy(
|
this.blocks[lastBlock], startIndex + 1,
|
||||||
this.blocks[lastBlock], startIndex + 1,
|
this.blocks[lastBlock], startIndex,
|
||||||
this.blocks[lastBlock], startIndex,
|
this.lastBlockEndIndex - startIndex - 1
|
||||||
this.lastBlockEndIndex - startIndex - 1
|
);
|
||||||
);
|
|
||||||
|
if(this.lastBlockEndIndex == 1) {
|
||||||
if(this.lastBlockEndIndex == 1) {
|
this.blocks.RemoveAt(lastBlock);
|
||||||
this.blocks.RemoveAt(lastBlock);
|
this.lastBlockEndIndex = this.blockSize;
|
||||||
this.lastBlockEndIndex = this.blockSize;
|
} else {
|
||||||
} else {
|
this.blocks[lastBlock][this.lastBlockEndIndex - 1] = default(TItem);
|
||||||
this.blocks[lastBlock][this.lastBlockEndIndex - 1] = default(TItem);
|
--this.lastBlockEndIndex;
|
||||||
--this.lastBlockEndIndex;
|
}
|
||||||
}
|
|
||||||
|
--this.count;
|
||||||
--this.count;
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
}
|
||||||
}
|
|
||||||
|
} // namespace Nuclex.Support.Collections
|
||||||
} // namespace Nuclex.Support.Collections
|
|
||||||
|
|
|
@ -1,84 +1,83 @@
|
||||||
#region CPL License
|
#region Apache License 2.0
|
||||||
/*
|
/*
|
||||||
Nuclex Framework
|
Nuclex .NET Framework
|
||||||
Copyright (C) 2002-2017 Nuclex Development Labs
|
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
|
||||||
|
|
||||||
This library is free software; you can redistribute it and/or
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
modify it under the terms of the IBM Common Public License as
|
you may not use this file except in compliance with the License.
|
||||||
published by the IBM Corporation; either version 1.0 of the
|
You may obtain a copy of the License at
|
||||||
License, or (at your option) any later version.
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
This library is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
Unless required by applicable law or agreed to in writing, software
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
IBM Common Public License for more details.
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
You should have received a copy of the IBM Common Public
|
limitations under the License.
|
||||||
License along with this library
|
*/
|
||||||
*/
|
#endregion // Apache License 2.0
|
||||||
#endregion
|
|
||||||
|
using System;
|
||||||
using System;
|
|
||||||
|
namespace Nuclex.Support.Collections {
|
||||||
namespace Nuclex.Support.Collections {
|
|
||||||
|
partial class Deque<TItem> {
|
||||||
partial class Deque<TItem> {
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Determines the index of the first occurence of the specified item in the deque
|
||||||
/// Determines the index of the first occurence of the specified item in the deque
|
/// </summary>
|
||||||
/// </summary>
|
/// <param name="item">Item that will be located in the deque</param>
|
||||||
/// <param name="item">Item that will be located in the deque</param>
|
/// <returns>The index of the item or -1 if it wasn't found</returns>
|
||||||
/// <returns>The index of the item or -1 if it wasn't found</returns>
|
public int IndexOf(TItem item) {
|
||||||
public int IndexOf(TItem item) {
|
if(this.blocks.Count == 1) { // Only one block to scan?
|
||||||
if(this.blocks.Count == 1) { // Only one block to scan?
|
int length = this.lastBlockEndIndex - this.firstBlockStartIndex;
|
||||||
int length = this.lastBlockEndIndex - this.firstBlockStartIndex;
|
int index = Array.IndexOf<TItem>(
|
||||||
int index = Array.IndexOf<TItem>(
|
this.blocks[0], item, this.firstBlockStartIndex, length
|
||||||
this.blocks[0], item, this.firstBlockStartIndex, length
|
);
|
||||||
);
|
|
||||||
|
// If we found something, we need to adjust its index so the first item in
|
||||||
// If we found something, we need to adjust its index so the first item in
|
// the deque always appears at index 0 to the user
|
||||||
// the deque always appears at index 0 to the user
|
if(index != -1) {
|
||||||
if(index != -1) {
|
return (index - this.firstBlockStartIndex);
|
||||||
return (index - this.firstBlockStartIndex);
|
} else {
|
||||||
} else {
|
return -1;
|
||||||
return -1;
|
}
|
||||||
}
|
} else { // At least two blocks exist
|
||||||
} else { // At least two blocks exist
|
|
||||||
|
// Scan the first block for the item and if found, return the index
|
||||||
// Scan the first block for the item and if found, return the index
|
int length = this.blockSize - this.firstBlockStartIndex;
|
||||||
int length = this.blockSize - this.firstBlockStartIndex;
|
int index = Array.IndexOf<TItem>(
|
||||||
int index = Array.IndexOf<TItem>(
|
this.blocks[0], item, this.firstBlockStartIndex, length
|
||||||
this.blocks[0], item, this.firstBlockStartIndex, length
|
);
|
||||||
);
|
|
||||||
|
// If we found something, we need to adjust its index
|
||||||
// If we found something, we need to adjust its index
|
if(index != -1) {
|
||||||
if(index != -1) {
|
return (index - this.firstBlockStartIndex);
|
||||||
return (index - this.firstBlockStartIndex);
|
}
|
||||||
}
|
|
||||||
|
int lastBlock = this.blocks.Count - 1;
|
||||||
int lastBlock = this.blocks.Count - 1;
|
for(int tempIndex = 1; tempIndex < lastBlock; ++tempIndex) {
|
||||||
for(int tempIndex = 1; tempIndex < lastBlock; ++tempIndex) {
|
index = Array.IndexOf<TItem>(
|
||||||
index = Array.IndexOf<TItem>(
|
this.blocks[tempIndex], item, 0, this.blockSize
|
||||||
this.blocks[tempIndex], item, 0, this.blockSize
|
);
|
||||||
);
|
if(index != -1) {
|
||||||
if(index != -1) {
|
return (index - this.firstBlockStartIndex + tempIndex * this.blockSize);
|
||||||
return (index - this.firstBlockStartIndex + tempIndex * this.blockSize);
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
// Nothing found, continue the search in the
|
||||||
// Nothing found, continue the search in the
|
index = Array.IndexOf<TItem>(
|
||||||
index = Array.IndexOf<TItem>(
|
this.blocks[lastBlock], item, 0, this.lastBlockEndIndex
|
||||||
this.blocks[lastBlock], item, 0, this.lastBlockEndIndex
|
);
|
||||||
);
|
if(index == -1) {
|
||||||
if(index == -1) {
|
return -1;
|
||||||
return -1;
|
} else {
|
||||||
} else {
|
return (index - this.firstBlockStartIndex + lastBlock * this.blockSize);
|
||||||
return (index - this.firstBlockStartIndex + lastBlock * this.blockSize);
|
}
|
||||||
}
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
}
|
||||||
}
|
|
||||||
|
} // namespace Nuclex.Support.Collections
|
||||||
} // namespace Nuclex.Support.Collections
|
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,334 +1,333 @@
|
||||||
#region CPL License
|
#region Apache License 2.0
|
||||||
/*
|
/*
|
||||||
Nuclex Framework
|
Nuclex .NET Framework
|
||||||
Copyright (C) 2002-2017 Nuclex Development Labs
|
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
|
||||||
|
|
||||||
This library is free software; you can redistribute it and/or
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
modify it under the terms of the IBM Common Public License as
|
you may not use this file except in compliance with the License.
|
||||||
published by the IBM Corporation; either version 1.0 of the
|
You may obtain a copy of the License at
|
||||||
License, or (at your option) any later version.
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
This library is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
Unless required by applicable law or agreed to in writing, software
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
IBM Common Public License for more details.
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
You should have received a copy of the IBM Common Public
|
limitations under the License.
|
||||||
License along with this library
|
*/
|
||||||
*/
|
#endregion // Apache License 2.0
|
||||||
#endregion
|
|
||||||
|
using System;
|
||||||
using System;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Generic;
|
using System.Collections;
|
||||||
using System.Collections;
|
|
||||||
|
namespace Nuclex.Support.Collections {
|
||||||
namespace Nuclex.Support.Collections {
|
|
||||||
|
/// <summary>A double-ended queue that allocates memory in blocks</summary>
|
||||||
/// <summary>A double-ended queue that allocates memory in blocks</summary>
|
/// <typeparam name="TItem">Type of the items being stored in the queue</typeparam>
|
||||||
/// <typeparam name="TItem">Type of the items being stored in the queue</typeparam>
|
/// <remarks>
|
||||||
/// <remarks>
|
/// <para>
|
||||||
/// <para>
|
/// The double-ended queue allows items to be appended to either side of the queue
|
||||||
/// The double-ended queue allows items to be appended to either side of the queue
|
/// without a hefty toll on performance. Like its namesake in C++, it is implemented
|
||||||
/// without a hefty toll on performance. Like its namesake in C++, it is implemented
|
/// using multiple arrays.
|
||||||
/// using multiple arrays.
|
/// </para>
|
||||||
/// </para>
|
/// <para>
|
||||||
/// <para>
|
/// Therefore, it's not only good at coping with lists that are modified at their
|
||||||
/// Therefore, it's not only good at coping with lists that are modified at their
|
/// beginning, but also at handling huge data sets since enlarging the deque doesn't
|
||||||
/// beginning, but also at handling huge data sets since enlarging the deque doesn't
|
/// require items to be copied around and it still can be accessed by index.
|
||||||
/// require items to be copied around and it still can be accessed by index.
|
/// </para>
|
||||||
/// </para>
|
/// </remarks>
|
||||||
/// </remarks>
|
public partial class Deque<TItem> : IList<TItem>, IList {
|
||||||
public partial class Deque<TItem> : IList<TItem>, IList {
|
|
||||||
|
#region class Enumerator
|
||||||
#region class Enumerator
|
|
||||||
|
/// <summary>Enumerates over the items in a deque</summary>
|
||||||
/// <summary>Enumerates over the items in a deque</summary>
|
private class Enumerator : IEnumerator<TItem>, IEnumerator {
|
||||||
private class Enumerator : IEnumerator<TItem>, IEnumerator {
|
|
||||||
|
/// <summary>Initializes a new deque enumerator</summary>
|
||||||
/// <summary>Initializes a new deque enumerator</summary>
|
/// <param name="deque">Deque whose items will be enumerated</param>
|
||||||
/// <param name="deque">Deque whose items will be enumerated</param>
|
public Enumerator(Deque<TItem> deque) {
|
||||||
public Enumerator(Deque<TItem> deque) {
|
this.deque = deque;
|
||||||
this.deque = deque;
|
this.blockSize = this.deque.blockSize;
|
||||||
this.blockSize = this.deque.blockSize;
|
this.lastBlock = this.deque.blocks.Count - 1;
|
||||||
this.lastBlock = this.deque.blocks.Count - 1;
|
this.lastBlockEndIndex = this.deque.lastBlockEndIndex - 1;
|
||||||
this.lastBlockEndIndex = this.deque.lastBlockEndIndex - 1;
|
|
||||||
|
Reset();
|
||||||
Reset();
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Immediately releases all resources owned by the instance</summary>
|
||||||
/// <summary>Immediately releases all resources owned by the instance</summary>
|
public void Dispose() {
|
||||||
public void Dispose() {
|
this.deque = null;
|
||||||
this.deque = null;
|
this.currentBlock = null;
|
||||||
this.currentBlock = null;
|
}
|
||||||
}
|
|
||||||
|
/// <summary>The item at the enumerator's current position</summary>
|
||||||
/// <summary>The item at the enumerator's current position</summary>
|
public TItem Current {
|
||||||
public TItem Current {
|
get {
|
||||||
get {
|
#if DEBUG
|
||||||
#if DEBUG
|
checkVersion();
|
||||||
checkVersion();
|
#endif
|
||||||
#endif
|
if (this.currentBlock == null) {
|
||||||
if (this.currentBlock == null) {
|
throw new InvalidOperationException("Enumerator is not on a valid position");
|
||||||
throw new InvalidOperationException("Enumerator is not on a valid position");
|
}
|
||||||
}
|
|
||||||
|
return this.currentBlock[this.subIndex];
|
||||||
return this.currentBlock[this.subIndex];
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Advances the enumerator to the next item</summary>
|
||||||
/// <summary>Advances the enumerator to the next item</summary>
|
/// <returns>True if there was a next item</returns>
|
||||||
/// <returns>True if there was a next item</returns>
|
public bool MoveNext() {
|
||||||
public bool MoveNext() {
|
|
||||||
|
#if DEBUG
|
||||||
#if DEBUG
|
checkVersion();
|
||||||
checkVersion();
|
#endif
|
||||||
#endif
|
|
||||||
|
// If we haven't reached the last block yet
|
||||||
// If we haven't reached the last block yet
|
if (this.currentBlockIndex < this.lastBlock) {
|
||||||
if (this.currentBlockIndex < this.lastBlock) {
|
|
||||||
|
// Advance to the next item. If the end of the current block is reached,
|
||||||
// Advance to the next item. If the end of the current block is reached,
|
// go to the next block's first item
|
||||||
// go to the next block's first item
|
++this.subIndex;
|
||||||
++this.subIndex;
|
if (this.subIndex >= this.blockSize) {
|
||||||
if (this.subIndex >= this.blockSize) {
|
++this.currentBlockIndex;
|
||||||
++this.currentBlockIndex;
|
this.currentBlock = this.deque.blocks[this.currentBlockIndex];
|
||||||
this.currentBlock = this.deque.blocks[this.currentBlockIndex];
|
if (this.currentBlockIndex == 0) {
|
||||||
if (this.currentBlockIndex == 0) {
|
this.subIndex = this.deque.firstBlockStartIndex;
|
||||||
this.subIndex = this.deque.firstBlockStartIndex;
|
} else {
|
||||||
} else {
|
this.subIndex = 0;
|
||||||
this.subIndex = 0;
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
// Item found. If the current block wasn't the last block, an item *has*
|
||||||
// Item found. If the current block wasn't the last block, an item *has*
|
// to follow since otherwise, no further blocks would exist!
|
||||||
// to follow since otherwise, no further blocks would exist!
|
return true;
|
||||||
return true;
|
|
||||||
|
} else { // We in or beyond the last block
|
||||||
} else { // We in or beyond the last block
|
|
||||||
|
// Are there any items left to advance to?
|
||||||
// Are there any items left to advance to?
|
if (this.subIndex < this.lastBlockEndIndex) {
|
||||||
if (this.subIndex < this.lastBlockEndIndex) {
|
++this.subIndex;
|
||||||
++this.subIndex;
|
return true;
|
||||||
return true;
|
} else { // Nope, we've reached the end of the deque
|
||||||
} else { // Nope, we've reached the end of the deque
|
this.currentBlock = null;
|
||||||
this.currentBlock = null;
|
return false;
|
||||||
return false;
|
}
|
||||||
}
|
|
||||||
|
}
|
||||||
}
|
|
||||||
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Resets the enumerator to its initial position</summary>
|
||||||
/// <summary>Resets the enumerator to its initial position</summary>
|
public void Reset() {
|
||||||
public void Reset() {
|
this.currentBlock = null;
|
||||||
this.currentBlock = null;
|
this.currentBlockIndex = -1;
|
||||||
this.currentBlockIndex = -1;
|
this.subIndex = this.deque.blockSize - 1;
|
||||||
this.subIndex = this.deque.blockSize - 1;
|
#if DEBUG
|
||||||
#if DEBUG
|
this.expectedVersion = this.deque.version;
|
||||||
this.expectedVersion = this.deque.version;
|
#endif
|
||||||
#endif
|
}
|
||||||
}
|
|
||||||
|
/// <summary>The item at the enumerator's current position</summary>
|
||||||
/// <summary>The item at the enumerator's current position</summary>
|
object IEnumerator.Current {
|
||||||
object IEnumerator.Current {
|
get { return Current; }
|
||||||
get { return Current; }
|
}
|
||||||
}
|
|
||||||
|
#if DEBUG
|
||||||
#if DEBUG
|
/// <summary>Ensures that the deque has not changed</summary>
|
||||||
/// <summary>Ensures that the deque has not changed</summary>
|
private void checkVersion() {
|
||||||
private void checkVersion() {
|
if(this.expectedVersion != this.deque.version)
|
||||||
if(this.expectedVersion != this.deque.version)
|
throw new InvalidOperationException("Deque has been modified");
|
||||||
throw new InvalidOperationException("Deque has been modified");
|
}
|
||||||
}
|
#endif
|
||||||
#endif
|
|
||||||
|
/// <summary>Deque the enumerator belongs to</summary>
|
||||||
/// <summary>Deque the enumerator belongs to</summary>
|
private Deque<TItem> deque;
|
||||||
private Deque<TItem> deque;
|
/// <summary>Size of the blocks in the deque</summary>
|
||||||
/// <summary>Size of the blocks in the deque</summary>
|
private int blockSize;
|
||||||
private int blockSize;
|
/// <summary>Index of the last block in the deque</summary>
|
||||||
/// <summary>Index of the last block in the deque</summary>
|
private int lastBlock;
|
||||||
private int lastBlock;
|
/// <summary>End index of the items in the deque's last block</summary>
|
||||||
/// <summary>End index of the items in the deque's last block</summary>
|
private int lastBlockEndIndex;
|
||||||
private int lastBlockEndIndex;
|
|
||||||
|
/// <summary>Index of the block the enumerator currently is in</summary>
|
||||||
/// <summary>Index of the block the enumerator currently is in</summary>
|
private int currentBlockIndex;
|
||||||
private int currentBlockIndex;
|
/// <summary>Reference to the block being enumerated</summary>
|
||||||
/// <summary>Reference to the block being enumerated</summary>
|
private TItem[] currentBlock;
|
||||||
private TItem[] currentBlock;
|
/// <summary>Index in the current block</summary>
|
||||||
/// <summary>Index in the current block</summary>
|
private int subIndex;
|
||||||
private int subIndex;
|
#if DEBUG
|
||||||
#if DEBUG
|
/// <summary>Version the deque is expected to have</summary>
|
||||||
/// <summary>Version the deque is expected to have</summary>
|
private int expectedVersion;
|
||||||
private int expectedVersion;
|
#endif
|
||||||
#endif
|
}
|
||||||
}
|
|
||||||
|
#endregion // class Enumerator
|
||||||
#endregion // class Enumerator
|
|
||||||
|
/// <summary>Initializes a new deque</summary>
|
||||||
/// <summary>Initializes a new deque</summary>
|
public Deque() : this(512) { }
|
||||||
public Deque() : this(512) { }
|
|
||||||
|
/// <summary>Initializes a new deque using the specified block size</summary>
|
||||||
/// <summary>Initializes a new deque using the specified block size</summary>
|
/// <param name="blockSize">Size of the individual memory blocks used</param>
|
||||||
/// <param name="blockSize">Size of the individual memory blocks used</param>
|
public Deque(int blockSize) {
|
||||||
public Deque(int blockSize) {
|
this.blockSize = blockSize;
|
||||||
this.blockSize = blockSize;
|
|
||||||
|
this.blocks = new List<TItem[]>();
|
||||||
this.blocks = new List<TItem[]>();
|
this.blocks.Add(new TItem[this.blockSize]);
|
||||||
this.blocks.Add(new TItem[this.blockSize]);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Number of items contained in the double ended queue</summary>
|
||||||
/// <summary>Number of items contained in the double ended queue</summary>
|
public int Count {
|
||||||
public int Count {
|
get { return this.count; }
|
||||||
get { return this.count; }
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Accesses an item by its index</summary>
|
||||||
/// <summary>Accesses an item by its index</summary>
|
/// <param name="index">Index of the item that will be accessed</param>
|
||||||
/// <param name="index">Index of the item that will be accessed</param>
|
/// <returns>The item at the specified index</returns>
|
||||||
/// <returns>The item at the specified index</returns>
|
public TItem this[int index] {
|
||||||
public TItem this[int index] {
|
get {
|
||||||
get {
|
int blockIndex, subIndex;
|
||||||
int blockIndex, subIndex;
|
findIndex(index, out blockIndex, out subIndex);
|
||||||
findIndex(index, out blockIndex, out subIndex);
|
|
||||||
|
return this.blocks[blockIndex][subIndex];
|
||||||
return this.blocks[blockIndex][subIndex];
|
}
|
||||||
}
|
set {
|
||||||
set {
|
int blockIndex, subIndex;
|
||||||
int blockIndex, subIndex;
|
findIndex(index, out blockIndex, out subIndex);
|
||||||
findIndex(index, out blockIndex, out subIndex);
|
|
||||||
|
this.blocks[blockIndex][subIndex] = value;
|
||||||
this.blocks[blockIndex][subIndex] = value;
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
/// <summary>The first item in the double-ended queue</summary>
|
||||||
/// <summary>The first item in the double-ended queue</summary>
|
public TItem First {
|
||||||
public TItem First {
|
get {
|
||||||
get {
|
if (this.count == 0) {
|
||||||
if (this.count == 0) {
|
throw new InvalidOperationException("The deque is empty");
|
||||||
throw new InvalidOperationException("The deque is empty");
|
}
|
||||||
}
|
return this.blocks[0][this.firstBlockStartIndex];
|
||||||
return this.blocks[0][this.firstBlockStartIndex];
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
/// <summary>The last item in the double-ended queue</summary>
|
||||||
/// <summary>The last item in the double-ended queue</summary>
|
public TItem Last {
|
||||||
public TItem Last {
|
get {
|
||||||
get {
|
if (this.count == 0) {
|
||||||
if (this.count == 0) {
|
throw new InvalidOperationException("The deque is empty");
|
||||||
throw new InvalidOperationException("The deque is empty");
|
}
|
||||||
}
|
return this.blocks[this.blocks.Count - 1][this.lastBlockEndIndex - 1];
|
||||||
return this.blocks[this.blocks.Count - 1][this.lastBlockEndIndex - 1];
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Determines whether the deque contains the specified item</summary>
|
||||||
/// <summary>Determines whether the deque contains the specified item</summary>
|
/// <param name="item">Item the deque will be scanned for</param>
|
||||||
/// <param name="item">Item the deque will be scanned for</param>
|
/// <returns>True if the deque contains the item, false otherwise</returns>
|
||||||
/// <returns>True if the deque contains the item, false otherwise</returns>
|
public bool Contains(TItem item) {
|
||||||
public bool Contains(TItem item) {
|
return (IndexOf(item) != -1);
|
||||||
return (IndexOf(item) != -1);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Copies the contents of the deque into an array</summary>
|
||||||
/// <summary>Copies the contents of the deque into an array</summary>
|
/// <param name="array">Array the contents of the deque will be copied into</param>
|
||||||
/// <param name="array">Array the contents of the deque will be copied into</param>
|
/// <param name="arrayIndex">Array index the deque contents will begin at</param>
|
||||||
/// <param name="arrayIndex">Array index the deque contents will begin at</param>
|
public void CopyTo(TItem[] array, int arrayIndex) {
|
||||||
public void CopyTo(TItem[] array, int arrayIndex) {
|
if (this.count > (array.Length - arrayIndex)) {
|
||||||
if (this.count > (array.Length - arrayIndex)) {
|
throw new ArgumentException(
|
||||||
throw new ArgumentException(
|
"Array too small to hold the collection items starting at the specified index"
|
||||||
"Array too small to hold the collection items starting at the specified index"
|
);
|
||||||
);
|
}
|
||||||
}
|
|
||||||
|
if (this.blocks.Count == 1) { // Does only one block exist?
|
||||||
if (this.blocks.Count == 1) { // Does only one block exist?
|
|
||||||
|
// Copy the one and only block there is
|
||||||
// Copy the one and only block there is
|
Array.Copy(
|
||||||
Array.Copy(
|
this.blocks[0], this.firstBlockStartIndex,
|
||||||
this.blocks[0], this.firstBlockStartIndex,
|
array, arrayIndex,
|
||||||
array, arrayIndex,
|
this.lastBlockEndIndex - this.firstBlockStartIndex
|
||||||
this.lastBlockEndIndex - this.firstBlockStartIndex
|
);
|
||||||
);
|
|
||||||
|
} else { // Multiple blocks exist
|
||||||
} else { // Multiple blocks exist
|
|
||||||
|
// Copy the first block which is filled from the start index to its end
|
||||||
// Copy the first block which is filled from the start index to its end
|
int length = this.blockSize - this.firstBlockStartIndex;
|
||||||
int length = this.blockSize - this.firstBlockStartIndex;
|
Array.Copy(
|
||||||
Array.Copy(
|
this.blocks[0], this.firstBlockStartIndex,
|
||||||
this.blocks[0], this.firstBlockStartIndex,
|
array, arrayIndex,
|
||||||
array, arrayIndex,
|
length
|
||||||
length
|
);
|
||||||
);
|
arrayIndex += length;
|
||||||
arrayIndex += length;
|
|
||||||
|
// Copy all intermediate blocks (if there are any). These are completely filled
|
||||||
// Copy all intermediate blocks (if there are any). These are completely filled
|
int lastBlock = this.blocks.Count - 1;
|
||||||
int lastBlock = this.blocks.Count - 1;
|
for (int index = 1; index < lastBlock; ++index) {
|
||||||
for (int index = 1; index < lastBlock; ++index) {
|
Array.Copy(
|
||||||
Array.Copy(
|
this.blocks[index], 0,
|
||||||
this.blocks[index], 0,
|
array, arrayIndex,
|
||||||
array, arrayIndex,
|
this.blockSize
|
||||||
this.blockSize
|
);
|
||||||
);
|
arrayIndex += this.blockSize;
|
||||||
arrayIndex += this.blockSize;
|
}
|
||||||
}
|
|
||||||
|
// Copy the final block which is filled from the beginning to the end index
|
||||||
// Copy the final block which is filled from the beginning to the end index
|
Array.Copy(
|
||||||
Array.Copy(
|
this.blocks[lastBlock], 0,
|
||||||
this.blocks[lastBlock], 0,
|
array, arrayIndex,
|
||||||
array, arrayIndex,
|
this.lastBlockEndIndex
|
||||||
this.lastBlockEndIndex
|
);
|
||||||
);
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Obtains a new enumerator for the contents of the deque</summary>
|
||||||
/// <summary>Obtains a new enumerator for the contents of the deque</summary>
|
/// <returns>The new enumerator</returns>
|
||||||
/// <returns>The new enumerator</returns>
|
public IEnumerator<TItem> GetEnumerator() {
|
||||||
public IEnumerator<TItem> GetEnumerator() {
|
return new Enumerator(this);
|
||||||
return new Enumerator(this);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Calculates the block index and local sub index of an entry</summary>
|
||||||
/// <summary>Calculates the block index and local sub index of an entry</summary>
|
/// <param name="index">Index of the entry that will be located</param>
|
||||||
/// <param name="index">Index of the entry that will be located</param>
|
/// <param name="blockIndex">Index of the block the entry is contained in</param>
|
||||||
/// <param name="blockIndex">Index of the block the entry is contained in</param>
|
/// <param name="subIndex">Local sub index of the entry within the block</param>
|
||||||
/// <param name="subIndex">Local sub index of the entry within the block</param>
|
private void findIndex(int index, out int blockIndex, out int subIndex) {
|
||||||
private void findIndex(int index, out int blockIndex, out int subIndex) {
|
if ((index < 0) || (index >= this.count)) {
|
||||||
if ((index < 0) || (index >= this.count)) {
|
throw new ArgumentOutOfRangeException("Index out of range", "index");
|
||||||
throw new ArgumentOutOfRangeException("Index out of range", "index");
|
}
|
||||||
}
|
|
||||||
|
index += this.firstBlockStartIndex;
|
||||||
index += this.firstBlockStartIndex;
|
blockIndex = Math.DivRem(index, this.blockSize, out subIndex);
|
||||||
blockIndex = Math.DivRem(index, this.blockSize, out subIndex);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Determines whether the provided object can be placed in the deque
|
||||||
/// Determines whether the provided object can be placed in the deque
|
/// </summary>
|
||||||
/// </summary>
|
/// <param name="value">Value that will be checked for compatibility</param>
|
||||||
/// <param name="value">Value that will be checked for compatibility</param>
|
/// <returns>True if the value can be placed in the deque</returns>
|
||||||
/// <returns>True if the value can be placed in the deque</returns>
|
private static bool isCompatibleObject(object value) {
|
||||||
private static bool isCompatibleObject(object value) {
|
return ((value is TItem) || ((value == null) && !typeof(TItem).IsValueType));
|
||||||
return ((value is TItem) || ((value == null) && !typeof(TItem).IsValueType));
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Verifies that the provided object matches the deque's type</summary>
|
||||||
/// <summary>Verifies that the provided object matches the deque's type</summary>
|
/// <param name="value">Value that will be checked for compatibility</param>
|
||||||
/// <param name="value">Value that will be checked for compatibility</param>
|
private static void verifyCompatibleObject(object value) {
|
||||||
private static void verifyCompatibleObject(object value) {
|
if (!isCompatibleObject(value)) {
|
||||||
if (!isCompatibleObject(value)) {
|
throw new ArgumentException("Value does not match the deque's type", "value");
|
||||||
throw new ArgumentException("Value does not match the deque's type", "value");
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Number if items currently stored in the deque</summary>
|
||||||
/// <summary>Number if items currently stored in the deque</summary>
|
private int count;
|
||||||
private int count;
|
/// <summary>Size of a single deque block</summary>
|
||||||
/// <summary>Size of a single deque block</summary>
|
private int blockSize;
|
||||||
private int blockSize;
|
/// <summary>Memory blocks being used to store the deque's data</summary>
|
||||||
/// <summary>Memory blocks being used to store the deque's data</summary>
|
private List<TItem[]> blocks;
|
||||||
private List<TItem[]> blocks;
|
/// <summary>Starting index of data in the first block</summary>
|
||||||
/// <summary>Starting index of data in the first block</summary>
|
private int firstBlockStartIndex;
|
||||||
private int firstBlockStartIndex;
|
/// <summary>End index of data in the last block</summary>
|
||||||
/// <summary>End index of data in the last block</summary>
|
private int lastBlockEndIndex;
|
||||||
private int lastBlockEndIndex;
|
#if DEBUG
|
||||||
#if DEBUG
|
/// <summary>Used to detect when enumerators go out of sync</summary>
|
||||||
/// <summary>Used to detect when enumerators go out of sync</summary>
|
private int version;
|
||||||
private int version;
|
#endif
|
||||||
#endif
|
|
||||||
|
}
|
||||||
}
|
|
||||||
|
} // namespace Nuclex.Support.Collections
|
||||||
} // namespace Nuclex.Support.Collections
|
|
||||||
|
|
|
@ -1,137 +1,136 @@
|
||||||
#region CPL License
|
#region Apache License 2.0
|
||||||
/*
|
/*
|
||||||
Nuclex Framework
|
Nuclex .NET Framework
|
||||||
Copyright (C) 2002-2017 Nuclex Development Labs
|
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
|
||||||
|
|
||||||
This library is free software; you can redistribute it and/or
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
modify it under the terms of the IBM Common Public License as
|
you may not use this file except in compliance with the License.
|
||||||
published by the IBM Corporation; either version 1.0 of the
|
You may obtain a copy of the License at
|
||||||
License, or (at your option) any later version.
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
This library is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
Unless required by applicable law or agreed to in writing, software
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
IBM Common Public License for more details.
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
You should have received a copy of the IBM Common Public
|
limitations under the License.
|
||||||
License along with this library
|
*/
|
||||||
*/
|
#endregion // Apache License 2.0
|
||||||
#endregion
|
|
||||||
|
using System;
|
||||||
using System;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Generic;
|
using System.IO;
|
||||||
using System.IO;
|
|
||||||
|
#if UNITTEST
|
||||||
#if UNITTEST
|
|
||||||
|
using NUnit.Framework;
|
||||||
using NUnit.Framework;
|
|
||||||
|
namespace Nuclex.Support.Collections {
|
||||||
namespace Nuclex.Support.Collections {
|
|
||||||
|
/// <summary>Unit Test for the IList extension methods</summary>
|
||||||
/// <summary>Unit Test for the IList extension methods</summary>
|
[TestFixture]
|
||||||
[TestFixture]
|
internal class IListExtensionsTest {
|
||||||
internal class IListExtensionsTest {
|
|
||||||
|
/// <summary>Tests whether the insertion sort algorithm sorts a list correctly</summary>
|
||||||
/// <summary>Tests whether the insertion sort algorithm sorts a list correctly</summary>
|
[Test]
|
||||||
[Test]
|
public void InsertionSortCanSortWholeList() {
|
||||||
public void InsertionSortCanSortWholeList() {
|
var testList = new List<int>(capacity: 5) { 1, 5, 2, 4, 3 };
|
||||||
var testList = new List<int>(capacity: 5) { 1, 5, 2, 4, 3 };
|
var testListAsIList = (IList<int>)testList;
|
||||||
var testListAsIList = (IList<int>)testList;
|
|
||||||
|
testListAsIList.InsertionSort();
|
||||||
testListAsIList.InsertionSort();
|
|
||||||
|
CollectionAssert.AreEqual(
|
||||||
CollectionAssert.AreEqual(
|
new List<int>(capacity: 5) { 1, 2, 3, 4, 5 },
|
||||||
new List<int>(capacity: 5) { 1, 2, 3, 4, 5 },
|
testList
|
||||||
testList
|
);
|
||||||
);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Tests whether the insertion sort algorithm works on big lists</summary>
|
||||||
/// <summary>Tests whether the insertion sort algorithm works on big lists</summary>
|
[Test]
|
||||||
[Test]
|
public void InsertionSortCanSortBigList() {
|
||||||
public void InsertionSortCanSortBigList() {
|
const int ListSize = 16384;
|
||||||
const int ListSize = 16384;
|
|
||||||
|
var testList = new List<int>(capacity: ListSize);
|
||||||
var testList = new List<int>(capacity: ListSize);
|
{
|
||||||
{
|
var random = new Random();
|
||||||
var random = new Random();
|
for(int index = 0; index < ListSize; ++index) {
|
||||||
for(int index = 0; index < ListSize; ++index) {
|
testList.Add(random.Next());
|
||||||
testList.Add(random.Next());
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
var testListAsIList = (IList<int>)testList;
|
||||||
var testListAsIList = (IList<int>)testList;
|
testListAsIList.InsertionSort();
|
||||||
testListAsIList.InsertionSort();
|
|
||||||
|
for(int index = 1; index < ListSize; ++index) {
|
||||||
for(int index = 1; index < ListSize; ++index) {
|
Assert.LessOrEqual(testListAsIList[index - 1], testListAsIList[index]);
|
||||||
Assert.LessOrEqual(testListAsIList[index - 1], testListAsIList[index]);
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Tests whether the insertion sort algorithm respects custom boundaries</summary>
|
||||||
/// <summary>Tests whether the insertion sort algorithm respects custom boundaries</summary>
|
[Test]
|
||||||
[Test]
|
public void InsertionSortCanSortListSegment() {
|
||||||
public void InsertionSortCanSortListSegment() {
|
var testList = new List<int>(capacity: 7) { 9, 1, 5, 2, 4, 3, 0 };
|
||||||
var testList = new List<int>(capacity: 7) { 9, 1, 5, 2, 4, 3, 0 };
|
var testListAsIList = (IList<int>)testList;
|
||||||
var testListAsIList = (IList<int>)testList;
|
|
||||||
|
testListAsIList.InsertionSort(1, 5, Comparer<int>.Default);
|
||||||
testListAsIList.InsertionSort(1, 5, Comparer<int>.Default);
|
|
||||||
|
CollectionAssert.AreEqual(
|
||||||
CollectionAssert.AreEqual(
|
new List<int>(capacity: 7) { 9, 1, 2, 3, 4, 5, 0 },
|
||||||
new List<int>(capacity: 7) { 9, 1, 2, 3, 4, 5, 0 },
|
testList
|
||||||
testList
|
);
|
||||||
);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Tests whether the quicksort algorithm sorts a list correctly</summary>
|
||||||
/// <summary>Tests whether the quicksort algorithm sorts a list correctly</summary>
|
[Test]
|
||||||
[Test]
|
public void QuickSortCanSortWholeList() {
|
||||||
public void QuickSortCanSortWholeList() {
|
var testList = new List<int>(capacity: 5) { 1, 5, 2, 4, 3 };
|
||||||
var testList = new List<int>(capacity: 5) { 1, 5, 2, 4, 3 };
|
var testListAsIList = (IList<int>)testList;
|
||||||
var testListAsIList = (IList<int>)testList;
|
|
||||||
|
testListAsIList.QuickSort();
|
||||||
testListAsIList.QuickSort();
|
|
||||||
|
CollectionAssert.AreEqual(
|
||||||
CollectionAssert.AreEqual(
|
new List<int>(capacity: 5) { 1, 2, 3, 4, 5 },
|
||||||
new List<int>(capacity: 5) { 1, 2, 3, 4, 5 },
|
testList
|
||||||
testList
|
);
|
||||||
);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Tests whether the quicksort algorithm works on big lists</summary>
|
||||||
/// <summary>Tests whether the quicksort algorithm works on big lists</summary>
|
[Test]
|
||||||
[Test]
|
public void QuickSortCanSortBigList() {
|
||||||
public void QuickSortCanSortBigList() {
|
const int ListSize = 16384;
|
||||||
const int ListSize = 16384;
|
|
||||||
|
var testList = new List<int>(capacity: ListSize);
|
||||||
var testList = new List<int>(capacity: ListSize);
|
{
|
||||||
{
|
var random = new Random();
|
||||||
var random = new Random();
|
for(int index = 0; index < ListSize; ++index) {
|
||||||
for(int index = 0; index < ListSize; ++index) {
|
testList.Add(random.Next());
|
||||||
testList.Add(random.Next());
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
var testListAsIList = (IList<int>)testList;
|
||||||
var testListAsIList = (IList<int>)testList;
|
testListAsIList.QuickSort();
|
||||||
testListAsIList.QuickSort();
|
|
||||||
|
for(int index = 1; index < ListSize; ++index) {
|
||||||
for(int index = 1; index < ListSize; ++index) {
|
Assert.LessOrEqual(testListAsIList[index - 1], testListAsIList[index]);
|
||||||
Assert.LessOrEqual(testListAsIList[index - 1], testListAsIList[index]);
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Tests whether the quicksort algorithm respects custom boundaries</summary>
|
||||||
/// <summary>Tests whether the quicksort algorithm respects custom boundaries</summary>
|
[Test]
|
||||||
[Test]
|
public void QuickSortCanSortListSegment() {
|
||||||
public void QuickSortCanSortListSegment() {
|
var testList = new List<int>(capacity: 7) { 9, 1, 5, 2, 4, 3, 0 };
|
||||||
var testList = new List<int>(capacity: 7) { 9, 1, 5, 2, 4, 3, 0 };
|
var testListAsIList = (IList<int>)testList;
|
||||||
var testListAsIList = (IList<int>)testList;
|
|
||||||
|
testListAsIList.QuickSort(1, 5, Comparer<int>.Default);
|
||||||
testListAsIList.QuickSort(1, 5, Comparer<int>.Default);
|
|
||||||
|
CollectionAssert.AreEqual(
|
||||||
CollectionAssert.AreEqual(
|
new List<int>(capacity: 7) { 9, 1, 2, 3, 4, 5, 0 },
|
||||||
new List<int>(capacity: 7) { 9, 1, 2, 3, 4, 5, 0 },
|
testList
|
||||||
testList
|
);
|
||||||
);
|
}
|
||||||
}
|
|
||||||
|
}
|
||||||
}
|
|
||||||
|
} // namespace Nuclex.Support.Collections
|
||||||
} // namespace Nuclex.Support.Collections
|
|
||||||
|
#endif // UNITTEST
|
||||||
#endif // UNITTEST
|
|
||||||
|
|
|
@ -1,219 +1,218 @@
|
||||||
#region CPL License
|
#region Apache License 2.0
|
||||||
/*
|
/*
|
||||||
Nuclex Framework
|
Nuclex .NET Framework
|
||||||
Copyright (C) 2002-2017 Nuclex Development Labs
|
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
|
||||||
|
|
||||||
This library is free software; you can redistribute it and/or
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
modify it under the terms of the IBM Common Public License as
|
you may not use this file except in compliance with the License.
|
||||||
published by the IBM Corporation; either version 1.0 of the
|
You may obtain a copy of the License at
|
||||||
License, or (at your option) any later version.
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
This library is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
Unless required by applicable law or agreed to in writing, software
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
IBM Common Public License for more details.
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
You should have received a copy of the IBM Common Public
|
limitations under the License.
|
||||||
License along with this library
|
*/
|
||||||
*/
|
#endregion // Apache License 2.0
|
||||||
#endregion
|
|
||||||
|
using System;
|
||||||
using System;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Generic;
|
|
||||||
|
namespace Nuclex.Support.Collections {
|
||||||
namespace Nuclex.Support.Collections {
|
|
||||||
|
/// <summary>Extension methods for the IList interface</summary>
|
||||||
/// <summary>Extension methods for the IList interface</summary>
|
public static class ListExtensions {
|
||||||
public static class ListExtensions {
|
|
||||||
|
#region struct Partition
|
||||||
#region struct Partition
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Stores the left and right index of a partition for the quicksort algorithm
|
||||||
/// Stores the left and right index of a partition for the quicksort algorithm
|
/// </summary>
|
||||||
/// </summary>
|
private struct Partition {
|
||||||
private struct Partition {
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Initializes a new partition using the specified left and right index
|
||||||
/// Initializes a new partition using the specified left and right index
|
/// </summary>
|
||||||
/// </summary>
|
/// <param name="leftmostIndex">Index of the leftmost element in the partition</param>
|
||||||
/// <param name="leftmostIndex">Index of the leftmost element in the partition</param>
|
/// <param name="rightmostIndex">Index of the rightmost element in the partition</param>
|
||||||
/// <param name="rightmostIndex">Index of the rightmost element in the partition</param>
|
public Partition(int leftmostIndex, int rightmostIndex) {
|
||||||
public Partition(int leftmostIndex, int rightmostIndex) {
|
this.LeftmostIndex = leftmostIndex;
|
||||||
this.LeftmostIndex = leftmostIndex;
|
this.RightmostIndex = rightmostIndex;
|
||||||
this.RightmostIndex = rightmostIndex;
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Index of the leftmost element in the partition</summary>
|
||||||
/// <summary>Index of the leftmost element in the partition</summary>
|
public int LeftmostIndex;
|
||||||
public int LeftmostIndex;
|
/// <summary>Index of the rightmost element in the partition</summary>
|
||||||
/// <summary>Index of the rightmost element in the partition</summary>
|
public int RightmostIndex;
|
||||||
public int RightmostIndex;
|
|
||||||
|
}
|
||||||
}
|
|
||||||
|
#endregion // struct Partition
|
||||||
#endregion // struct Partition
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Sorts a subset of the elements in an IList<T> using the insertion sort algorithm
|
||||||
/// Sorts a subset of the elements in an IList<T> using the insertion sort algorithm
|
/// </summary>
|
||||||
/// </summary>
|
/// <typeparam name="TElement">Type of elements the list contains</typeparam>
|
||||||
/// <typeparam name="TElement">Type of elements the list contains</typeparam>
|
/// <param name="list">List in which a subset will be sorted</param>
|
||||||
/// <param name="list">List in which a subset will be sorted</param>
|
/// <param name="startIndex">Index at which the sorting process will begin</param>
|
||||||
/// <param name="startIndex">Index at which the sorting process will begin</param>
|
/// <param name="count">Index one past the last element that will be sorted</param>
|
||||||
/// <param name="count">Index one past the last element that will be sorted</param>
|
/// <param name="comparer">Comparison function to use for comparing list elements</param>
|
||||||
/// <param name="comparer">Comparison function to use for comparing list elements</param>
|
public static void InsertionSort<TElement>(
|
||||||
public static void InsertionSort<TElement>(
|
this IList<TElement> list, int startIndex, int count, IComparer<TElement> comparer
|
||||||
this IList<TElement> list, int startIndex, int count, IComparer<TElement> comparer
|
) {
|
||||||
) {
|
int rightIndex = startIndex;
|
||||||
int rightIndex = startIndex;
|
|
||||||
|
int lastIndex = startIndex + count - 1;
|
||||||
int lastIndex = startIndex + count - 1;
|
for(int index = startIndex + 1; index <= lastIndex; ++index) {
|
||||||
for(int index = startIndex + 1; index <= lastIndex; ++index) {
|
TElement temp = list[index];
|
||||||
TElement temp = list[index];
|
|
||||||
|
while(rightIndex >= startIndex) {
|
||||||
while(rightIndex >= startIndex) {
|
if(comparer.Compare(list[rightIndex], temp) < 0) {
|
||||||
if(comparer.Compare(list[rightIndex], temp) < 0) {
|
break;
|
||||||
break;
|
}
|
||||||
}
|
|
||||||
|
list[rightIndex + 1] = list[rightIndex];
|
||||||
list[rightIndex + 1] = list[rightIndex];
|
--rightIndex;
|
||||||
--rightIndex;
|
}
|
||||||
}
|
|
||||||
|
list[rightIndex + 1] = temp;
|
||||||
list[rightIndex + 1] = temp;
|
|
||||||
|
rightIndex = index;
|
||||||
rightIndex = index;
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Sorts all the elements in an IList<T> using the insertion sort algorithm
|
||||||
/// Sorts all the elements in an IList<T> using the insertion sort algorithm
|
/// </summary>
|
||||||
/// </summary>
|
/// <typeparam name="TElement">Type of elements the list contains</typeparam>
|
||||||
/// <typeparam name="TElement">Type of elements the list contains</typeparam>
|
/// <param name="list">List in which a subset will be sorted</param>
|
||||||
/// <param name="list">List in which a subset will be sorted</param>
|
/// <param name="comparer">Comparison function to use for comparing list elements</param>
|
||||||
/// <param name="comparer">Comparison function to use for comparing list elements</param>
|
public static void InsertionSort<TElement>(
|
||||||
public static void InsertionSort<TElement>(
|
this IList<TElement> list, IComparer<TElement> comparer
|
||||||
this IList<TElement> list, IComparer<TElement> comparer
|
) {
|
||||||
) {
|
InsertionSort(list, 0, list.Count, comparer);
|
||||||
InsertionSort(list, 0, list.Count, comparer);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Sorts all the elements in an IList<T> using the insertion sort algorithm
|
||||||
/// Sorts all the elements in an IList<T> using the insertion sort algorithm
|
/// </summary>
|
||||||
/// </summary>
|
/// <typeparam name="TElement">Type of elements the list contains</typeparam>
|
||||||
/// <typeparam name="TElement">Type of elements the list contains</typeparam>
|
/// <param name="list">List in which a subset will be sorted</param>
|
||||||
/// <param name="list">List in which a subset will be sorted</param>
|
public static void InsertionSort<TElement>(this IList<TElement> list) {
|
||||||
public static void InsertionSort<TElement>(this IList<TElement> list) {
|
InsertionSort(list, 0, list.Count, Comparer<TElement>.Default);
|
||||||
InsertionSort(list, 0, list.Count, Comparer<TElement>.Default);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Sorts all the elements in an IList<T> using the quicksort algorithm
|
||||||
/// Sorts all the elements in an IList<T> using the quicksort algorithm
|
/// </summary>
|
||||||
/// </summary>
|
/// <typeparam name="TElement">Type of elements the list contains</typeparam>
|
||||||
/// <typeparam name="TElement">Type of elements the list contains</typeparam>
|
/// <param name="list">List in which a subset will be sorted</param>
|
||||||
/// <param name="list">List in which a subset will be sorted</param>
|
/// <param name="startIndex">Index at which the sorting process will begin</param>
|
||||||
/// <param name="startIndex">Index at which the sorting process will begin</param>
|
/// <param name="count">Index one past the last element that will be sorted</param>
|
||||||
/// <param name="count">Index one past the last element that will be sorted</param>
|
/// <param name="comparer">Comparison function to use for comparing list elements</param>
|
||||||
/// <param name="comparer">Comparison function to use for comparing list elements</param>
|
public static void QuickSort<TElement>(
|
||||||
public static void QuickSort<TElement>(
|
this IList<TElement> list, int startIndex, int count, IComparer<TElement> comparer
|
||||||
this IList<TElement> list, int startIndex, int count, IComparer<TElement> comparer
|
) {
|
||||||
) {
|
var remainingPartitions = new Stack<Partition>();
|
||||||
var remainingPartitions = new Stack<Partition>();
|
|
||||||
|
int lastIndex = startIndex + count - 1;
|
||||||
int lastIndex = startIndex + count - 1;
|
for(; ; ) {
|
||||||
for(; ; ) {
|
int pivotIndex = quicksortPartition(list, startIndex, lastIndex, comparer);
|
||||||
int pivotIndex = quicksortPartition(list, startIndex, lastIndex, comparer);
|
|
||||||
|
// This block just queues the next partitions left of the pivot point and right
|
||||||
// This block just queues the next partitions left of the pivot point and right
|
// of the pivot point (if they contain at least 2 elements). It's fattened up
|
||||||
// of the pivot point (if they contain at least 2 elements). It's fattened up
|
// a bit by trying to forego the stack and adjusting the startIndex/lastIndex
|
||||||
// a bit by trying to forego the stack and adjusting the startIndex/lastIndex
|
// directly where it's clear the next loop can process these partitions.
|
||||||
// directly where it's clear the next loop can process these partitions.
|
if(pivotIndex - 1 > startIndex) { // Are the elements to sort right of the pivot?
|
||||||
if(pivotIndex - 1 > startIndex) { // Are the elements to sort right of the pivot?
|
if(pivotIndex + 1 < lastIndex) { // Are the elements left of the pivot as well?
|
||||||
if(pivotIndex + 1 < lastIndex) { // Are the elements left of the pivot as well?
|
remainingPartitions.Push(new Partition(startIndex, pivotIndex - 1));
|
||||||
remainingPartitions.Push(new Partition(startIndex, pivotIndex - 1));
|
startIndex = pivotIndex + 1;
|
||||||
startIndex = pivotIndex + 1;
|
} else { // Elements to sort are only right of the pivot
|
||||||
} else { // Elements to sort are only right of the pivot
|
lastIndex = pivotIndex - 1;
|
||||||
lastIndex = pivotIndex - 1;
|
}
|
||||||
}
|
} else if(pivotIndex + 1 < lastIndex) { // Are elements to sort only left of the pivot?
|
||||||
} else if(pivotIndex + 1 < lastIndex) { // Are elements to sort only left of the pivot?
|
startIndex = pivotIndex + 1;
|
||||||
startIndex = pivotIndex + 1;
|
} else { // Partition was fully sorted
|
||||||
} else { // Partition was fully sorted
|
|
||||||
|
// Did we process all queued partitions? If so, the list is sorted
|
||||||
// Did we process all queued partitions? If so, the list is sorted
|
if(remainingPartitions.Count == 0) {
|
||||||
if(remainingPartitions.Count == 0) {
|
return;
|
||||||
return;
|
}
|
||||||
}
|
|
||||||
|
// Pull the next partition that needs to be sorted from the stack
|
||||||
// Pull the next partition that needs to be sorted from the stack
|
Partition current = remainingPartitions.Pop();
|
||||||
Partition current = remainingPartitions.Pop();
|
startIndex = current.LeftmostIndex;
|
||||||
startIndex = current.LeftmostIndex;
|
lastIndex = current.RightmostIndex;
|
||||||
lastIndex = current.RightmostIndex;
|
|
||||||
|
} // if sortable sub-partitions exist left/right/nowhere
|
||||||
} // if sortable sub-partitions exist left/right/nowhere
|
} // for ever (termination inside loop)
|
||||||
} // for ever (termination inside loop)
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Sorts all the elements in an IList<T> using the insertion sort algorithm
|
||||||
/// Sorts all the elements in an IList<T> using the insertion sort algorithm
|
/// </summary>
|
||||||
/// </summary>
|
/// <typeparam name="TElement">Type of elements the list contains</typeparam>
|
||||||
/// <typeparam name="TElement">Type of elements the list contains</typeparam>
|
/// <param name="list">List in which a subset will be sorted</param>
|
||||||
/// <param name="list">List in which a subset will be sorted</param>
|
/// <param name="comparer">Comparison function to use for comparing list elements</param>
|
||||||
/// <param name="comparer">Comparison function to use for comparing list elements</param>
|
public static void QuickSort<TElement>(
|
||||||
public static void QuickSort<TElement>(
|
this IList<TElement> list, IComparer<TElement> comparer
|
||||||
this IList<TElement> list, IComparer<TElement> comparer
|
) {
|
||||||
) {
|
QuickSort(list, 0, list.Count, comparer);
|
||||||
QuickSort(list, 0, list.Count, comparer);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Sorts all the elements in an IList<T> using the insertion sort algorithm
|
||||||
/// Sorts all the elements in an IList<T> using the insertion sort algorithm
|
/// </summary>
|
||||||
/// </summary>
|
/// <typeparam name="TElement">Type of elements the list contains</typeparam>
|
||||||
/// <typeparam name="TElement">Type of elements the list contains</typeparam>
|
/// <param name="list">List in which a subset will be sorted</param>
|
||||||
/// <param name="list">List in which a subset will be sorted</param>
|
public static void QuickSort<TElement>(this IList<TElement> list) {
|
||||||
public static void QuickSort<TElement>(this IList<TElement> list) {
|
QuickSort(list, 0, list.Count, Comparer<TElement>.Default);
|
||||||
QuickSort(list, 0, list.Count, Comparer<TElement>.Default);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Moves an element downward over all elements that precede it in the sort order
|
||||||
/// Moves an element downward over all elements that precede it in the sort order
|
/// </summary>
|
||||||
/// </summary>
|
/// <typeparam name="TElement">Type of elements stored in the sorted list</typeparam>
|
||||||
/// <typeparam name="TElement">Type of elements stored in the sorted list</typeparam>
|
/// <param name="list">List that is being sorted</param>
|
||||||
/// <param name="list">List that is being sorted</param>
|
/// <param name="firstIndex">Index of the first element in the partition</param>
|
||||||
/// <param name="firstIndex">Index of the first element in the partition</param>
|
/// <param name="lastIndex">Index of hte last element in the partition</param>
|
||||||
/// <param name="lastIndex">Index of hte last element in the partition</param>
|
/// <param name="comparer">
|
||||||
/// <param name="comparer">
|
/// Comparison function that decides the ordering of elements
|
||||||
/// Comparison function that decides the ordering of elements
|
/// </param>
|
||||||
/// </param>
|
/// <returns>The index of the next pivot element</returns>
|
||||||
/// <returns>The index of the next pivot element</returns>
|
private static int quicksortPartition<TElement>(
|
||||||
private static int quicksortPartition<TElement>(
|
IList<TElement> list, int firstIndex, int lastIndex, IComparer<TElement> comparer
|
||||||
IList<TElement> list, int firstIndex, int lastIndex, IComparer<TElement> comparer
|
) {
|
||||||
) {
|
|
||||||
|
// Step through all elements in the partition and accumulate those that are smaller
|
||||||
// Step through all elements in the partition and accumulate those that are smaller
|
// than the last element on the left (by swapping). At the end 'firstIndex' will be
|
||||||
// than the last element on the left (by swapping). At the end 'firstIndex' will be
|
// the new pivot point, left of which are all elements smaller than the element at
|
||||||
// the new pivot point, left of which are all elements smaller than the element at
|
// 'lastIndex' and right of it will be all elements which are larger.
|
||||||
// 'lastIndex' and right of it will be all elements which are larger.
|
for(int index = firstIndex; index < lastIndex; ++index) {
|
||||||
for(int index = firstIndex; index < lastIndex; ++index) {
|
if(comparer.Compare(list[index], list[lastIndex]) < 0) {
|
||||||
if(comparer.Compare(list[index], list[lastIndex]) < 0) {
|
TElement temp = list[firstIndex];
|
||||||
TElement temp = list[firstIndex];
|
list[firstIndex] = list[index];
|
||||||
list[firstIndex] = list[index];
|
list[index] = temp;
|
||||||
list[index] = temp;
|
|
||||||
|
++firstIndex;
|
||||||
++firstIndex;
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
// The element at 'lastIndex' as a sort value that's in the middle of the two sides,
|
||||||
// The element at 'lastIndex' as a sort value that's in the middle of the two sides,
|
// so we'll have to swap it, too, putting it in the middle and making it the new pivot.
|
||||||
// so we'll have to swap it, too, putting it in the middle and making it the new pivot.
|
{
|
||||||
{
|
TElement temp = list[firstIndex];
|
||||||
TElement temp = list[firstIndex];
|
list[firstIndex] = list[lastIndex];
|
||||||
list[firstIndex] = list[lastIndex];
|
list[lastIndex] = temp;
|
||||||
list[lastIndex] = temp;
|
}
|
||||||
}
|
|
||||||
|
// Return the index of the new pivot position
|
||||||
// Return the index of the new pivot position
|
return firstIndex;
|
||||||
return firstIndex;
|
|
||||||
|
}
|
||||||
}
|
|
||||||
|
}
|
||||||
}
|
|
||||||
|
} // namespace Nuclex.Support.Collections
|
||||||
} // namespace Nuclex.Support.Collections
|
|
||||||
|
|
|
@ -1,68 +1,67 @@
|
||||||
#region CPL License
|
#region Apache License 2.0
|
||||||
/*
|
/*
|
||||||
Nuclex Framework
|
Nuclex .NET Framework
|
||||||
Copyright (C) 2002-2017 Nuclex Development Labs
|
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
|
||||||
|
|
||||||
This library is free software; you can redistribute it and/or
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
modify it under the terms of the IBM Common Public License as
|
you may not use this file except in compliance with the License.
|
||||||
published by the IBM Corporation; either version 1.0 of the
|
You may obtain a copy of the License at
|
||||||
License, or (at your option) any later version.
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
This library is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
Unless required by applicable law or agreed to in writing, software
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
IBM Common Public License for more details.
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
You should have received a copy of the IBM Common Public
|
limitations under the License.
|
||||||
License along with this library
|
*/
|
||||||
*/
|
#endregion // Apache License 2.0
|
||||||
#endregion
|
|
||||||
|
using System;
|
||||||
using System;
|
using System.Collections;
|
||||||
using System.Collections;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Generic;
|
|
||||||
|
namespace Nuclex.Support.Collections {
|
||||||
namespace Nuclex.Support.Collections {
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Associative collection that can store several values under one key and vice versa
|
||||||
/// Associative collection that can store several values under one key and vice versa
|
/// </summary>
|
||||||
/// </summary>
|
/// <typeparam name="TKey">Type of keys used within the dictionary</typeparam>
|
||||||
/// <typeparam name="TKey">Type of keys used within the dictionary</typeparam>
|
/// <typeparam name="TValue">Type of values stored in the dictionary</typeparam>
|
||||||
/// <typeparam name="TValue">Type of values stored in the dictionary</typeparam>
|
public interface IMultiDictionary<TKey, TValue> :
|
||||||
public interface IMultiDictionary<TKey, TValue> :
|
IDictionary<TKey, ICollection<TValue>>,
|
||||||
IDictionary<TKey, ICollection<TValue>>,
|
IDictionary,
|
||||||
IDictionary,
|
ICollection<KeyValuePair<TKey, TValue>>,
|
||||||
ICollection<KeyValuePair<TKey, TValue>>,
|
IEnumerable<KeyValuePair<TKey, TValue>>,
|
||||||
IEnumerable<KeyValuePair<TKey, TValue>>,
|
IEnumerable {
|
||||||
IEnumerable {
|
|
||||||
|
/// <summary>Adds a value into the dictionary under the provided key</summary>
|
||||||
/// <summary>Adds a value into the dictionary under the provided key</summary>
|
/// <param name="key">Key the value will be stored under</param>
|
||||||
/// <param name="key">Key the value will be stored under</param>
|
/// <param name="value">Value that will be stored under the specified key</param>
|
||||||
/// <param name="value">Value that will be stored under the specified key</param>
|
void Add(TKey key, TValue value);
|
||||||
void Add(TKey key, TValue value);
|
|
||||||
|
/// <summary>Determines the number of values stored under the specified key</summary>
|
||||||
/// <summary>Determines the number of values stored under the specified key</summary>
|
/// <param name="key">Key whose values will be counted</param>
|
||||||
/// <param name="key">Key whose values will be counted</param>
|
/// <returns>The number of values stored under the specified key</returns>
|
||||||
/// <returns>The number of values stored under the specified key</returns>
|
int CountValues(TKey key);
|
||||||
int CountValues(TKey key);
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Removes the item with the specified key and value from the dictionary
|
||||||
/// Removes the item with the specified key and value from the dictionary
|
/// </summary>
|
||||||
/// </summary>
|
/// <param name="key">Key of the item that will be removed</param>
|
||||||
/// <param name="key">Key of the item that will be removed</param>
|
/// <param name="value">Value of the item that will be removed</param>
|
||||||
/// <param name="value">Value of the item that will be removed</param>
|
/// <returns>
|
||||||
/// <returns>
|
/// True if the specified item was contained in the dictionary and was removed
|
||||||
/// True if the specified item was contained in the dictionary and was removed
|
/// </returns>
|
||||||
/// </returns>
|
/// <exception cref="NotSupportedException">If the dictionary is read-only</exception>
|
||||||
/// <exception cref="NotSupportedException">If the dictionary is read-only</exception>
|
bool Remove(TKey key, TValue value);
|
||||||
bool Remove(TKey key, TValue value);
|
|
||||||
|
/// <summary>Removes all items with the specified key from the dictionary</summary>
|
||||||
/// <summary>Removes all items with the specified key from the dictionary</summary>
|
/// <param name="key">Key of the item that will be removed</param>
|
||||||
/// <param name="key">Key of the item that will be removed</param>
|
/// <returns>The number of items that have been removed from the dictionary</returns>
|
||||||
/// <returns>The number of items that have been removed from the dictionary</returns>
|
/// <exception cref="NotSupportedException">If the dictionary is read-only</exception>
|
||||||
/// <exception cref="NotSupportedException">If the dictionary is read-only</exception>
|
int RemoveKey(TKey key);
|
||||||
int RemoveKey(TKey key);
|
|
||||||
|
}
|
||||||
}
|
|
||||||
|
} // namespace Nuclex.Support.Collections
|
||||||
} // namespace Nuclex.Support.Collections
|
|
||||||
|
|
|
@ -1,51 +1,50 @@
|
||||||
#region CPL License
|
#region Apache License 2.0
|
||||||
/*
|
/*
|
||||||
Nuclex Framework
|
Nuclex .NET Framework
|
||||||
Copyright (C) 2002-2017 Nuclex Development Labs
|
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
|
||||||
|
|
||||||
This library is free software; you can redistribute it and/or
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
modify it under the terms of the IBM Common Public License as
|
you may not use this file except in compliance with the License.
|
||||||
published by the IBM Corporation; either version 1.0 of the
|
You may obtain a copy of the License at
|
||||||
License, or (at your option) any later version.
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
This library is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
Unless required by applicable law or agreed to in writing, software
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
IBM Common Public License for more details.
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
You should have received a copy of the IBM Common Public
|
limitations under the License.
|
||||||
License along with this library
|
*/
|
||||||
*/
|
#endregion // Apache License 2.0
|
||||||
#endregion
|
|
||||||
|
using System;
|
||||||
using System;
|
|
||||||
|
namespace Nuclex.Support.Collections {
|
||||||
namespace Nuclex.Support.Collections {
|
|
||||||
|
/// <summary>Interface for collections that can be observed</summary>
|
||||||
/// <summary>Interface for collections that can be observed</summary>
|
/// <typeparam name="TItem">Type of items managed in the collection</typeparam>
|
||||||
/// <typeparam name="TItem">Type of items managed in the collection</typeparam>
|
public interface IObservableCollection<TItem> {
|
||||||
public interface IObservableCollection<TItem> {
|
|
||||||
|
/// <summary>Raised when an item has been added to the collection</summary>
|
||||||
/// <summary>Raised when an item has been added to the collection</summary>
|
event EventHandler<ItemEventArgs<TItem>> ItemAdded;
|
||||||
event EventHandler<ItemEventArgs<TItem>> ItemAdded;
|
|
||||||
|
/// <summary>Raised when an item is removed from the collection</summary>
|
||||||
/// <summary>Raised when an item is removed from the collection</summary>
|
event EventHandler<ItemEventArgs<TItem>> ItemRemoved;
|
||||||
event EventHandler<ItemEventArgs<TItem>> ItemRemoved;
|
|
||||||
|
/// <summary>Raised when an item is replaced in the collection</summary>
|
||||||
/// <summary>Raised when an item is replaced in the collection</summary>
|
event EventHandler<ItemReplaceEventArgs<TItem>> ItemReplaced;
|
||||||
event EventHandler<ItemReplaceEventArgs<TItem>> ItemReplaced;
|
|
||||||
|
/// <summary>Raised when the collection is about to be cleared</summary>
|
||||||
/// <summary>Raised when the collection is about to be cleared</summary>
|
/// <remarks>
|
||||||
/// <remarks>
|
/// This could be covered by calling ItemRemoved for each item currently
|
||||||
/// This could be covered by calling ItemRemoved for each item currently
|
/// contained in the collection, but it is often simpler and more efficient
|
||||||
/// contained in the collection, but it is often simpler and more efficient
|
/// to process the clearing of the entire collection as a special operation.
|
||||||
/// to process the clearing of the entire collection as a special operation.
|
/// </remarks>
|
||||||
/// </remarks>
|
event EventHandler Clearing;
|
||||||
event EventHandler Clearing;
|
|
||||||
|
/// <summary>Raised when the collection has been cleared of its items</summary>
|
||||||
/// <summary>Raised when the collection has been cleared of its items</summary>
|
event EventHandler Cleared;
|
||||||
event EventHandler Cleared;
|
|
||||||
|
}
|
||||||
}
|
|
||||||
|
} // namespace Nuclex.Support.Collections
|
||||||
} // namespace Nuclex.Support.Collections
|
|
||||||
|
|
|
@ -1,45 +1,44 @@
|
||||||
#region CPL License
|
#region Apache License 2.0
|
||||||
/*
|
/*
|
||||||
Nuclex Framework
|
Nuclex .NET Framework
|
||||||
Copyright (C) 2002-2017 Nuclex Development Labs
|
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
|
||||||
|
|
||||||
This library is free software; you can redistribute it and/or
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
modify it under the terms of the IBM Common Public License as
|
you may not use this file except in compliance with the License.
|
||||||
published by the IBM Corporation; either version 1.0 of the
|
You may obtain a copy of the License at
|
||||||
License, or (at your option) any later version.
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
This library is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
Unless required by applicable law or agreed to in writing, software
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
IBM Common Public License for more details.
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
You should have received a copy of the IBM Common Public
|
limitations under the License.
|
||||||
License along with this library
|
*/
|
||||||
*/
|
#endregion // Apache License 2.0
|
||||||
#endregion
|
|
||||||
|
using System;
|
||||||
using System;
|
|
||||||
|
namespace Nuclex.Support.Collections {
|
||||||
namespace Nuclex.Support.Collections {
|
|
||||||
|
/// <summary>Allows an object to be returned to its initial state</summary>
|
||||||
/// <summary>Allows an object to be returned to its initial state</summary>
|
/// <remarks>
|
||||||
/// <remarks>
|
/// <para>
|
||||||
/// <para>
|
/// This interface is typically implemented by objects which can be recycled
|
||||||
/// This interface is typically implemented by objects which can be recycled
|
/// in order to avoid the construction overhead of a heavyweight class and to
|
||||||
/// in order to avoid the construction overhead of a heavyweight class and to
|
/// eliminate garbage by reusing instances.
|
||||||
/// eliminate garbage by reusing instances.
|
/// </para>
|
||||||
/// </para>
|
/// <para>
|
||||||
/// <para>
|
/// Recyclable objects should have a parameterless constructor and calling
|
||||||
/// Recyclable objects should have a parameterless constructor and calling
|
/// their Recycle() method should bring them back into the state they were
|
||||||
/// their Recycle() method should bring them back into the state they were
|
/// in right after they had been constructed.
|
||||||
/// in right after they had been constructed.
|
/// </para>
|
||||||
/// </para>
|
/// </remarks>
|
||||||
/// </remarks>
|
public interface IRecyclable {
|
||||||
public interface IRecyclable {
|
|
||||||
|
/// <summary>Returns the object to its initial state</summary>
|
||||||
/// <summary>Returns the object to its initial state</summary>
|
void Recycle();
|
||||||
void Recycle();
|
|
||||||
|
}
|
||||||
}
|
|
||||||
|
} // namespace Nuclex.Support.Collections
|
||||||
} // namespace Nuclex.Support.Collections
|
|
||||||
|
|
|
@ -1,56 +1,55 @@
|
||||||
#region CPL License
|
#region Apache License 2.0
|
||||||
/*
|
/*
|
||||||
Nuclex Framework
|
Nuclex .NET Framework
|
||||||
Copyright (C) 2002-2017 Nuclex Development Labs
|
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
|
||||||
|
|
||||||
This library is free software; you can redistribute it and/or
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
modify it under the terms of the IBM Common Public License as
|
you may not use this file except in compliance with the License.
|
||||||
published by the IBM Corporation; either version 1.0 of the
|
You may obtain a copy of the License at
|
||||||
License, or (at your option) any later version.
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
This library is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
Unless required by applicable law or agreed to in writing, software
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
IBM Common Public License for more details.
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
You should have received a copy of the IBM Common Public
|
limitations under the License.
|
||||||
License along with this library
|
*/
|
||||||
*/
|
#endregion // Apache License 2.0
|
||||||
#endregion
|
|
||||||
|
#if UNITTEST
|
||||||
#if UNITTEST
|
|
||||||
|
using System;
|
||||||
using System;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Generic;
|
|
||||||
|
using NUnit.Framework;
|
||||||
using NUnit.Framework;
|
|
||||||
|
namespace Nuclex.Support.Collections {
|
||||||
namespace Nuclex.Support.Collections {
|
|
||||||
|
/// <summary>Unit Test for the item event argument container</summary>
|
||||||
/// <summary>Unit Test for the item event argument container</summary>
|
[TestFixture]
|
||||||
[TestFixture]
|
internal class ItemEventArgsTest {
|
||||||
internal class ItemEventArgsTest {
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Tests whether an integer argument can be stored in the argument container
|
||||||
/// Tests whether an integer argument can be stored in the argument container
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void IntegersCanBeCarried() {
|
||||||
public void IntegersCanBeCarried() {
|
var test = new ItemEventArgs<int>(12345);
|
||||||
var test = new ItemEventArgs<int>(12345);
|
Assert.AreEqual(12345, test.Item);
|
||||||
Assert.AreEqual(12345, test.Item);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Tests whether a string argument can be stored in the argument container
|
||||||
/// Tests whether a string argument can be stored in the argument container
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void StringsCanBeCarried() {
|
||||||
public void StringsCanBeCarried() {
|
var test = new ItemEventArgs<string>("hello world");
|
||||||
var test = new ItemEventArgs<string>("hello world");
|
Assert.AreEqual("hello world", test.Item);
|
||||||
Assert.AreEqual("hello world", test.Item);
|
}
|
||||||
}
|
|
||||||
|
}
|
||||||
}
|
|
||||||
|
} // namespace Nuclex.Support.Collections
|
||||||
} // namespace Nuclex.Support.Collections
|
|
||||||
|
#endif // UNITTEST
|
||||||
#endif // UNITTEST
|
|
||||||
|
|
|
@ -1,46 +1,45 @@
|
||||||
#region CPL License
|
#region Apache License 2.0
|
||||||
/*
|
/*
|
||||||
Nuclex Framework
|
Nuclex .NET Framework
|
||||||
Copyright (C) 2002-2017 Nuclex Development Labs
|
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
|
||||||
|
|
||||||
This library is free software; you can redistribute it and/or
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
modify it under the terms of the IBM Common Public License as
|
you may not use this file except in compliance with the License.
|
||||||
published by the IBM Corporation; either version 1.0 of the
|
You may obtain a copy of the License at
|
||||||
License, or (at your option) any later version.
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
This library is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
Unless required by applicable law or agreed to in writing, software
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
IBM Common Public License for more details.
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
You should have received a copy of the IBM Common Public
|
limitations under the License.
|
||||||
License along with this library
|
*/
|
||||||
*/
|
#endregion // Apache License 2.0
|
||||||
#endregion
|
|
||||||
|
using System;
|
||||||
using System;
|
|
||||||
|
namespace Nuclex.Support.Collections {
|
||||||
namespace Nuclex.Support.Collections {
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Argument container used by collections to notify about changed items
|
||||||
/// Argument container used by collections to notify about changed items
|
/// </summary>
|
||||||
/// </summary>
|
public class ItemEventArgs<TItem> : EventArgs {
|
||||||
public class ItemEventArgs<TItem> : EventArgs {
|
|
||||||
|
/// <summary>Initializes a new event arguments supplier</summary>
|
||||||
/// <summary>Initializes a new event arguments supplier</summary>
|
/// <param name="item">Item to be supplied to the event handler</param>
|
||||||
/// <param name="item">Item to be supplied to the event handler</param>
|
public ItemEventArgs(TItem item) {
|
||||||
public ItemEventArgs(TItem item) {
|
this.item = item;
|
||||||
this.item = item;
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Obtains the collection item the event arguments are carrying</summary>
|
||||||
/// <summary>Obtains the collection item the event arguments are carrying</summary>
|
public TItem Item {
|
||||||
public TItem Item {
|
get { return this.item; }
|
||||||
get { return this.item; }
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Item to be passed to the event handler</summary>
|
||||||
/// <summary>Item to be passed to the event handler</summary>
|
private TItem item;
|
||||||
private TItem item;
|
|
||||||
|
}
|
||||||
}
|
|
||||||
|
} // namespace Nuclex.Support.Collections
|
||||||
} // namespace Nuclex.Support.Collections
|
|
||||||
|
|
|
@ -1,58 +1,57 @@
|
||||||
#region CPL License
|
#region Apache License 2.0
|
||||||
/*
|
/*
|
||||||
Nuclex Framework
|
Nuclex .NET Framework
|
||||||
Copyright (C) 2002-2017 Nuclex Development Labs
|
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
|
||||||
|
|
||||||
This library is free software; you can redistribute it and/or
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
modify it under the terms of the IBM Common Public License as
|
you may not use this file except in compliance with the License.
|
||||||
published by the IBM Corporation; either version 1.0 of the
|
You may obtain a copy of the License at
|
||||||
License, or (at your option) any later version.
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
This library is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
Unless required by applicable law or agreed to in writing, software
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
IBM Common Public License for more details.
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
You should have received a copy of the IBM Common Public
|
limitations under the License.
|
||||||
License along with this library
|
*/
|
||||||
*/
|
#endregion // Apache License 2.0
|
||||||
#endregion
|
|
||||||
|
#if UNITTEST
|
||||||
#if UNITTEST
|
|
||||||
|
using System;
|
||||||
using System;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Generic;
|
|
||||||
|
using NUnit.Framework;
|
||||||
using NUnit.Framework;
|
|
||||||
|
namespace Nuclex.Support.Collections {
|
||||||
namespace Nuclex.Support.Collections {
|
|
||||||
|
/// <summary>Unit Test for the item event argument container</summary>
|
||||||
/// <summary>Unit Test for the item event argument container</summary>
|
[TestFixture]
|
||||||
[TestFixture]
|
internal class ItemReplaceEventArgsTest {
|
||||||
internal class ItemReplaceEventArgsTest {
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Tests whether an integer argument can be stored in the argument container
|
||||||
/// Tests whether an integer argument can be stored in the argument container
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void IntegersCanBeCarried() {
|
||||||
public void IntegersCanBeCarried() {
|
var test = new ItemReplaceEventArgs<int>(12345, 54321);
|
||||||
var test = new ItemReplaceEventArgs<int>(12345, 54321);
|
Assert.AreEqual(12345, test.OldItem);
|
||||||
Assert.AreEqual(12345, test.OldItem);
|
Assert.AreEqual(54321, test.NewItem);
|
||||||
Assert.AreEqual(54321, test.NewItem);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Tests whether a string argument can be stored in the argument container
|
||||||
/// Tests whether a string argument can be stored in the argument container
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void StringsCanBeCarried() {
|
||||||
public void StringsCanBeCarried() {
|
var test = new ItemReplaceEventArgs<string>("hello", "world");
|
||||||
var test = new ItemReplaceEventArgs<string>("hello", "world");
|
Assert.AreEqual("hello", test.OldItem);
|
||||||
Assert.AreEqual("hello", test.OldItem);
|
Assert.AreEqual("world", test.NewItem);
|
||||||
Assert.AreEqual("world", test.NewItem);
|
}
|
||||||
}
|
|
||||||
|
}
|
||||||
}
|
|
||||||
|
} // namespace Nuclex.Support.Collections
|
||||||
} // namespace Nuclex.Support.Collections
|
|
||||||
|
#endif // UNITTEST
|
||||||
#endif // UNITTEST
|
|
||||||
|
|
|
@ -1,55 +1,54 @@
|
||||||
#region CPL License
|
#region Apache License 2.0
|
||||||
/*
|
/*
|
||||||
Nuclex Framework
|
Nuclex .NET Framework
|
||||||
Copyright (C) 2002-2017 Nuclex Development Labs
|
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
|
||||||
|
|
||||||
This library is free software; you can redistribute it and/or
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
modify it under the terms of the IBM Common Public License as
|
you may not use this file except in compliance with the License.
|
||||||
published by the IBM Corporation; either version 1.0 of the
|
You may obtain a copy of the License at
|
||||||
License, or (at your option) any later version.
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
This library is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
Unless required by applicable law or agreed to in writing, software
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
IBM Common Public License for more details.
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
You should have received a copy of the IBM Common Public
|
limitations under the License.
|
||||||
License along with this library
|
*/
|
||||||
*/
|
#endregion // Apache License 2.0
|
||||||
#endregion
|
|
||||||
|
using System;
|
||||||
using System;
|
|
||||||
|
namespace Nuclex.Support.Collections {
|
||||||
namespace Nuclex.Support.Collections {
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Argument container used by collections to notify about replaced items
|
||||||
/// Argument container used by collections to notify about replaced items
|
/// </summary>
|
||||||
/// </summary>
|
public class ItemReplaceEventArgs<TItem> : EventArgs {
|
||||||
public class ItemReplaceEventArgs<TItem> : EventArgs {
|
|
||||||
|
/// <summary>Initializes a new event arguments supplier</summary>
|
||||||
/// <summary>Initializes a new event arguments supplier</summary>
|
/// <param name="oldItem">Item that has been replaced by another item</param>
|
||||||
/// <param name="oldItem">Item that has been replaced by another item</param>
|
/// <param name="newItem">Replacement item that is now part of the collection</param>
|
||||||
/// <param name="newItem">Replacement item that is now part of the collection</param>
|
public ItemReplaceEventArgs(TItem oldItem, TItem newItem) {
|
||||||
public ItemReplaceEventArgs(TItem oldItem, TItem newItem) {
|
this.oldItem = oldItem;
|
||||||
this.oldItem = oldItem;
|
this.newItem = newItem;
|
||||||
this.newItem = newItem;
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Item that has been replaced by another item</summary>
|
||||||
/// <summary>Item that has been replaced by another item</summary>
|
public TItem OldItem {
|
||||||
public TItem OldItem {
|
get { return this.oldItem; }
|
||||||
get { return this.oldItem; }
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Replacement item that is now part of the collection</summary>
|
||||||
/// <summary>Replacement item that is now part of the collection</summary>
|
public TItem NewItem {
|
||||||
public TItem NewItem {
|
get { return this.newItem; }
|
||||||
get { return this.newItem; }
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Item that was removed from the collection</summary>
|
||||||
/// <summary>Item that was removed from the collection</summary>
|
private TItem oldItem;
|
||||||
private TItem oldItem;
|
/// <summary>Item that was added to the collection</summary>
|
||||||
/// <summary>Item that was added to the collection</summary>
|
private TItem newItem;
|
||||||
private TItem newItem;
|
|
||||||
|
}
|
||||||
}
|
|
||||||
|
} // namespace Nuclex.Support.Collections
|
||||||
} // namespace Nuclex.Support.Collections
|
|
||||||
|
|
|
@ -1,257 +1,256 @@
|
||||||
#region CPL License
|
#region Apache License 2.0
|
||||||
/*
|
/*
|
||||||
Nuclex Framework
|
Nuclex .NET Framework
|
||||||
Copyright (C) 2002-2017 Nuclex Development Labs
|
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
|
||||||
|
|
||||||
This library is free software; you can redistribute it and/or
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
modify it under the terms of the IBM Common Public License as
|
you may not use this file except in compliance with the License.
|
||||||
published by the IBM Corporation; either version 1.0 of the
|
You may obtain a copy of the License at
|
||||||
License, or (at your option) any later version.
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
This library is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
Unless required by applicable law or agreed to in writing, software
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
IBM Common Public License for more details.
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
You should have received a copy of the IBM Common Public
|
limitations under the License.
|
||||||
License along with this library
|
*/
|
||||||
*/
|
#endregion // Apache License 2.0
|
||||||
#endregion
|
|
||||||
|
using System;
|
||||||
using System;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Generic;
|
using System.IO;
|
||||||
using System.IO;
|
|
||||||
|
#if UNITTEST
|
||||||
#if UNITTEST
|
|
||||||
|
using NUnit.Framework;
|
||||||
using NUnit.Framework;
|
|
||||||
|
namespace Nuclex.Support.Collections {
|
||||||
namespace Nuclex.Support.Collections {
|
|
||||||
|
/// <summary>Unit Test for the list segment class</summary>
|
||||||
/// <summary>Unit Test for the list segment class</summary>
|
[TestFixture]
|
||||||
[TestFixture]
|
internal class ListSegmentTest {
|
||||||
internal class ListSegmentTest {
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Tests whether the default constructor of the ListSegment class throws the
|
||||||
/// Tests whether the default constructor of the ListSegment class throws the
|
/// right exception when being passed 'null' instead of a list
|
||||||
/// right exception when being passed 'null' instead of a list
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void SimpleConstructorThrowsWhenListIsNull() {
|
||||||
public void SimpleConstructorThrowsWhenListIsNull() {
|
Assert.Throws<ArgumentNullException>(
|
||||||
Assert.Throws<ArgumentNullException>(
|
delegate() { new ListSegment<int>(null); }
|
||||||
delegate() { new ListSegment<int>(null); }
|
);
|
||||||
);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Tests whether the simple constructor of the ListSegment class accepts
|
||||||
/// Tests whether the simple constructor of the ListSegment class accepts
|
/// an empty list
|
||||||
/// an empty list
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void SimpleConstructorAcceptsEmptyList() {
|
||||||
public void SimpleConstructorAcceptsEmptyList() {
|
new ListSegment<int>(new List<int>());
|
||||||
new ListSegment<int>(new List<int>());
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Tests whether the full constructor of the ListSegment class throws the
|
||||||
/// Tests whether the full constructor of the ListSegment class throws the
|
/// right exception when being passed 'null' instead of a string
|
||||||
/// right exception when being passed 'null' instead of a string
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void ConstructorThrowsWhenListIsNull() {
|
||||||
public void ConstructorThrowsWhenListIsNull() {
|
Assert.Throws<ArgumentNullException>(
|
||||||
Assert.Throws<ArgumentNullException>(
|
delegate() { new ListSegment<int>(null, 0, 0); }
|
||||||
delegate() { new ListSegment<int>(null, 0, 0); }
|
);
|
||||||
);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Tests whether the full constructor of the ListSegment class accepts
|
||||||
/// Tests whether the full constructor of the ListSegment class accepts
|
/// an empty string
|
||||||
/// an empty string
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void ConstructorAcceptsEmptyList() {
|
||||||
public void ConstructorAcceptsEmptyList() {
|
new ListSegment<int>(new List<int>(), 0, 0);
|
||||||
new ListSegment<int>(new List<int>(), 0, 0);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Tests whether the full constructor of the ListSegment class throws the
|
||||||
/// Tests whether the full constructor of the ListSegment class throws the
|
/// right exception when being passed an invalid start offset
|
||||||
/// right exception when being passed an invalid start offset
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void ConstructorThrowsOnInvalidOffset() {
|
||||||
public void ConstructorThrowsOnInvalidOffset() {
|
Assert.Throws<ArgumentOutOfRangeException>(
|
||||||
Assert.Throws<ArgumentOutOfRangeException>(
|
delegate() { new ListSegment<int>(new List<int>(), -1, 0); }
|
||||||
delegate() { new ListSegment<int>(new List<int>(), -1, 0); }
|
);
|
||||||
);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Tests whether the full constructor of the ListSegment class throws the
|
||||||
/// Tests whether the full constructor of the ListSegment class throws the
|
/// right exception when being passed an invalid element count
|
||||||
/// right exception when being passed an invalid element count
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void ConstructorThrowsOnInvalidCount() {
|
||||||
public void ConstructorThrowsOnInvalidCount() {
|
Assert.Throws<ArgumentOutOfRangeException>(
|
||||||
Assert.Throws<ArgumentOutOfRangeException>(
|
delegate() { new ListSegment<int>(new List<int>(), 0, -1); }
|
||||||
delegate() { new ListSegment<int>(new List<int>(), 0, -1); }
|
);
|
||||||
);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Tests whether the full constructor of the ListSegment class throws the
|
||||||
/// Tests whether the full constructor of the ListSegment class throws the
|
/// right exception when being passed a string length that's too large
|
||||||
/// right exception when being passed a string length that's too large
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void ConstructorThrowsOnListOverrun() {
|
||||||
public void ConstructorThrowsOnListOverrun() {
|
var testList = new List<int>(capacity: 5) { 1, 2, 3, 4, 5 };
|
||||||
var testList = new List<int>(capacity: 5) { 1, 2, 3, 4, 5 };
|
Assert.Throws<ArgumentException>(
|
||||||
Assert.Throws<ArgumentException>(
|
delegate() { new ListSegment<int>(testList, 3, 3); }
|
||||||
delegate() { new ListSegment<int>(testList, 3, 3); }
|
);
|
||||||
);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Tests whether the 'Text' property works as expected</summary>
|
||||||
/// <summary>Tests whether the 'Text' property works as expected</summary>
|
[Test]
|
||||||
[Test]
|
public void ListPropertyStoresOriginalList() {
|
||||||
public void ListPropertyStoresOriginalList() {
|
var testList = new List<int>(capacity: 5) { 1, 2, 3, 4, 5 };
|
||||||
var testList = new List<int>(capacity: 5) { 1, 2, 3, 4, 5 };
|
ListSegment<int> testSegment = new ListSegment<int>(testList, 1, 3);
|
||||||
ListSegment<int> testSegment = new ListSegment<int>(testList, 1, 3);
|
Assert.AreSame(testList, testSegment.List);
|
||||||
Assert.AreSame(testList, testSegment.List);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Tests whether the 'Offset' property works as expected</summary>
|
||||||
/// <summary>Tests whether the 'Offset' property works as expected</summary>
|
[Test]
|
||||||
[Test]
|
public void OffsetPropertyIsStored() {
|
||||||
public void OffsetPropertyIsStored() {
|
var testList = new List<int>(capacity: 5) { 1, 2, 3, 4, 5 };
|
||||||
var testList = new List<int>(capacity: 5) { 1, 2, 3, 4, 5 };
|
ListSegment<int> testSegment = new ListSegment<int>(testList, 1, 3);
|
||||||
ListSegment<int> testSegment = new ListSegment<int>(testList, 1, 3);
|
Assert.AreEqual(1, testSegment.Offset);
|
||||||
Assert.AreEqual(1, testSegment.Offset);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Tests whether the 'Count' property works as expected</summary>
|
||||||
/// <summary>Tests whether the 'Count' property works as expected</summary>
|
[Test]
|
||||||
[Test]
|
public void CountPropertyIsStored() {
|
||||||
public void CountPropertyIsStored() {
|
var testList = new List<int>(capacity: 5) { 1, 2, 3, 4, 5 };
|
||||||
var testList = new List<int>(capacity: 5) { 1, 2, 3, 4, 5 };
|
ListSegment<int> testSegment = new ListSegment<int>(testList, 1, 3);
|
||||||
ListSegment<int> testSegment = new ListSegment<int>(testList, 1, 3);
|
Assert.AreEqual(3, testSegment.Count);
|
||||||
Assert.AreEqual(3, testSegment.Count);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Tests whether two differing instances produce different hash codes
|
||||||
/// Tests whether two differing instances produce different hash codes
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void DifferentInstancesHaveDifferentHashCodes_Usually() {
|
||||||
public void DifferentInstancesHaveDifferentHashCodes_Usually() {
|
var forwardCountSegment = new ListSegment<int>(
|
||||||
var forwardCountSegment = new ListSegment<int>(
|
new List<int>(capacity: 9) { 1, 2, 3, 4, 5, 6, 7, 8, 9 }, 2, 7
|
||||||
new List<int>(capacity: 9) { 1, 2, 3, 4, 5, 6, 7, 8, 9 }, 2, 7
|
);
|
||||||
);
|
var reverseCountSegment = new ListSegment<int>(
|
||||||
var reverseCountSegment = new ListSegment<int>(
|
new List<int>(capacity: 9) { 9, 8, 7, 6, 5, 4, 3, 2, 1 }, 1, 8
|
||||||
new List<int>(capacity: 9) { 9, 8, 7, 6, 5, 4, 3, 2, 1 }, 1, 8
|
);
|
||||||
);
|
|
||||||
|
Assert.AreNotEqual(
|
||||||
Assert.AreNotEqual(
|
forwardCountSegment.GetHashCode(), reverseCountSegment.GetHashCode()
|
||||||
forwardCountSegment.GetHashCode(), reverseCountSegment.GetHashCode()
|
);
|
||||||
);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Tests whether two equivalent instances produce an identical hash code
|
||||||
/// Tests whether two equivalent instances produce an identical hash code
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void EquivalentInstancesHaveSameHashcode() {
|
||||||
public void EquivalentInstancesHaveSameHashcode() {
|
var testSegment = new ListSegment<int>(
|
||||||
var testSegment = new ListSegment<int>(
|
new List<int>(capacity: 9) { 1, 2, 3, 4, 5, 6, 7, 8, 9 }, 2, 7
|
||||||
new List<int>(capacity: 9) { 1, 2, 3, 4, 5, 6, 7, 8, 9 }, 2, 7
|
);
|
||||||
);
|
var identicalSegment = new ListSegment<int>(
|
||||||
var identicalSegment = new ListSegment<int>(
|
new List<int>(capacity: 9) { 1, 2, 3, 4, 5, 6, 7, 8, 9 }, 2, 7
|
||||||
new List<int>(capacity: 9) { 1, 2, 3, 4, 5, 6, 7, 8, 9 }, 2, 7
|
);
|
||||||
);
|
|
||||||
|
Assert.AreEqual(
|
||||||
Assert.AreEqual(
|
testSegment.GetHashCode(), identicalSegment.GetHashCode()
|
||||||
testSegment.GetHashCode(), identicalSegment.GetHashCode()
|
);
|
||||||
);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Tests the equals method performing a comparison against null</summary>
|
||||||
/// <summary>Tests the equals method performing a comparison against null</summary>
|
[Test]
|
||||||
[Test]
|
public void EqualsAgainstNullIsAlwaysFalse() {
|
||||||
public void EqualsAgainstNullIsAlwaysFalse() {
|
var testList = new List<int>(capacity: 5) { 1, 2, 3, 4, 5 };
|
||||||
var testList = new List<int>(capacity: 5) { 1, 2, 3, 4, 5 };
|
ListSegment<int> testSegment = new ListSegment<int>(testList, 1, 3);
|
||||||
ListSegment<int> testSegment = new ListSegment<int>(testList, 1, 3);
|
|
||||||
|
Assert.IsFalse(
|
||||||
Assert.IsFalse(
|
testSegment.Equals(null)
|
||||||
testSegment.Equals(null)
|
);
|
||||||
);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Tests the equality operator with differing instances</summary>
|
||||||
/// <summary>Tests the equality operator with differing instances</summary>
|
[Test]
|
||||||
[Test]
|
public void DifferingInstancesAreNotEqual() {
|
||||||
public void DifferingInstancesAreNotEqual() {
|
var forwardCountSegment = new ListSegment<int>(
|
||||||
var forwardCountSegment = new ListSegment<int>(
|
new List<int>(capacity: 9) { 1, 2, 3, 4, 5, 6, 7, 8, 9 }, 2, 7
|
||||||
new List<int>(capacity: 9) { 1, 2, 3, 4, 5, 6, 7, 8, 9 }, 2, 7
|
);
|
||||||
);
|
var reverseCountSegment = new ListSegment<int>(
|
||||||
var reverseCountSegment = new ListSegment<int>(
|
new List<int>(capacity: 9) { 9, 8, 7, 6, 5, 4, 3, 2, 1 }, 1, 8
|
||||||
new List<int>(capacity: 9) { 9, 8, 7, 6, 5, 4, 3, 2, 1 }, 1, 8
|
);
|
||||||
);
|
|
||||||
|
Assert.IsFalse(forwardCountSegment == reverseCountSegment);
|
||||||
Assert.IsFalse(forwardCountSegment == reverseCountSegment);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Tests the equality operator with equivalent instances</summary>
|
||||||
/// <summary>Tests the equality operator with equivalent instances</summary>
|
[Test]
|
||||||
[Test]
|
public void EquivalentInstancesAreEqual() {
|
||||||
public void EquivalentInstancesAreEqual() {
|
var testSegment = new ListSegment<int>(
|
||||||
var testSegment = new ListSegment<int>(
|
new List<int>(capacity: 9) { 1, 2, 3, 4, 5, 6, 7, 8, 9 }, 2, 7
|
||||||
new List<int>(capacity: 9) { 1, 2, 3, 4, 5, 6, 7, 8, 9 }, 2, 7
|
);
|
||||||
);
|
var identicalSegment = new ListSegment<int>(
|
||||||
var identicalSegment = new ListSegment<int>(
|
new List<int>(capacity: 9) { 1, 2, 3, 4, 5, 6, 7, 8, 9 }, 2, 7
|
||||||
new List<int>(capacity: 9) { 1, 2, 3, 4, 5, 6, 7, 8, 9 }, 2, 7
|
);
|
||||||
);
|
|
||||||
|
Assert.IsTrue(testSegment == identicalSegment);
|
||||||
Assert.IsTrue(testSegment == identicalSegment);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Tests the inequality operator with differing instances</summary>
|
||||||
/// <summary>Tests the inequality operator with differing instances</summary>
|
[Test]
|
||||||
[Test]
|
public void DifferingInstancesAreUnequal() {
|
||||||
public void DifferingInstancesAreUnequal() {
|
var forwardCountSegment = new ListSegment<int>(
|
||||||
var forwardCountSegment = new ListSegment<int>(
|
new List<int>(capacity: 9) { 1, 2, 3, 4, 5, 6, 7, 8, 9 }, 2, 7
|
||||||
new List<int>(capacity: 9) { 1, 2, 3, 4, 5, 6, 7, 8, 9 }, 2, 7
|
);
|
||||||
);
|
var reverseCountSegment = new ListSegment<int>(
|
||||||
var reverseCountSegment = new ListSegment<int>(
|
new List<int>(capacity: 9) { 9, 8, 7, 6, 5, 4, 3, 2, 1 }, 1, 8
|
||||||
new List<int>(capacity: 9) { 9, 8, 7, 6, 5, 4, 3, 2, 1 }, 1, 8
|
);
|
||||||
);
|
|
||||||
|
Assert.IsTrue(forwardCountSegment != reverseCountSegment);
|
||||||
Assert.IsTrue(forwardCountSegment != reverseCountSegment);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Tests the inequality operator with equivalent instances</summary>
|
||||||
/// <summary>Tests the inequality operator with equivalent instances</summary>
|
[Test]
|
||||||
[Test]
|
public void EquivalentInstancesAreNotUnequal() {
|
||||||
public void EquivalentInstancesAreNotUnequal() {
|
var testSegment = new ListSegment<int>(
|
||||||
var testSegment = new ListSegment<int>(
|
new List<int>(capacity: 9) { 1, 2, 3, 4, 5, 6, 7, 8, 9 }, 2, 7
|
||||||
new List<int>(capacity: 9) { 1, 2, 3, 4, 5, 6, 7, 8, 9 }, 2, 7
|
);
|
||||||
);
|
var identicalSegment = new ListSegment<int>(
|
||||||
var identicalSegment = new ListSegment<int>(
|
new List<int>(capacity: 9) { 1, 2, 3, 4, 5, 6, 7, 8, 9 }, 2, 7
|
||||||
new List<int>(capacity: 9) { 1, 2, 3, 4, 5, 6, 7, 8, 9 }, 2, 7
|
);
|
||||||
);
|
|
||||||
|
Assert.IsFalse(testSegment != identicalSegment);
|
||||||
Assert.IsFalse(testSegment != identicalSegment);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Tests the ToString() method of the string segment</summary>
|
||||||
/// <summary>Tests the ToString() method of the string segment</summary>
|
[Test]
|
||||||
[Test]
|
public void TestToString() {
|
||||||
public void TestToString() {
|
var testList = new List<int>(capacity: 6) { 1, 2, 3, 4, 5, 6 };
|
||||||
var testList = new List<int>(capacity: 6) { 1, 2, 3, 4, 5, 6 };
|
ListSegment<int> testSegment = new ListSegment<int>(testList, 2, 2);
|
||||||
ListSegment<int> testSegment = new ListSegment<int>(testList, 2, 2);
|
|
||||||
|
string stringRepresentation = testSegment.ToString();
|
||||||
string stringRepresentation = testSegment.ToString();
|
StringAssert.Contains("3, 4", stringRepresentation);
|
||||||
StringAssert.Contains("3, 4", stringRepresentation);
|
StringAssert.DoesNotContain("2", stringRepresentation);
|
||||||
StringAssert.DoesNotContain("2", stringRepresentation);
|
StringAssert.DoesNotContain("5", stringRepresentation);
|
||||||
StringAssert.DoesNotContain("5", stringRepresentation);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Tests whether the 'Text' property works as expected</summary>
|
||||||
/// <summary>Tests whether the 'Text' property works as expected</summary>
|
[Test]
|
||||||
[Test]
|
public void ToListReturnsSubset() {
|
||||||
public void ToListReturnsSubset() {
|
var testList = new List<int>(capacity: 5) { 1, 2, 3, 4, 5 };
|
||||||
var testList = new List<int>(capacity: 5) { 1, 2, 3, 4, 5 };
|
ListSegment<int> testSegment = new ListSegment<int>(testList, 1, 3);
|
||||||
ListSegment<int> testSegment = new ListSegment<int>(testList, 1, 3);
|
CollectionAssert.AreEqual(
|
||||||
CollectionAssert.AreEqual(
|
new List<int>(capacity: 3) { 2, 3, 4 },
|
||||||
new List<int>(capacity: 3) { 2, 3, 4 },
|
testSegment.ToList()
|
||||||
testSegment.ToList()
|
);
|
||||||
);
|
}
|
||||||
}
|
|
||||||
|
}
|
||||||
}
|
|
||||||
|
} // namespace Nuclex.Support.Collections
|
||||||
} // namespace Nuclex.Support.Collections
|
|
||||||
|
#endif // UNITTEST
|
||||||
#endif // UNITTEST
|
|
||||||
|
|
|
@ -1,281 +1,280 @@
|
||||||
#region CPL License
|
#region Apache License 2.0
|
||||||
/*
|
/*
|
||||||
Nuclex Framework
|
Nuclex .NET Framework
|
||||||
Copyright (C) 2002-2017 Nuclex Development Labs
|
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
|
||||||
|
|
||||||
This library is free software; you can redistribute it and/or
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
modify it under the terms of the IBM Common Public License as
|
you may not use this file except in compliance with the License.
|
||||||
published by the IBM Corporation; either version 1.0 of the
|
You may obtain a copy of the License at
|
||||||
License, or (at your option) any later version.
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
This library is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
Unless required by applicable law or agreed to in writing, software
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
IBM Common Public License for more details.
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
You should have received a copy of the IBM Common Public
|
limitations under the License.
|
||||||
License along with this library
|
*/
|
||||||
*/
|
#endregion // Apache License 2.0
|
||||||
#endregion
|
|
||||||
|
using System;
|
||||||
using System;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Generic;
|
using System.Runtime.InteropServices;
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
|
namespace Nuclex.Support.Collections {
|
||||||
namespace Nuclex.Support.Collections {
|
|
||||||
|
/// <summary>View into a section of an IList<T> without copying said string</summary>
|
||||||
/// <summary>View into a section of an IList<T> without copying said string</summary>
|
/// <typeparam name="TElement">
|
||||||
/// <typeparam name="TElement">
|
/// Type of elements that are stored in the list the segment references
|
||||||
/// Type of elements that are stored in the list the segment references
|
/// </typeparam>
|
||||||
/// </typeparam>
|
/// <remarks>
|
||||||
/// <remarks>
|
/// <para>
|
||||||
/// <para>
|
/// The design of this class pretty much mirrors that of the
|
||||||
/// The design of this class pretty much mirrors that of the
|
/// <see cref="T:System.ArraySegment" /> class found in the .NET framework, but is
|
||||||
/// <see cref="T:System.ArraySegment" /> class found in the .NET framework, but is
|
/// specialized to be used for IList<T>, which can not be cast to arrays
|
||||||
/// specialized to be used for IList<T>, which can not be cast to arrays
|
/// directly (and <see cref="M:System.ArrayList.Adapter" /> loses type safety).
|
||||||
/// directly (and <see cref="M:System.ArrayList.Adapter" /> loses type safety).
|
/// </para>
|
||||||
/// </para>
|
/// <para>
|
||||||
/// <para>
|
/// In certain situations, passing a ListSegment instead of storing the selected
|
||||||
/// In certain situations, passing a ListSegment instead of storing the selected
|
/// elements in a new list is useful. For example, the caller might want to know
|
||||||
/// elements in a new list is useful. For example, the caller might want to know
|
/// from which index of the original list the section was taken. When the original
|
||||||
/// from which index of the original list the section was taken. When the original
|
/// list needs to be modified, for example in a sorting algorithm, the list segment
|
||||||
/// list needs to be modified, for example in a sorting algorithm, the list segment
|
/// can be used to specify a region for the algorithm to work on while still accessing
|
||||||
/// can be used to specify a region for the algorithm to work on while still accessing
|
/// the original list.
|
||||||
/// the original list.
|
/// </para>
|
||||||
/// </para>
|
/// </remarks>
|
||||||
/// </remarks>
|
#if !NO_SERIALIZATION
|
||||||
#if !NO_SERIALIZATION
|
[Serializable, StructLayout(LayoutKind.Sequential)]
|
||||||
[Serializable, StructLayout(LayoutKind.Sequential)]
|
#endif
|
||||||
#endif
|
public struct ListSegment<TElement> {
|
||||||
public struct ListSegment<TElement> {
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Initializes a new instance of the <see cref="ListSegment<TElement>" /> class
|
||||||
/// Initializes a new instance of the <see cref="ListSegment<TElement>" /> class
|
/// that delimits all the elements in the specified string
|
||||||
/// that delimits all the elements in the specified string
|
/// </summary>
|
||||||
/// </summary>
|
/// <param name="list">List that will be wrapped</param>
|
||||||
/// <param name="list">List that will be wrapped</param>
|
/// <exception cref="System.ArgumentNullException">String is null</exception>
|
||||||
/// <exception cref="System.ArgumentNullException">String is null</exception>
|
public ListSegment(IList<TElement> list) {
|
||||||
public ListSegment(IList<TElement> list) {
|
if(list == null) { // questionable, but matches behavior of ArraySegment class
|
||||||
if(list == null) { // questionable, but matches behavior of ArraySegment class
|
throw new ArgumentNullException("text", "Text must not be null");
|
||||||
throw new ArgumentNullException("text", "Text must not be null");
|
}
|
||||||
}
|
|
||||||
|
this.list = list;
|
||||||
this.list = list;
|
this.offset = 0;
|
||||||
this.offset = 0;
|
this.count = list.Count;
|
||||||
this.count = list.Count;
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Initializes a new instance of the <see cref="ListSegment<TElement>" /> class
|
||||||
/// Initializes a new instance of the <see cref="ListSegment<TElement>" /> class
|
/// that delimits the specified range of the elements in the specified string
|
||||||
/// that delimits the specified range of the elements in the specified string
|
/// </summary>
|
||||||
/// </summary>
|
/// <param name="list">The list containing the range of elements to delimit</param>
|
||||||
/// <param name="list">The list containing the range of elements to delimit</param>
|
/// <param name="offset">The zero-based index of the first element in the range</param>
|
||||||
/// <param name="offset">The zero-based index of the first element in the range</param>
|
/// <param name="count">The number of elements in the range</param>
|
||||||
/// <param name="count">The number of elements in the range</param>
|
/// <exception cref="System.ArgumentOutOfRangeException">
|
||||||
/// <exception cref="System.ArgumentOutOfRangeException">
|
/// Offset or count is less than 0
|
||||||
/// Offset or count is less than 0
|
/// </exception>
|
||||||
/// </exception>
|
/// <exception cref="System.ArgumentException">
|
||||||
/// <exception cref="System.ArgumentException">
|
/// Offset and count do not specify a valid range in array
|
||||||
/// Offset and count do not specify a valid range in array
|
/// </exception>
|
||||||
/// </exception>
|
/// <exception cref="System.ArgumentNullException">String is null</exception>
|
||||||
/// <exception cref="System.ArgumentNullException">String is null</exception>
|
public ListSegment(IList<TElement> list, int offset, int count) {
|
||||||
public ListSegment(IList<TElement> list, int offset, int count) {
|
if(list == null) { // questionable, but matches behavior of ArraySegment class
|
||||||
if(list == null) { // questionable, but matches behavior of ArraySegment class
|
throw new ArgumentNullException("list");
|
||||||
throw new ArgumentNullException("list");
|
}
|
||||||
}
|
if(offset < 0) {
|
||||||
if(offset < 0) {
|
throw new ArgumentOutOfRangeException(
|
||||||
throw new ArgumentOutOfRangeException(
|
"offset", "Argument out of range, non-negative number required"
|
||||||
"offset", "Argument out of range, non-negative number required"
|
);
|
||||||
);
|
}
|
||||||
}
|
if(count < 0) {
|
||||||
if(count < 0) {
|
throw new ArgumentOutOfRangeException(
|
||||||
throw new ArgumentOutOfRangeException(
|
"count", "Argument out of range, non-negative number required"
|
||||||
"count", "Argument out of range, non-negative number required"
|
);
|
||||||
);
|
}
|
||||||
}
|
if(count > (list.Count - offset)) {
|
||||||
if(count > (list.Count - offset)) {
|
throw new ArgumentException(
|
||||||
throw new ArgumentException(
|
"Invalid argument, specified offset and count exceed list size"
|
||||||
"Invalid argument, specified offset and count exceed list size"
|
);
|
||||||
);
|
}
|
||||||
}
|
|
||||||
|
this.list = list;
|
||||||
this.list = list;
|
this.offset = offset;
|
||||||
this.offset = offset;
|
this.count = count;
|
||||||
this.count = count;
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Gets the original list containing the range of elements that the list
|
||||||
/// Gets the original list containing the range of elements that the list
|
/// segment delimits
|
||||||
/// segment delimits
|
/// </summary>
|
||||||
/// </summary>
|
/// <returns>
|
||||||
/// <returns>
|
/// The original list that was passed to the constructor, and that contains the range
|
||||||
/// The original list that was passed to the constructor, and that contains the range
|
/// delimited by the <see cref="ListSegment<TElement>" />
|
||||||
/// delimited by the <see cref="ListSegment<TElement>" />
|
/// </returns>
|
||||||
/// </returns>
|
public IList<TElement> List {
|
||||||
public IList<TElement> List {
|
get { return this.list; }
|
||||||
get { return this.list; }
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Gets the position of the first element in the range delimited by the list segment,
|
||||||
/// Gets the position of the first element in the range delimited by the list segment,
|
/// relative to the start of the original list
|
||||||
/// relative to the start of the original list
|
/// </summary>
|
||||||
/// </summary>
|
/// <returns>
|
||||||
/// <returns>
|
/// The position of the first element in the range delimited by the
|
||||||
/// The position of the first element in the range delimited by the
|
/// <see cref="ListSegment<TElement>" />, relative to the start of the original list
|
||||||
/// <see cref="ListSegment<TElement>" />, relative to the start of the original list
|
/// </returns>
|
||||||
/// </returns>
|
public int Offset {
|
||||||
public int Offset {
|
get { return this.offset; }
|
||||||
get { return this.offset; }
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Gets the number of elements in the range delimited by the list segment
|
||||||
/// Gets the number of elements in the range delimited by the list segment
|
/// </summary>
|
||||||
/// </summary>
|
/// <returns>
|
||||||
/// <returns>
|
/// The number of elements in the range delimited by
|
||||||
/// The number of elements in the range delimited by
|
/// the <see cref="ListSegment<TElement>" />
|
||||||
/// the <see cref="ListSegment<TElement>" />
|
/// </returns>
|
||||||
/// </returns>
|
public int Count {
|
||||||
public int Count {
|
get { return this.count; }
|
||||||
get { return this.count; }
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Returns the hash code for the current instance</summary>
|
||||||
/// <summary>Returns the hash code for the current instance</summary>
|
/// <returns>A 32-bit signed integer hash code</returns>
|
||||||
/// <returns>A 32-bit signed integer hash code</returns>
|
public override int GetHashCode() {
|
||||||
public override int GetHashCode() {
|
int hashCode = this.offset ^ this.count;
|
||||||
int hashCode = this.offset ^ this.count;
|
for(int index = 0; index < this.count; ++index) {
|
||||||
for(int index = 0; index < this.count; ++index) {
|
hashCode ^= this.list[index + this.offset].GetHashCode();
|
||||||
hashCode ^= this.list[index + this.offset].GetHashCode();
|
}
|
||||||
}
|
return hashCode;
|
||||||
return hashCode;
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Determines whether the specified object is equal to the current instance
|
||||||
/// Determines whether the specified object is equal to the current instance
|
/// </summary>
|
||||||
/// </summary>
|
/// <returns>
|
||||||
/// <returns>
|
/// True if the specified object is a <see cref="ListSegment<TElement>" /> structure
|
||||||
/// True if the specified object is a <see cref="ListSegment<TElement>" /> structure
|
/// and is equal to the current instance; otherwise, false
|
||||||
/// and is equal to the current instance; otherwise, false
|
/// </returns>
|
||||||
/// </returns>
|
/// <param name="other">The object to be compared with the current instance</param>
|
||||||
/// <param name="other">The object to be compared with the current instance</param>
|
public override bool Equals(object other) {
|
||||||
public override bool Equals(object other) {
|
return
|
||||||
return
|
(other is ListSegment<TElement>) &&
|
||||||
(other is ListSegment<TElement>) &&
|
this.Equals((ListSegment<TElement>)other);
|
||||||
this.Equals((ListSegment<TElement>)other);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Determines whether the specified <see cref="ListSegment<TElement>" />
|
||||||
/// Determines whether the specified <see cref="ListSegment<TElement>" />
|
/// structure is equal to the current instance
|
||||||
/// structure is equal to the current instance
|
/// </summary>
|
||||||
/// </summary>
|
/// <returns>
|
||||||
/// <returns>
|
/// True if the specified <see cref="ListSegment<TElement>" /> structure is equal
|
||||||
/// True if the specified <see cref="ListSegment<TElement>" /> structure is equal
|
/// to the current instance; otherwise, false
|
||||||
/// to the current instance; otherwise, false
|
/// </returns>
|
||||||
/// </returns>
|
/// <param name="other">
|
||||||
/// <param name="other">
|
/// The <see cref="ListSegment<TElement>" /> structure to be compared with
|
||||||
/// The <see cref="ListSegment<TElement>" /> structure to be compared with
|
/// the current instance
|
||||||
/// the current instance
|
/// </param>
|
||||||
/// </param>
|
public bool Equals(ListSegment<TElement> other) {
|
||||||
public bool Equals(ListSegment<TElement> other) {
|
if(other.count != this.count) {
|
||||||
if(other.count != this.count) {
|
return false;
|
||||||
return false;
|
}
|
||||||
}
|
|
||||||
|
if(ReferenceEquals(other.list, this.list)) {
|
||||||
if(ReferenceEquals(other.list, this.list)) {
|
return (other.offset == this.offset);
|
||||||
return (other.offset == this.offset);
|
} else {
|
||||||
} else {
|
var comparer = Comparer<TElement>.Default;
|
||||||
var comparer = Comparer<TElement>.Default;
|
for(int index = 0; index < this.count; ++index) {
|
||||||
for(int index = 0; index < this.count; ++index) {
|
int difference = comparer.Compare(
|
||||||
int difference = comparer.Compare(
|
other.list[index + other.offset], this.list[index + this.offset]
|
||||||
other.list[index + other.offset], this.list[index + this.offset]
|
);
|
||||||
);
|
if(difference != 0) {
|
||||||
if(difference != 0) {
|
return false;
|
||||||
return false;
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
return true;
|
||||||
return true;
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Indicates whether two <see cref="ListSegment<TElement>" /> structures are equal
|
||||||
/// Indicates whether two <see cref="ListSegment<TElement>" /> structures are equal
|
/// </summary>
|
||||||
/// </summary>
|
/// <returns>True if a is equal to b; otherwise, false</returns>
|
||||||
/// <returns>True if a is equal to b; otherwise, false</returns>
|
/// <param name="left">
|
||||||
/// <param name="left">
|
/// The <see cref="ListSegment<TElement>" /> structure on the left side of
|
||||||
/// The <see cref="ListSegment<TElement>" /> structure on the left side of
|
/// the equality operator
|
||||||
/// the equality operator
|
/// </param>
|
||||||
/// </param>
|
/// <param name="right">
|
||||||
/// <param name="right">
|
/// The <see cref="ListSegment<TElement>" /> structure on the right side of
|
||||||
/// The <see cref="ListSegment<TElement>" /> structure on the right side of
|
/// the equality operator
|
||||||
/// the equality operator
|
/// </param>
|
||||||
/// </param>
|
public static bool operator ==(ListSegment<TElement> left, ListSegment<TElement> right) {
|
||||||
public static bool operator ==(ListSegment<TElement> left, ListSegment<TElement> right) {
|
return left.Equals(right);
|
||||||
return left.Equals(right);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Indicates whether two <see cref="ListSegment<TElement>" /> structures are unequal
|
||||||
/// Indicates whether two <see cref="ListSegment<TElement>" /> structures are unequal
|
/// </summary>
|
||||||
/// </summary>
|
/// <returns>True if a is not equal to b; otherwise, false</returns>
|
||||||
/// <returns>True if a is not equal to b; otherwise, false</returns>
|
/// <param name="left">
|
||||||
/// <param name="left">
|
/// The <see cref="ListSegment<TElement>" /> structure on the left side of
|
||||||
/// The <see cref="ListSegment<TElement>" /> structure on the left side of
|
/// the inequality operator
|
||||||
/// the inequality operator
|
/// </param>
|
||||||
/// </param>
|
/// <param name="right">
|
||||||
/// <param name="right">
|
/// The <see cref="ListSegment<TElement>" /> structure on the right side of
|
||||||
/// The <see cref="ListSegment<TElement>" /> structure on the right side of
|
/// the inequality operator
|
||||||
/// the inequality operator
|
/// </param>
|
||||||
/// </param>
|
public static bool operator !=(ListSegment<TElement> left, ListSegment<TElement> right) {
|
||||||
public static bool operator !=(ListSegment<TElement> left, ListSegment<TElement> right) {
|
return !(left == right);
|
||||||
return !(left == right);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Returns a string representation of the list segment</summary>
|
||||||
/// <summary>Returns a string representation of the list segment</summary>
|
/// <returns>The string representation of the list segment</returns>
|
||||||
/// <returns>The string representation of the list segment</returns>
|
public override string ToString() {
|
||||||
public override string ToString() {
|
var builder = new System.Text.StringBuilder();
|
||||||
var builder = new System.Text.StringBuilder();
|
builder.Append("ListSegment {");
|
||||||
builder.Append("ListSegment {");
|
for(int index = 0; index < Math.Min(this.count, 10); ++index) {
|
||||||
for(int index = 0; index < Math.Min(this.count, 10); ++index) {
|
if(index == 0) {
|
||||||
if(index == 0) {
|
builder.Append(" ");
|
||||||
builder.Append(" ");
|
} else {
|
||||||
} else {
|
builder.Append(", ");
|
||||||
builder.Append(", ");
|
}
|
||||||
}
|
builder.Append(this.list[index + this.offset].ToString());
|
||||||
builder.Append(this.list[index + this.offset].ToString());
|
}
|
||||||
}
|
|
||||||
|
if(this.count >= 11) {
|
||||||
if(this.count >= 11) {
|
builder.Append(", ... }");
|
||||||
builder.Append(", ... }");
|
} else {
|
||||||
} else {
|
builder.Append(" }");
|
||||||
builder.Append(" }");
|
}
|
||||||
}
|
|
||||||
|
return builder.ToString();
|
||||||
return builder.ToString();
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Returns a new list containing only the elements in the list segment</summary>
|
||||||
/// <summary>Returns a new list containing only the elements in the list segment</summary>
|
/// <returns>A new list containing only the elements in the list segment</returns>
|
||||||
/// <returns>A new list containing only the elements in the list segment</returns>
|
public List<TElement> ToList() {
|
||||||
public List<TElement> ToList() {
|
if(this.count == 0) {
|
||||||
if(this.count == 0) {
|
return new List<TElement>(capacity: 0);
|
||||||
return new List<TElement>(capacity: 0);
|
} else {
|
||||||
} else {
|
var newList = new List<TElement>(capacity: this.count);
|
||||||
var newList = new List<TElement>(capacity: this.count);
|
{
|
||||||
{
|
int endIndex = this.offset + this.count;
|
||||||
int endIndex = this.offset + this.count;
|
for(int index = this.offset; index < endIndex; ++index) {
|
||||||
for(int index = this.offset; index < endIndex; ++index) {
|
newList.Add(this.list[index]);
|
||||||
newList.Add(this.list[index]);
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
return newList;
|
||||||
return newList;
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
/// <summary>List wrapped by the list segment</summary>
|
||||||
/// <summary>List wrapped by the list segment</summary>
|
private IList<TElement> list;
|
||||||
private IList<TElement> list;
|
/// <summary>Offset in the original list the segment begins at</summary>
|
||||||
/// <summary>Offset in the original list the segment begins at</summary>
|
private int offset;
|
||||||
private int offset;
|
/// <summary>Number of elements in the segment</summary>
|
||||||
/// <summary>Number of elements in the segment</summary>
|
private int count;
|
||||||
private int count;
|
|
||||||
|
}
|
||||||
}
|
|
||||||
|
} // namespace Nuclex.Support.Collections
|
||||||
} // namespace Nuclex.Support.Collections
|
|
||||||
|
|
|
@ -1,273 +1,272 @@
|
||||||
#region CPL License
|
#region Apache License 2.0
|
||||||
/*
|
/*
|
||||||
Nuclex Framework
|
Nuclex .NET Framework
|
||||||
Copyright (C) 2002-2017 Nuclex Development Labs
|
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
|
||||||
|
|
||||||
This library is free software; you can redistribute it and/or
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
modify it under the terms of the IBM Common Public License as
|
you may not use this file except in compliance with the License.
|
||||||
published by the IBM Corporation; either version 1.0 of the
|
You may obtain a copy of the License at
|
||||||
License, or (at your option) any later version.
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
This library is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
Unless required by applicable law or agreed to in writing, software
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
IBM Common Public License for more details.
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
You should have received a copy of the IBM Common Public
|
limitations under the License.
|
||||||
License along with this library
|
*/
|
||||||
*/
|
#endregion // Apache License 2.0
|
||||||
#endregion
|
|
||||||
|
using System;
|
||||||
using System;
|
using System.Collections;
|
||||||
using System.Collections;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Generic;
|
|
||||||
|
namespace Nuclex.Support.Collections {
|
||||||
namespace Nuclex.Support.Collections {
|
|
||||||
|
partial class MultiDictionary<TKey, TValue> {
|
||||||
partial class MultiDictionary<TKey, TValue> {
|
|
||||||
|
#region IEnumerable implementation
|
||||||
#region IEnumerable implementation
|
|
||||||
|
/// <summary>Returns a new object enumerator for the Dictionary</summary>
|
||||||
/// <summary>Returns a new object enumerator for the Dictionary</summary>
|
/// <returns>The new object enumerator</returns>
|
||||||
/// <returns>The new object enumerator</returns>
|
IEnumerator IEnumerable.GetEnumerator() {
|
||||||
IEnumerator IEnumerable.GetEnumerator() {
|
return GetEnumerator();
|
||||||
return GetEnumerator();
|
}
|
||||||
}
|
|
||||||
|
#endregion
|
||||||
#endregion
|
|
||||||
|
#region IDictionary implementation
|
||||||
#region IDictionary implementation
|
|
||||||
|
/// <summary>Adds an item into the dictionary</summary>
|
||||||
/// <summary>Adds an item into the dictionary</summary>
|
/// <param name="key">Key under which the item will be added</param>
|
||||||
/// <param name="key">Key under which the item will be added</param>
|
/// <param name="value">Item that will be added</param>
|
||||||
/// <param name="value">Item that will be added</param>
|
void IDictionary.Add(object key, object value) {
|
||||||
void IDictionary.Add(object key, object value) {
|
Add((TKey)key, (TValue)value);
|
||||||
Add((TKey)key, (TValue)value);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Determines whether the specified key exists in the dictionary</summary>
|
||||||
/// <summary>Determines whether the specified key exists in the dictionary</summary>
|
/// <param name="key">Key that will be checked for</param>
|
||||||
/// <param name="key">Key that will be checked for</param>
|
/// <returns>True if an item with the specified key exists in the dictionary</returns>
|
||||||
/// <returns>True if an item with the specified key exists in the dictionary</returns>
|
bool IDictionary.Contains(object key) {
|
||||||
bool IDictionary.Contains(object key) {
|
return this.objectDictionary.Contains(key);
|
||||||
return this.objectDictionary.Contains(key);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Whether the size of the dictionary is fixed</summary>
|
||||||
/// <summary>Whether the size of the dictionary is fixed</summary>
|
bool IDictionary.IsFixedSize {
|
||||||
bool IDictionary.IsFixedSize {
|
get { return this.objectDictionary.IsFixedSize; }
|
||||||
get { return this.objectDictionary.IsFixedSize; }
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Returns a collection of all keys in the dictionary</summary>
|
||||||
/// <summary>Returns a collection of all keys in the dictionary</summary>
|
ICollection IDictionary.Keys {
|
||||||
ICollection IDictionary.Keys {
|
get { return this.objectDictionary.Keys; }
|
||||||
get { return this.objectDictionary.Keys; }
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Returns a collection of all values stored in the dictionary</summary>
|
||||||
/// <summary>Returns a collection of all values stored in the dictionary</summary>
|
ICollection IDictionary.Values {
|
||||||
ICollection IDictionary.Values {
|
get {
|
||||||
get {
|
if(this.valueCollection == null) {
|
||||||
if(this.valueCollection == null) {
|
this.valueCollection = new ValueCollection(this);
|
||||||
this.valueCollection = new ValueCollection(this);
|
}
|
||||||
}
|
|
||||||
|
return this.valueCollection;
|
||||||
return this.valueCollection;
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Removes an item from the dictionary</summary>
|
||||||
/// <summary>Removes an item from the dictionary</summary>
|
/// <param name="key">Key of the item that will be removed</param>
|
||||||
/// <param name="key">Key of the item that will be removed</param>
|
void IDictionary.Remove(object key) {
|
||||||
void IDictionary.Remove(object key) {
|
RemoveKey((TKey)key);
|
||||||
RemoveKey((TKey)key);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Accesses an item in the dictionary by its key</summary>
|
||||||
/// <summary>Accesses an item in the dictionary by its key</summary>
|
/// <param name="key">Key of the item that will be accessed</param>
|
||||||
/// <param name="key">Key of the item that will be accessed</param>
|
/// <returns>The item with the specified key</returns>
|
||||||
/// <returns>The item with the specified key</returns>
|
object IDictionary.this[object key] {
|
||||||
object IDictionary.this[object key] {
|
get { return this.objectDictionary[key]; }
|
||||||
get { return this.objectDictionary[key]; }
|
set { this[(TKey)key] = (ICollection<TValue>)value; }
|
||||||
set { this[(TKey)key] = (ICollection<TValue>)value; }
|
}
|
||||||
}
|
|
||||||
|
#endregion
|
||||||
#endregion
|
|
||||||
|
#region IDictionaryEnumerator implementation
|
||||||
#region IDictionaryEnumerator implementation
|
|
||||||
|
/// <summary>Returns a new entry enumerator for the dictionary</summary>
|
||||||
/// <summary>Returns a new entry enumerator for the dictionary</summary>
|
/// <returns>The new entry enumerator</returns>
|
||||||
/// <returns>The new entry enumerator</returns>
|
IDictionaryEnumerator IDictionary.GetEnumerator() {
|
||||||
IDictionaryEnumerator IDictionary.GetEnumerator() {
|
return new Enumerator(this);
|
||||||
return new Enumerator(this);
|
}
|
||||||
}
|
|
||||||
|
#endregion // IDictionaryEnumerator implementation
|
||||||
#endregion // IDictionaryEnumerator implementation
|
|
||||||
|
#region ICollection<KeyValuePair<TKey, TValue>> implementation
|
||||||
#region ICollection<KeyValuePair<TKey, TValue>> implementation
|
|
||||||
|
/// <summary>Inserts an already prepared element into the dictionary</summary>
|
||||||
/// <summary>Inserts an already prepared element into the dictionary</summary>
|
/// <param name="item">Prepared element that will be added to the dictionary</param>
|
||||||
/// <param name="item">Prepared element that will be added to the dictionary</param>
|
void ICollection<KeyValuePair<TKey, TValue>>.Add(
|
||||||
void ICollection<KeyValuePair<TKey, TValue>>.Add(
|
KeyValuePair<TKey, TValue> item
|
||||||
KeyValuePair<TKey, TValue> item
|
) {
|
||||||
) {
|
Add(item.Key, item.Value);
|
||||||
Add(item.Key, item.Value);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Removes all items from the dictionary</summary>
|
||||||
/// <summary>Removes all items from the dictionary</summary>
|
/// <param name="itemToRemove">Item that will be removed from the dictionary</param>
|
||||||
/// <param name="itemToRemove">Item that will be removed from the dictionary</param>
|
bool ICollection<KeyValuePair<TKey, TValue>>.Remove(
|
||||||
bool ICollection<KeyValuePair<TKey, TValue>>.Remove(
|
KeyValuePair<TKey, TValue> itemToRemove
|
||||||
KeyValuePair<TKey, TValue> itemToRemove
|
) {
|
||||||
) {
|
ICollection<TValue> values;
|
||||||
ICollection<TValue> values;
|
if(!this.typedDictionary.TryGetValue(itemToRemove.Key, out values)) {
|
||||||
if(!this.typedDictionary.TryGetValue(itemToRemove.Key, out values)) {
|
return false;
|
||||||
return false;
|
}
|
||||||
}
|
|
||||||
|
if(values.Remove(itemToRemove.Value)) {
|
||||||
if(values.Remove(itemToRemove.Value)) {
|
if(values.Count == 0) {
|
||||||
if(values.Count == 0) {
|
this.typedDictionary.Remove(itemToRemove.Key);
|
||||||
this.typedDictionary.Remove(itemToRemove.Key);
|
}
|
||||||
}
|
return true;
|
||||||
return true;
|
} else {
|
||||||
} else {
|
return false;
|
||||||
return false;
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
#endregion
|
||||||
#endregion
|
|
||||||
|
#region ICollection implementation
|
||||||
#region ICollection implementation
|
|
||||||
|
/// <summary>Copies the contents of the Dictionary into an array</summary>
|
||||||
/// <summary>Copies the contents of the Dictionary into an array</summary>
|
/// <param name="array">Array the Dictionary contents will be copied into</param>
|
||||||
/// <param name="array">Array the Dictionary contents will be copied into</param>
|
/// <param name="arrayIndex">
|
||||||
/// <param name="arrayIndex">
|
/// Starting index at which to begin filling the destination array
|
||||||
/// Starting index at which to begin filling the destination array
|
/// </param>
|
||||||
/// </param>
|
void ICollection.CopyTo(Array array, int arrayIndex) {
|
||||||
void ICollection.CopyTo(Array array, int arrayIndex) {
|
foreach(KeyValuePair<TKey, ICollection<TValue>> item in this.typedDictionary) {
|
||||||
foreach(KeyValuePair<TKey, ICollection<TValue>> item in this.typedDictionary) {
|
foreach(TValue value in item.Value) {
|
||||||
foreach(TValue value in item.Value) {
|
array.SetValue(new KeyValuePair<TKey, TValue>(item.Key, value), arrayIndex);
|
||||||
array.SetValue(new KeyValuePair<TKey, TValue>(item.Key, value), arrayIndex);
|
++arrayIndex;
|
||||||
++arrayIndex;
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Whether the Dictionary is synchronized for multi-threaded usage</summary>
|
||||||
/// <summary>Whether the Dictionary is synchronized for multi-threaded usage</summary>
|
bool ICollection.IsSynchronized {
|
||||||
bool ICollection.IsSynchronized {
|
get { return this.objectDictionary.IsSynchronized; }
|
||||||
get { return this.objectDictionary.IsSynchronized; }
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Synchronization root on which the Dictionary locks</summary>
|
||||||
/// <summary>Synchronization root on which the Dictionary locks</summary>
|
object ICollection.SyncRoot {
|
||||||
object ICollection.SyncRoot {
|
get { return this.objectDictionary.SyncRoot; }
|
||||||
get { return this.objectDictionary.SyncRoot; }
|
}
|
||||||
}
|
|
||||||
|
#endregion
|
||||||
#endregion
|
|
||||||
|
#region IDictionary<TKey, ICollection<TValue>> implementation
|
||||||
#region IDictionary<TKey, ICollection<TValue>> implementation
|
|
||||||
|
/// <summary>Adds a series of values to a dictionary</summary>
|
||||||
/// <summary>Adds a series of values to a dictionary</summary>
|
/// <param name="key">Key under which the values will be added</param>
|
||||||
/// <param name="key">Key under which the values will be added</param>
|
/// <param name="values">Values that will be added to the dictionary</param>
|
||||||
/// <param name="values">Values that will be added to the dictionary</param>
|
void IDictionary<TKey, ICollection<TValue>>.Add(TKey key, ICollection<TValue> values) {
|
||||||
void IDictionary<TKey, ICollection<TValue>>.Add(TKey key, ICollection<TValue> values) {
|
ICollection<TValue> currentValues;
|
||||||
ICollection<TValue> currentValues;
|
if(!this.typedDictionary.TryGetValue(key, out currentValues)) {
|
||||||
if(!this.typedDictionary.TryGetValue(key, out currentValues)) {
|
currentValues = new ValueList(this);
|
||||||
currentValues = new ValueList(this);
|
}
|
||||||
}
|
|
||||||
|
foreach(TValue value in values) {
|
||||||
foreach(TValue value in values) {
|
currentValues.Add(value);
|
||||||
currentValues.Add(value);
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Removes all values with the specified key</summary>
|
||||||
/// <summary>Removes all values with the specified key</summary>
|
/// <param name="key">Key whose associated entries will be removed</param>
|
||||||
/// <param name="key">Key whose associated entries will be removed</param>
|
/// <returns>True if at least one entry has been removed from the dictionary</returns>
|
||||||
/// <returns>True if at least one entry has been removed from the dictionary</returns>
|
bool IDictionary<TKey, ICollection<TValue>>.Remove(TKey key) {
|
||||||
bool IDictionary<TKey, ICollection<TValue>>.Remove(TKey key) {
|
return (RemoveKey(key) > 0);
|
||||||
return (RemoveKey(key) > 0);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Returns a collection of value collections</summary>
|
||||||
/// <summary>Returns a collection of value collections</summary>
|
ICollection<ICollection<TValue>> IDictionary<TKey, ICollection<TValue>>.Values {
|
||||||
ICollection<ICollection<TValue>> IDictionary<TKey, ICollection<TValue>>.Values {
|
get { return this.typedDictionary.Values; }
|
||||||
get { return this.typedDictionary.Values; }
|
}
|
||||||
}
|
|
||||||
|
#endregion // IDictionary<TKey, ICollection<TValue>> implementation
|
||||||
#endregion // IDictionary<TKey, ICollection<TValue>> implementation
|
|
||||||
|
#region ICollection<KeyValuePair<TKey, ICollection<TValue>>> implementation
|
||||||
#region ICollection<KeyValuePair<TKey, ICollection<TValue>>> implementation
|
|
||||||
|
/// <summary>Adds a series of values to a dictionary</summary>
|
||||||
/// <summary>Adds a series of values to a dictionary</summary>
|
/// <param name="item">Entry containing the values that will be added</param>
|
||||||
/// <param name="item">Entry containing the values that will be added</param>
|
void ICollection<KeyValuePair<TKey, ICollection<TValue>>>.Add(
|
||||||
void ICollection<KeyValuePair<TKey, ICollection<TValue>>>.Add(
|
KeyValuePair<TKey, ICollection<TValue>> item
|
||||||
KeyValuePair<TKey, ICollection<TValue>> item
|
) {
|
||||||
) {
|
ICollection<TValue> currentValues;
|
||||||
ICollection<TValue> currentValues;
|
if(!this.typedDictionary.TryGetValue(item.Key, out currentValues)) {
|
||||||
if(!this.typedDictionary.TryGetValue(item.Key, out currentValues)) {
|
currentValues = new ValueList(this);
|
||||||
currentValues = new ValueList(this);
|
}
|
||||||
}
|
|
||||||
|
foreach(TValue value in item.Value) {
|
||||||
foreach(TValue value in item.Value) {
|
currentValues.Add(value);
|
||||||
currentValues.Add(value);
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Checks whether the dictionary contains the specified key/value pair
|
||||||
/// Checks whether the dictionary contains the specified key/value pair
|
/// </summary>
|
||||||
/// </summary>
|
/// <param name="item">Key/value pair for which the dictionary will be checked</param>
|
||||||
/// <param name="item">Key/value pair for which the dictionary will be checked</param>
|
/// <returns>True if the dictionary contains the specified key/value pair</returns>
|
||||||
/// <returns>True if the dictionary contains the specified key/value pair</returns>
|
bool ICollection<KeyValuePair<TKey, ICollection<TValue>>>.Contains(
|
||||||
bool ICollection<KeyValuePair<TKey, ICollection<TValue>>>.Contains(
|
KeyValuePair<TKey, ICollection<TValue>> item
|
||||||
KeyValuePair<TKey, ICollection<TValue>> item
|
) {
|
||||||
) {
|
return this.typedDictionary.Contains(item);
|
||||||
return this.typedDictionary.Contains(item);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Copies the contents of the dictionary into an array</summary>
|
||||||
/// <summary>Copies the contents of the dictionary into an array</summary>
|
/// <param name="array"></param>
|
||||||
/// <param name="array"></param>
|
/// <param name="arrayIndex"></param>
|
||||||
/// <param name="arrayIndex"></param>
|
void ICollection<KeyValuePair<TKey, ICollection<TValue>>>.CopyTo(
|
||||||
void ICollection<KeyValuePair<TKey, ICollection<TValue>>>.CopyTo(
|
KeyValuePair<TKey, ICollection<TValue>>[] array, int arrayIndex
|
||||||
KeyValuePair<TKey, ICollection<TValue>>[] array, int arrayIndex
|
) {
|
||||||
) {
|
this.typedDictionary.CopyTo(array, arrayIndex);
|
||||||
this.typedDictionary.CopyTo(array, arrayIndex);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Removes the specified key/value pair from the dictionary</summary>
|
||||||
/// <summary>Removes the specified key/value pair from the dictionary</summary>
|
/// <param name="item">Key/value pair that will be removed</param>
|
||||||
/// <param name="item">Key/value pair that will be removed</param>
|
/// <returns>True if the key/value pair was contained in the dictionary</returns>
|
||||||
/// <returns>True if the key/value pair was contained in the dictionary</returns>
|
bool ICollection<KeyValuePair<TKey, ICollection<TValue>>>.Remove(
|
||||||
bool ICollection<KeyValuePair<TKey, ICollection<TValue>>>.Remove(
|
KeyValuePair<TKey, ICollection<TValue>> item
|
||||||
KeyValuePair<TKey, ICollection<TValue>> item
|
) {
|
||||||
) {
|
return this.typedDictionary.Remove(item);
|
||||||
return this.typedDictionary.Remove(item);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Returns an enumerator for the dictionary</summary>
|
||||||
/// <summary>Returns an enumerator for the dictionary</summary>
|
/// <returns>An enumerator for the key/value pairs in the dictionary</returns>
|
||||||
/// <returns>An enumerator for the key/value pairs in the dictionary</returns>
|
IEnumerator<KeyValuePair<TKey, ICollection<TValue>>> IEnumerable<
|
||||||
IEnumerator<KeyValuePair<TKey, ICollection<TValue>>> IEnumerable<
|
KeyValuePair<TKey, ICollection<TValue>>
|
||||||
KeyValuePair<TKey, ICollection<TValue>>
|
>.GetEnumerator() {
|
||||||
>.GetEnumerator() {
|
return this.typedDictionary.GetEnumerator();
|
||||||
return this.typedDictionary.GetEnumerator();
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Number of unique keys in the dictionary</summary>
|
||||||
/// <summary>Number of unique keys in the dictionary</summary>
|
/// <remarks>
|
||||||
/// <remarks>
|
/// <para>
|
||||||
/// <para>
|
/// This Count property returns a different value from the main interface of
|
||||||
/// This Count property returns a different value from the main interface of
|
/// the multi dictionary to stay consistent with the implemented interfaces.
|
||||||
/// the multi dictionary to stay consistent with the implemented interfaces.
|
/// </para>
|
||||||
/// </para>
|
/// <para>
|
||||||
/// <para>
|
/// If you cast a multi dictionary to a collection of collections, the count
|
||||||
/// If you cast a multi dictionary to a collection of collections, the count
|
/// property of the outer collection should, of course, be the number of inner
|
||||||
/// property of the outer collection should, of course, be the number of inner
|
/// collections it contains (and not the sum of the items contained in all of
|
||||||
/// collections it contains (and not the sum of the items contained in all of
|
/// the inner collections).
|
||||||
/// the inner collections).
|
/// </para>
|
||||||
/// </para>
|
/// <para>
|
||||||
/// <para>
|
/// If you use the count property in the main interface of the multi dictionary,
|
||||||
/// If you use the count property in the main interface of the multi dictionary,
|
/// the value collections are hidden (it behaves as if the key was in the
|
||||||
/// the value collections are hidden (it behaves as if the key was in the
|
/// dictionary multiple times), so now the sum of all key-value pairs should
|
||||||
/// dictionary multiple times), so now the sum of all key-value pairs should
|
/// be returned.
|
||||||
/// be returned.
|
/// </para>
|
||||||
/// </para>
|
/// </remarks>
|
||||||
/// </remarks>
|
int ICollection<KeyValuePair<TKey, ICollection<TValue>>>.Count {
|
||||||
int ICollection<KeyValuePair<TKey, ICollection<TValue>>>.Count {
|
get { return this.typedDictionary.Count; }
|
||||||
get { return this.typedDictionary.Count; }
|
}
|
||||||
}
|
|
||||||
|
#endregion // ICollection<KeyValuePair<TKey, ICollection<TValue>>> implementation
|
||||||
#endregion // ICollection<KeyValuePair<TKey, ICollection<TValue>>> implementation
|
|
||||||
|
}
|
||||||
}
|
|
||||||
|
} // namespace Nuclex.Support.Collections
|
||||||
} // namespace Nuclex.Support.Collections
|
|
||||||
|
|
|
@ -1,391 +1,390 @@
|
||||||
#region CPL License
|
#region Apache License 2.0
|
||||||
/*
|
/*
|
||||||
Nuclex Framework
|
Nuclex .NET Framework
|
||||||
Copyright (C) 2002-2017 Nuclex Development Labs
|
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
|
||||||
|
|
||||||
This library is free software; you can redistribute it and/or
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
modify it under the terms of the IBM Common Public License as
|
you may not use this file except in compliance with the License.
|
||||||
published by the IBM Corporation; either version 1.0 of the
|
You may obtain a copy of the License at
|
||||||
License, or (at your option) any later version.
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
This library is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
Unless required by applicable law or agreed to in writing, software
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
IBM Common Public License for more details.
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
You should have received a copy of the IBM Common Public
|
limitations under the License.
|
||||||
License along with this library
|
*/
|
||||||
*/
|
#endregion // Apache License 2.0
|
||||||
#endregion
|
|
||||||
|
#if UNITTEST
|
||||||
#if UNITTEST
|
|
||||||
|
using System;
|
||||||
using System;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Generic;
|
|
||||||
|
using NUnit.Framework;
|
||||||
using NUnit.Framework;
|
using NMock;
|
||||||
using NMock;
|
|
||||||
|
namespace Nuclex.Support.Collections {
|
||||||
namespace Nuclex.Support.Collections {
|
|
||||||
|
/// <summary>Unit tests for the multi dictionary</summary>
|
||||||
/// <summary>Unit tests for the multi dictionary</summary>
|
[TestFixture]
|
||||||
[TestFixture]
|
internal class MultiDictionaryTest {
|
||||||
internal class MultiDictionaryTest {
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Verifies that new instances of the multi dictionary can be created
|
||||||
/// Verifies that new instances of the multi dictionary can be created
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void CanConstructNewDictionary() {
|
||||||
public void CanConstructNewDictionary() {
|
var dictionary = new MultiDictionary<int, string>();
|
||||||
var dictionary = new MultiDictionary<int, string>();
|
Assert.IsNotNull(dictionary); // nonsense, prevents compiler warning
|
||||||
Assert.IsNotNull(dictionary); // nonsense, prevents compiler warning
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Verifies that the count is initialized correctly when building
|
||||||
/// Verifies that the count is initialized correctly when building
|
/// a multi dictionary from a dictionary of value collections.
|
||||||
/// a multi dictionary from a dictionary of value collections.
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void CountIsCalculatedIfInitializedFromDictionary() {
|
||||||
public void CountIsCalculatedIfInitializedFromDictionary() {
|
var contents = new Dictionary<int, ICollection<string>>();
|
||||||
var contents = new Dictionary<int, ICollection<string>>();
|
contents.Add(1, new List<string>(new string[] { "one", "eins" }));
|
||||||
contents.Add(1, new List<string>(new string[] { "one", "eins" }));
|
contents.Add(2, new List<string>(new string[] { "two", "zwei" }));
|
||||||
contents.Add(2, new List<string>(new string[] { "two", "zwei" }));
|
|
||||||
|
var multiDictionary = new MultiDictionary<int, string>(contents);
|
||||||
var multiDictionary = new MultiDictionary<int, string>(contents);
|
Assert.AreEqual(4, multiDictionary.Count);
|
||||||
Assert.AreEqual(4, multiDictionary.Count);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Verifies that a new multi dictionary based on a read-only dictionary is
|
||||||
/// Verifies that a new multi dictionary based on a read-only dictionary is
|
/// also read-only
|
||||||
/// also read-only
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void IsReadOnlyWhenBasedOnReadOnlyContainer() {
|
||||||
public void IsReadOnlyWhenBasedOnReadOnlyContainer() {
|
var readOnly = new ReadOnlyDictionary<int, ICollection<string>>(
|
||||||
var readOnly = new ReadOnlyDictionary<int, ICollection<string>>(
|
new Dictionary<int, ICollection<string>>()
|
||||||
new Dictionary<int, ICollection<string>>()
|
);
|
||||||
);
|
var dictionary = new MultiDictionary<int, string>(readOnly);
|
||||||
var dictionary = new MultiDictionary<int, string>(readOnly);
|
|
||||||
|
Assert.IsTrue(dictionary.IsReadOnly);
|
||||||
Assert.IsTrue(dictionary.IsReadOnly);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Ensures that the multi dictionary can contain the same key multiple times
|
||||||
/// Ensures that the multi dictionary can contain the same key multiple times
|
/// (or in other words, multiple values on the same key)
|
||||||
/// (or in other words, multiple values on the same key)
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void CanContainKeyMultipleTimes() {
|
||||||
public void CanContainKeyMultipleTimes() {
|
var dictionary = new MultiDictionary<int, string>();
|
||||||
var dictionary = new MultiDictionary<int, string>();
|
dictionary.Add(123, "one two three");
|
||||||
dictionary.Add(123, "one two three");
|
dictionary.Add(123, "eins zwei drei");
|
||||||
dictionary.Add(123, "eins zwei drei");
|
|
||||||
|
Assert.AreEqual(2, dictionary.Count);
|
||||||
Assert.AreEqual(2, dictionary.Count);
|
|
||||||
|
CollectionAssert.AreEquivalent(
|
||||||
CollectionAssert.AreEquivalent(
|
new KeyValuePair<int, string>[] {
|
||||||
new KeyValuePair<int, string>[] {
|
new KeyValuePair<int, string>(123, "one two three"),
|
||||||
new KeyValuePair<int, string>(123, "one two three"),
|
new KeyValuePair<int, string>(123, "eins zwei drei")
|
||||||
new KeyValuePair<int, string>(123, "eins zwei drei")
|
},
|
||||||
},
|
dictionary
|
||||||
dictionary
|
);
|
||||||
);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Verifies that adding values through the indexer still updates the item count
|
||||||
/// Verifies that adding values through the indexer still updates the item count
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void AddingValuesFromIndexerUpdatesCount() {
|
||||||
public void AddingValuesFromIndexerUpdatesCount() {
|
var dictionary = new MultiDictionary<int, string>();
|
||||||
var dictionary = new MultiDictionary<int, string>();
|
dictionary.Add(42, "the answer to everything");
|
||||||
dictionary.Add(42, "the answer to everything");
|
dictionary[42].Add("21x2");
|
||||||
dictionary[42].Add("21x2");
|
|
||||||
|
Assert.AreEqual(2, dictionary.Count);
|
||||||
Assert.AreEqual(2, dictionary.Count);
|
|
||||||
|
CollectionAssert.AreEquivalent(
|
||||||
CollectionAssert.AreEquivalent(
|
new KeyValuePair<int, string>[] {
|
||||||
new KeyValuePair<int, string>[] {
|
new KeyValuePair<int, string>(42, "the answer to everything"),
|
||||||
new KeyValuePair<int, string>(42, "the answer to everything"),
|
new KeyValuePair<int, string>(42, "21x2")
|
||||||
new KeyValuePair<int, string>(42, "21x2")
|
},
|
||||||
},
|
dictionary
|
||||||
dictionary
|
);
|
||||||
);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Tests whether the collection can count the number of values stored
|
||||||
/// Tests whether the collection can count the number of values stored
|
/// under a key
|
||||||
/// under a key
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void ValuesWithSameKeyCanBeCounted() {
|
||||||
public void ValuesWithSameKeyCanBeCounted() {
|
var dictionary = new MultiDictionary<int, string>();
|
||||||
var dictionary = new MultiDictionary<int, string>();
|
dictionary.Add(10, "ten");
|
||||||
dictionary.Add(10, "ten");
|
dictionary.Add(20, "twenty");
|
||||||
dictionary.Add(20, "twenty");
|
dictionary.Add(30, "thirty");
|
||||||
dictionary.Add(30, "thirty");
|
dictionary.Add(10, "zehn");
|
||||||
dictionary.Add(10, "zehn");
|
dictionary.Add(20, "zwanzig");
|
||||||
dictionary.Add(20, "zwanzig");
|
dictionary.Add(10, "dix");
|
||||||
dictionary.Add(10, "dix");
|
|
||||||
|
Assert.AreEqual(6, dictionary.Count);
|
||||||
Assert.AreEqual(6, dictionary.Count);
|
Assert.AreEqual(3, dictionary.CountValues(10));
|
||||||
Assert.AreEqual(3, dictionary.CountValues(10));
|
Assert.AreEqual(2, dictionary.CountValues(20));
|
||||||
Assert.AreEqual(2, dictionary.CountValues(20));
|
Assert.AreEqual(1, dictionary.CountValues(30));
|
||||||
Assert.AreEqual(1, dictionary.CountValues(30));
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Verifies that counting the values of a non-existing key returns 0
|
||||||
/// Verifies that counting the values of a non-existing key returns 0
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void CountingValuesOfNonExistentKeyReturnsNull() {
|
||||||
public void CountingValuesOfNonExistentKeyReturnsNull() {
|
var dictionary = new MultiDictionary<int, string>();
|
||||||
var dictionary = new MultiDictionary<int, string>();
|
Assert.AreEqual(0, dictionary.CountValues(1));
|
||||||
Assert.AreEqual(0, dictionary.CountValues(1));
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Ensures that its possible to remove values individually without affecting
|
||||||
/// Ensures that its possible to remove values individually without affecting
|
/// other values stored under the same key
|
||||||
/// other values stored under the same key
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void ValuesCanBeRemovedIndividually() {
|
||||||
public void ValuesCanBeRemovedIndividually() {
|
var dictionary = new MultiDictionary<int, string>();
|
||||||
var dictionary = new MultiDictionary<int, string>();
|
dictionary.Add(10, "ten");
|
||||||
dictionary.Add(10, "ten");
|
dictionary.Add(10, "zehn");
|
||||||
dictionary.Add(10, "zehn");
|
dictionary.Add(10, "dix");
|
||||||
dictionary.Add(10, "dix");
|
|
||||||
|
dictionary.Remove(10, "zehn");
|
||||||
dictionary.Remove(10, "zehn");
|
|
||||||
|
Assert.AreEqual(2, dictionary.Count);
|
||||||
Assert.AreEqual(2, dictionary.Count);
|
CollectionAssert.AreEquivalent(
|
||||||
CollectionAssert.AreEquivalent(
|
new KeyValuePair<int, string>[] {
|
||||||
new KeyValuePair<int, string>[] {
|
new KeyValuePair<int, string>(10, "ten"),
|
||||||
new KeyValuePair<int, string>(10, "ten"),
|
new KeyValuePair<int, string>(10, "dix")
|
||||||
new KeyValuePair<int, string>(10, "dix")
|
},
|
||||||
},
|
dictionary
|
||||||
dictionary
|
);
|
||||||
);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Verifies that the Count property returns the number of unique keys if it is called
|
||||||
/// Verifies that the Count property returns the number of unique keys if it is called
|
/// on the collection-of-collections interface implemented by the multi dictionary
|
||||||
/// on the collection-of-collections interface implemented by the multi dictionary
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void CollectionOfCollectionCountIsUniqueKeyCount() {
|
||||||
public void CollectionOfCollectionCountIsUniqueKeyCount() {
|
var dictionary = new MultiDictionary<int, string>();
|
||||||
var dictionary = new MultiDictionary<int, string>();
|
dictionary.Add(10, "ten");
|
||||||
dictionary.Add(10, "ten");
|
dictionary.Add(10, "zehn");
|
||||||
dictionary.Add(10, "zehn");
|
|
||||||
|
Assert.AreEqual(2, dictionary.Count);
|
||||||
Assert.AreEqual(2, dictionary.Count);
|
var collectionOfCollections =
|
||||||
var collectionOfCollections =
|
(ICollection<KeyValuePair<int, ICollection<string>>>)dictionary;
|
||||||
(ICollection<KeyValuePair<int, ICollection<string>>>)dictionary;
|
Assert.AreEqual(1, collectionOfCollections.Count);
|
||||||
Assert.AreEqual(1, collectionOfCollections.Count);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Verifies that the multi dictionary can be tested for containment of a specific value
|
||||||
/// Verifies that the multi dictionary can be tested for containment of a specific value
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void ContainmentCanBeTested() {
|
||||||
public void ContainmentCanBeTested() {
|
var dictionary = new MultiDictionary<int, string>();
|
||||||
var dictionary = new MultiDictionary<int, string>();
|
dictionary.Add(10, "ten");
|
||||||
dictionary.Add(10, "ten");
|
dictionary.Add(10, "zehn");
|
||||||
dictionary.Add(10, "zehn");
|
|
||||||
|
Assert.IsTrue(dictionary.Contains(new KeyValuePair<int, string>(10, "ten")));
|
||||||
Assert.IsTrue(dictionary.Contains(new KeyValuePair<int, string>(10, "ten")));
|
Assert.IsTrue(dictionary.Contains(new KeyValuePair<int, string>(10, "zehn")));
|
||||||
Assert.IsTrue(dictionary.Contains(new KeyValuePair<int, string>(10, "zehn")));
|
Assert.IsFalse(dictionary.Contains(new KeyValuePair<int, string>(10, "dix")));
|
||||||
Assert.IsFalse(dictionary.Contains(new KeyValuePair<int, string>(10, "dix")));
|
Assert.IsFalse(dictionary.Contains(new KeyValuePair<int, string>(20, "ten")));
|
||||||
Assert.IsFalse(dictionary.Contains(new KeyValuePair<int, string>(20, "ten")));
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Verifies that the multi dictionary can be tested for containment of a specific key
|
||||||
/// Verifies that the multi dictionary can be tested for containment of a specific key
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void KeyContainmentCanBeTested() {
|
||||||
public void KeyContainmentCanBeTested() {
|
var dictionary = new MultiDictionary<int, string>();
|
||||||
var dictionary = new MultiDictionary<int, string>();
|
dictionary.Add(10, "ten");
|
||||||
dictionary.Add(10, "ten");
|
dictionary.Add(10, "zehn");
|
||||||
dictionary.Add(10, "zehn");
|
|
||||||
|
Assert.IsTrue(dictionary.ContainsKey(10));
|
||||||
Assert.IsTrue(dictionary.ContainsKey(10));
|
Assert.IsFalse(dictionary.ContainsKey(20));
|
||||||
Assert.IsFalse(dictionary.ContainsKey(20));
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Verifies that the key collection can be retrieved from the dictionary
|
||||||
/// Verifies that the key collection can be retrieved from the dictionary
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void KeyCollectionCanBeRetrieved() {
|
||||||
public void KeyCollectionCanBeRetrieved() {
|
var dictionary = new MultiDictionary<int, string>();
|
||||||
var dictionary = new MultiDictionary<int, string>();
|
dictionary.Add(10, "ten");
|
||||||
dictionary.Add(10, "ten");
|
dictionary.Add(10, "zehn");
|
||||||
dictionary.Add(10, "zehn");
|
|
||||||
|
ICollection<int> keys = dictionary.Keys;
|
||||||
ICollection<int> keys = dictionary.Keys;
|
Assert.IsNotNull(keys);
|
||||||
Assert.IsNotNull(keys);
|
Assert.AreEqual(1, keys.Count);
|
||||||
Assert.AreEqual(1, keys.Count);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Verifies that the key collection can be retrieved from the dictionary
|
||||||
/// Verifies that the key collection can be retrieved from the dictionary
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void ValueCollectionCanBeRetrieved() {
|
||||||
public void ValueCollectionCanBeRetrieved() {
|
var dictionary = new MultiDictionary<int, string>();
|
||||||
var dictionary = new MultiDictionary<int, string>();
|
dictionary.Add(10, "ten");
|
||||||
dictionary.Add(10, "ten");
|
dictionary.Add(10, "zehn");
|
||||||
dictionary.Add(10, "zehn");
|
dictionary.Add(20, "twenty");
|
||||||
dictionary.Add(20, "twenty");
|
|
||||||
|
ICollection<string> values = dictionary.Values;
|
||||||
ICollection<string> values = dictionary.Values;
|
Assert.IsNotNull(values);
|
||||||
Assert.IsNotNull(values);
|
Assert.AreEqual(3, values.Count);
|
||||||
Assert.AreEqual(3, values.Count);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Verifies that TryGetValue() returns false and doesn't throw if a key
|
||||||
/// Verifies that TryGetValue() returns false and doesn't throw if a key
|
/// is not found in the collection
|
||||||
/// is not found in the collection
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void TryGetValueReturnsFalseOnMissingKey() {
|
||||||
public void TryGetValueReturnsFalseOnMissingKey() {
|
var dictionary = new MultiDictionary<int, string>();
|
||||||
var dictionary = new MultiDictionary<int, string>();
|
ICollection<string> values;
|
||||||
ICollection<string> values;
|
Assert.IsFalse(dictionary.TryGetValue(123, out values));
|
||||||
Assert.IsFalse(dictionary.TryGetValue(123, out values));
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Verifies that keys can be looked up via TryGetValue()</summary>
|
||||||
/// <summary>Verifies that keys can be looked up via TryGetValue()</summary>
|
[Test]
|
||||||
[Test]
|
public void TryGetValueCanLookUpValues() {
|
||||||
public void TryGetValueCanLookUpValues() {
|
var dictionary = new MultiDictionary<int, string>();
|
||||||
var dictionary = new MultiDictionary<int, string>();
|
dictionary.Add(10, "ten");
|
||||||
dictionary.Add(10, "ten");
|
dictionary.Add(10, "zehn");
|
||||||
dictionary.Add(10, "zehn");
|
ICollection<string> values;
|
||||||
ICollection<string> values;
|
Assert.IsTrue(dictionary.TryGetValue(10, out values));
|
||||||
Assert.IsTrue(dictionary.TryGetValue(10, out values));
|
Assert.AreEqual(2, values.Count);
|
||||||
Assert.AreEqual(2, values.Count);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Verifies that assigning null to a key deletes all the values stored
|
||||||
/// Verifies that assigning null to a key deletes all the values stored
|
/// under it
|
||||||
/// under it
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void AssigningNullToKeyRemovesAllValues() {
|
||||||
public void AssigningNullToKeyRemovesAllValues() {
|
var dictionary = new MultiDictionary<int, string>();
|
||||||
var dictionary = new MultiDictionary<int, string>();
|
dictionary.Add(10, "ten");
|
||||||
dictionary.Add(10, "ten");
|
dictionary.Add(10, "zehn");
|
||||||
dictionary.Add(10, "zehn");
|
dictionary.Add(20, "twenty");
|
||||||
dictionary.Add(20, "twenty");
|
|
||||||
|
Assert.AreEqual(3, dictionary.Count);
|
||||||
Assert.AreEqual(3, dictionary.Count);
|
dictionary[10] = null;
|
||||||
dictionary[10] = null;
|
Assert.AreEqual(1, dictionary.Count);
|
||||||
Assert.AreEqual(1, dictionary.Count);
|
Assert.IsFalse(dictionary.ContainsKey(10));
|
||||||
Assert.IsFalse(dictionary.ContainsKey(10));
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Verifies that assigning null to a key deletes all the values stored
|
||||||
/// Verifies that assigning null to a key deletes all the values stored
|
/// under it
|
||||||
/// under it
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void ValueListCanBeAssignedToNewKey() {
|
||||||
public void ValueListCanBeAssignedToNewKey() {
|
var dictionary = new MultiDictionary<int, string>();
|
||||||
var dictionary = new MultiDictionary<int, string>();
|
dictionary[3] = new List<string>() { "three", "drei" };
|
||||||
dictionary[3] = new List<string>() { "three", "drei" };
|
|
||||||
|
Assert.AreEqual(2, dictionary.Count);
|
||||||
Assert.AreEqual(2, dictionary.Count);
|
Assert.IsTrue(dictionary.Contains(new KeyValuePair<int, string>(3, "three")));
|
||||||
Assert.IsTrue(dictionary.Contains(new KeyValuePair<int, string>(3, "three")));
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Verifies that assigning null to a key deletes all the values stored
|
||||||
/// Verifies that assigning null to a key deletes all the values stored
|
/// under it
|
||||||
/// under it
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void ValueListCanOverwriteExistingKey() {
|
||||||
public void ValueListCanOverwriteExistingKey() {
|
var dictionary = new MultiDictionary<int, string>();
|
||||||
var dictionary = new MultiDictionary<int, string>();
|
dictionary.Add(10, "dix");
|
||||||
dictionary.Add(10, "dix");
|
|
||||||
|
Assert.AreEqual(1, dictionary.Count);
|
||||||
Assert.AreEqual(1, dictionary.Count);
|
|
||||||
|
dictionary[10] = new List<string>() { "ten", "zehn" };
|
||||||
dictionary[10] = new List<string>() { "ten", "zehn" };
|
|
||||||
|
Assert.AreEqual(2, dictionary.Count);
|
||||||
Assert.AreEqual(2, dictionary.Count);
|
Assert.IsFalse(dictionary.Contains(new KeyValuePair<int, string>(10, "dix")));
|
||||||
Assert.IsFalse(dictionary.Contains(new KeyValuePair<int, string>(10, "dix")));
|
Assert.IsTrue(dictionary.Contains(new KeyValuePair<int, string>(10, "ten")));
|
||||||
Assert.IsTrue(dictionary.Contains(new KeyValuePair<int, string>(10, "ten")));
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Verifies that nothing bad happens when a key is removed from the dictionary
|
||||||
/// Verifies that nothing bad happens when a key is removed from the dictionary
|
/// that it doesn't contain
|
||||||
/// that it doesn't contain
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void NonExistingKeyCanBeRemoved() {
|
||||||
public void NonExistingKeyCanBeRemoved() {
|
var dictionary = new MultiDictionary<int, string>();
|
||||||
var dictionary = new MultiDictionary<int, string>();
|
Assert.AreEqual(0, dictionary.RemoveKey(123));
|
||||||
Assert.AreEqual(0, dictionary.RemoveKey(123));
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Verifies that the remove method returns the number of values that have
|
||||||
/// Verifies that the remove method returns the number of values that have
|
/// been removed from the dictionary
|
||||||
/// been removed from the dictionary
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void RemoveReturnsNumberOfValuesRemoved() {
|
||||||
public void RemoveReturnsNumberOfValuesRemoved() {
|
var dictionary = new MultiDictionary<int, string>();
|
||||||
var dictionary = new MultiDictionary<int, string>();
|
dictionary.Add(10, "ten");
|
||||||
dictionary.Add(10, "ten");
|
dictionary.Add(10, "zehn");
|
||||||
dictionary.Add(10, "zehn");
|
Assert.AreEqual(2, dictionary.RemoveKey(10));
|
||||||
Assert.AreEqual(2, dictionary.RemoveKey(10));
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Verifies that the dictionary becomes empty after clearing it
|
||||||
/// Verifies that the dictionary becomes empty after clearing it
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void DictionaryIsEmptyAfterClear() {
|
||||||
public void DictionaryIsEmptyAfterClear() {
|
var dictionary = new MultiDictionary<int, string>();
|
||||||
var dictionary = new MultiDictionary<int, string>();
|
dictionary.Add(10, "ten");
|
||||||
dictionary.Add(10, "ten");
|
dictionary.Add(10, "zehn");
|
||||||
dictionary.Add(10, "zehn");
|
dictionary.Add(20, "twenty");
|
||||||
dictionary.Add(20, "twenty");
|
Assert.AreEqual(3, dictionary.Count);
|
||||||
Assert.AreEqual(3, dictionary.Count);
|
dictionary.Clear();
|
||||||
dictionary.Clear();
|
Assert.AreEqual(0, dictionary.Count);
|
||||||
Assert.AreEqual(0, dictionary.Count);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Verifies that non-existing values can be removed from the dictionary
|
||||||
/// Verifies that non-existing values can be removed from the dictionary
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void NonExistingValueCanBeRemoved() {
|
||||||
public void NonExistingValueCanBeRemoved() {
|
var dictionary = new MultiDictionary<int, string>();
|
||||||
var dictionary = new MultiDictionary<int, string>();
|
Assert.IsFalse(dictionary.Remove(123, "test"));
|
||||||
Assert.IsFalse(dictionary.Remove(123, "test"));
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Verifies that nothing bad happens when the last value under a key is removed
|
||||||
/// Verifies that nothing bad happens when the last value under a key is removed
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void LastValueOfKeyCanBeRemoved() {
|
||||||
public void LastValueOfKeyCanBeRemoved() {
|
var dictionary = new MultiDictionary<int, string>();
|
||||||
var dictionary = new MultiDictionary<int, string>();
|
dictionary.Add(123, "test");
|
||||||
dictionary.Add(123, "test");
|
dictionary.Remove(123, "test");
|
||||||
dictionary.Remove(123, "test");
|
Assert.AreEqual(0, dictionary.CountValues(123));
|
||||||
Assert.AreEqual(0, dictionary.CountValues(123));
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Verifies that the dictionary can be copied into an array
|
||||||
/// Verifies that the dictionary can be copied into an array
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void DictionaryCanBeCopiedIntoArray() {
|
||||||
public void DictionaryCanBeCopiedIntoArray() {
|
var expected = new List<KeyValuePair<int, string>>() {
|
||||||
var expected = new List<KeyValuePair<int, string>>() {
|
new KeyValuePair<int, string>(1, "one"),
|
||||||
new KeyValuePair<int, string>(1, "one"),
|
new KeyValuePair<int, string>(1, "eins"),
|
||||||
new KeyValuePair<int, string>(1, "eins"),
|
new KeyValuePair<int, string>(2, "two"),
|
||||||
new KeyValuePair<int, string>(2, "two"),
|
new KeyValuePair<int, string>(2, "zwei")
|
||||||
new KeyValuePair<int, string>(2, "zwei")
|
};
|
||||||
};
|
|
||||||
|
var dictionary = new MultiDictionary<int, string>();
|
||||||
var dictionary = new MultiDictionary<int, string>();
|
foreach(KeyValuePair<int, string> entry in expected) {
|
||||||
foreach(KeyValuePair<int, string> entry in expected) {
|
dictionary.Add(entry.Key, entry.Value);
|
||||||
dictionary.Add(entry.Key, entry.Value);
|
}
|
||||||
}
|
|
||||||
|
var actual = new KeyValuePair<int, string>[4];
|
||||||
var actual = new KeyValuePair<int, string>[4];
|
dictionary.CopyTo(actual, 0);
|
||||||
dictionary.CopyTo(actual, 0);
|
|
||||||
|
CollectionAssert.AreEquivalent(expected, actual);
|
||||||
CollectionAssert.AreEquivalent(expected, actual);
|
}
|
||||||
}
|
|
||||||
|
}
|
||||||
}
|
|
||||||
|
} // namespace Nuclex.Support.Collections
|
||||||
} // namespace Nuclex.Support.Collections
|
|
||||||
|
#endif // UNITTEST
|
||||||
#endif // UNITTEST
|
|
||||||
|
|
|
@ -1,269 +1,268 @@
|
||||||
#region CPL License
|
#region Apache License 2.0
|
||||||
/*
|
/*
|
||||||
Nuclex Framework
|
Nuclex .NET Framework
|
||||||
Copyright (C) 2002-2017 Nuclex Development Labs
|
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
|
||||||
|
|
||||||
This library is free software; you can redistribute it and/or
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
modify it under the terms of the IBM Common Public License as
|
you may not use this file except in compliance with the License.
|
||||||
published by the IBM Corporation; either version 1.0 of the
|
You may obtain a copy of the License at
|
||||||
License, or (at your option) any later version.
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
This library is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
Unless required by applicable law or agreed to in writing, software
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
IBM Common Public License for more details.
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
You should have received a copy of the IBM Common Public
|
limitations under the License.
|
||||||
License along with this library
|
*/
|
||||||
*/
|
#endregion // Apache License 2.0
|
||||||
#endregion
|
|
||||||
|
using System;
|
||||||
using System;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Generic;
|
using System.Collections;
|
||||||
using System.Collections;
|
|
||||||
|
namespace Nuclex.Support.Collections {
|
||||||
namespace Nuclex.Support.Collections {
|
|
||||||
|
partial class MultiDictionary<TKey, TValue> {
|
||||||
partial class MultiDictionary<TKey, TValue> {
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Provides access to the values stored in a multi dictionary as a collection
|
||||||
/// Provides access to the values stored in a multi dictionary as a collection
|
/// </summary>
|
||||||
/// </summary>
|
private class ValueCollection : ICollection<TValue>, ICollection {
|
||||||
private class ValueCollection : ICollection<TValue>, ICollection {
|
|
||||||
|
#region class Enumerator
|
||||||
#region class Enumerator
|
|
||||||
|
/// <summary>Enumerates the values stored in a multi dictionary</summary>
|
||||||
/// <summary>Enumerates the values stored in a multi dictionary</summary>
|
private class Enumerator : IEnumerator<TValue> {
|
||||||
private class Enumerator : IEnumerator<TValue> {
|
|
||||||
|
/// <summary>Initializes a new enumerator</summary>
|
||||||
/// <summary>Initializes a new enumerator</summary>
|
/// <param name="valueCollections">Value collections being enumerated</param>
|
||||||
/// <param name="valueCollections">Value collections being enumerated</param>
|
public Enumerator(ICollection<ICollection<TValue>> valueCollections) {
|
||||||
public Enumerator(ICollection<ICollection<TValue>> valueCollections) {
|
this.valueCollections = valueCollections;
|
||||||
this.valueCollections = valueCollections;
|
|
||||||
|
Reset();
|
||||||
Reset();
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Immediately releases all resources owned by the instance</summary>
|
||||||
/// <summary>Immediately releases all resources owned by the instance</summary>
|
public void Dispose() {
|
||||||
public void Dispose() {
|
if(this.currentValue != null) {
|
||||||
if(this.currentValue != null) {
|
this.currentValue.Dispose();
|
||||||
this.currentValue.Dispose();
|
this.currentValue = null;
|
||||||
this.currentValue = null;
|
}
|
||||||
}
|
if(this.currentCollection != null) {
|
||||||
if(this.currentCollection != null) {
|
this.currentCollection.Dispose();
|
||||||
this.currentCollection.Dispose();
|
this.currentCollection = null;
|
||||||
this.currentCollection = null;
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
/// <summary>The current value the enumerator is pointing at</summary>
|
||||||
/// <summary>The current value the enumerator is pointing at</summary>
|
public TValue Current {
|
||||||
public TValue Current {
|
get {
|
||||||
get {
|
if(this.currentValue == null) {
|
||||||
if(this.currentValue == null) {
|
throw new InvalidOperationException("Enumerator is not on a valid position");
|
||||||
throw new InvalidOperationException("Enumerator is not on a valid position");
|
}
|
||||||
}
|
|
||||||
|
return this.currentValue.Current;
|
||||||
return this.currentValue.Current;
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Advances the enumerator to the next item</summary>
|
||||||
/// <summary>Advances the enumerator to the next item</summary>
|
/// <returns>
|
||||||
/// <returns>
|
/// True if there was a next item, false if the enumerator reached the end
|
||||||
/// True if there was a next item, false if the enumerator reached the end
|
/// </returns>
|
||||||
/// </returns>
|
public bool MoveNext() {
|
||||||
public bool MoveNext() {
|
if(this.currentCollection == null) {
|
||||||
if(this.currentCollection == null) {
|
return false;
|
||||||
return false;
|
}
|
||||||
}
|
|
||||||
|
for(; ; ) {
|
||||||
for(; ; ) {
|
|
||||||
|
// Try to move the enumerator in the current key's list to the next item
|
||||||
// Try to move the enumerator in the current key's list to the next item
|
if(this.currentValue != null) {
|
||||||
if(this.currentValue != null) {
|
if(this.currentValue.MoveNext()) {
|
||||||
if(this.currentValue.MoveNext()) {
|
return true; // We found the next item
|
||||||
return true; // We found the next item
|
} else {
|
||||||
} else {
|
this.currentValue.Dispose();
|
||||||
this.currentValue.Dispose();
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
// Enumerator for the current key's list reached the end, go to the next key
|
||||||
// Enumerator for the current key's list reached the end, go to the next key
|
if(this.currentCollection.MoveNext()) {
|
||||||
if(this.currentCollection.MoveNext()) {
|
this.currentValue = this.currentCollection.Current.GetEnumerator();
|
||||||
this.currentValue = this.currentCollection.Current.GetEnumerator();
|
} else {
|
||||||
} else {
|
this.currentValue = null; // Guaranteed to be disposed already
|
||||||
this.currentValue = null; // Guaranteed to be disposed already
|
this.currentCollection.Dispose();
|
||||||
this.currentCollection.Dispose();
|
this.currentCollection = null;
|
||||||
this.currentCollection = null;
|
return false;
|
||||||
return false;
|
}
|
||||||
}
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Resets the enumerator to its initial position</summary>
|
||||||
/// <summary>Resets the enumerator to its initial position</summary>
|
public void Reset() {
|
||||||
public void Reset() {
|
if(this.currentValue != null) {
|
||||||
if(this.currentValue != null) {
|
this.currentValue.Dispose();
|
||||||
this.currentValue.Dispose();
|
this.currentValue = null;
|
||||||
this.currentValue = null;
|
}
|
||||||
}
|
if(this.currentCollection != null) {
|
||||||
if(this.currentCollection != null) {
|
this.currentCollection.Dispose();
|
||||||
this.currentCollection.Dispose();
|
}
|
||||||
}
|
this.currentCollection = valueCollections.GetEnumerator();
|
||||||
this.currentCollection = valueCollections.GetEnumerator();
|
}
|
||||||
}
|
|
||||||
|
#region IEnumerator implementation
|
||||||
#region IEnumerator implementation
|
|
||||||
|
/// <summary>The current entry the enumerator is pointing at</summary>
|
||||||
/// <summary>The current entry the enumerator is pointing at</summary>
|
object IEnumerator.Current {
|
||||||
object IEnumerator.Current {
|
get { return Current; }
|
||||||
get { return Current; }
|
}
|
||||||
}
|
|
||||||
|
#endregion // IEnumerator implementation
|
||||||
#endregion // IEnumerator implementation
|
|
||||||
|
/// <summary>Value collections being enumerated</summary>
|
||||||
/// <summary>Value collections being enumerated</summary>
|
private ICollection<ICollection<TValue>> valueCollections;
|
||||||
private ICollection<ICollection<TValue>> valueCollections;
|
/// <summary>The current value collection the enumerator is in</summary>
|
||||||
/// <summary>The current value collection the enumerator is in</summary>
|
private IEnumerator<ICollection<TValue>> currentCollection;
|
||||||
private IEnumerator<ICollection<TValue>> currentCollection;
|
/// <summary>Current value in the collection the enumerator is in</summary>
|
||||||
/// <summary>Current value in the collection the enumerator is in</summary>
|
private IEnumerator<TValue> currentValue;
|
||||||
private IEnumerator<TValue> currentValue;
|
|
||||||
|
}
|
||||||
}
|
|
||||||
|
#endregion // class Enumerator
|
||||||
#endregion // class Enumerator
|
|
||||||
|
/// <summary>Initializes a new multi dictionary value collection</summary>
|
||||||
/// <summary>Initializes a new multi dictionary value collection</summary>
|
/// <param name="dictionary">Dictionary whose values the collection represents</param>
|
||||||
/// <param name="dictionary">Dictionary whose values the collection represents</param>
|
public ValueCollection(MultiDictionary<TKey, TValue> dictionary) {
|
||||||
public ValueCollection(MultiDictionary<TKey, TValue> dictionary) {
|
this.dictionary = dictionary;
|
||||||
this.dictionary = dictionary;
|
this.dictionaryAsICollection = (ICollection)dictionary;
|
||||||
this.dictionaryAsICollection = (ICollection)dictionary;
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Determines whether the collection contains a specific value</summary>
|
||||||
/// <summary>Determines whether the collection contains a specific value</summary>
|
/// <param name="item">Value for which the collection will be checked</param>
|
||||||
/// <param name="item">Value for which the collection will be checked</param>
|
/// <returns>True if the collection contains the specified value</returns>
|
||||||
/// <returns>True if the collection contains the specified value</returns>
|
public bool Contains(TValue item) {
|
||||||
public bool Contains(TValue item) {
|
foreach(ICollection<TValue> values in this.dictionary.Values) {
|
||||||
foreach(ICollection<TValue> values in this.dictionary.Values) {
|
if(values.Contains(item)) {
|
||||||
if(values.Contains(item)) {
|
return true;
|
||||||
return true;
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
return false;
|
||||||
return false;
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Copies the contents of the collection into an array</summary>
|
||||||
/// <summary>Copies the contents of the collection into an array</summary>
|
/// <param name="array">Array the collection contents will be copied into</param>
|
||||||
/// <param name="array">Array the collection contents will be copied into</param>
|
/// <param name="arrayIndex">
|
||||||
/// <param name="arrayIndex">
|
/// Starting index in the array where writing will begin
|
||||||
/// Starting index in the array where writing will begin
|
/// </param>
|
||||||
/// </param>
|
public void CopyTo(TValue[] array, int arrayIndex) {
|
||||||
public void CopyTo(TValue[] array, int arrayIndex) {
|
foreach(ICollection<TValue> values in this.dictionary.Values) {
|
||||||
foreach(ICollection<TValue> values in this.dictionary.Values) {
|
foreach(TValue value in values) {
|
||||||
foreach(TValue value in values) {
|
array[arrayIndex] = value;
|
||||||
array[arrayIndex] = value;
|
++arrayIndex;
|
||||||
++arrayIndex;
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
/// <summary>The number of values in the collection</summary>
|
||||||
/// <summary>The number of values in the collection</summary>
|
public int Count {
|
||||||
public int Count {
|
get { return this.dictionary.count; }
|
||||||
get { return this.dictionary.count; }
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Always true since the value collection is read-only</summary>
|
||||||
/// <summary>Always true since the value collection is read-only</summary>
|
public bool IsReadOnly {
|
||||||
public bool IsReadOnly {
|
get { return true; }
|
||||||
get { return true; }
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Returns a new enumerator for the value collection</summary>
|
||||||
/// <summary>Returns a new enumerator for the value collection</summary>
|
/// <returns>A new enumerator for the value collection</returns>
|
||||||
/// <returns>A new enumerator for the value collection</returns>
|
public IEnumerator<TValue> GetEnumerator() {
|
||||||
public IEnumerator<TValue> GetEnumerator() {
|
return new Enumerator(this.dictionary.typedDictionary.Values);
|
||||||
return new Enumerator(this.dictionary.typedDictionary.Values);
|
}
|
||||||
}
|
|
||||||
|
#region IEnumerator implementation
|
||||||
#region IEnumerator implementation
|
|
||||||
|
/// <summary>Returns a non-typesafe enumerator for the collection</summary>
|
||||||
/// <summary>Returns a non-typesafe enumerator for the collection</summary>
|
/// <returns>The non-typesafe collection enumerator</returns>
|
||||||
/// <returns>The non-typesafe collection enumerator</returns>
|
IEnumerator IEnumerable.GetEnumerator() {
|
||||||
IEnumerator IEnumerable.GetEnumerator() {
|
return GetEnumerator();
|
||||||
return GetEnumerator();
|
}
|
||||||
}
|
|
||||||
|
#endregion // IEnumerator implementation
|
||||||
#endregion // IEnumerator implementation
|
|
||||||
|
#region ICollection<> implementation
|
||||||
#region ICollection<> implementation
|
|
||||||
|
/// <summary>Throws a NotSupportedException</summary>
|
||||||
/// <summary>Throws a NotSupportedException</summary>
|
/// <param name="item">Not used</param>
|
||||||
/// <param name="item">Not used</param>
|
void ICollection<TValue>.Add(TValue item) {
|
||||||
void ICollection<TValue>.Add(TValue item) {
|
throw new NotSupportedException(
|
||||||
throw new NotSupportedException(
|
"Items cannot be added to a dictionary through its values collection"
|
||||||
"Items cannot be added to a dictionary through its values collection"
|
);
|
||||||
);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Throws a NotSupportedException</summary>
|
||||||
/// <summary>Throws a NotSupportedException</summary>
|
void ICollection<TValue>.Clear() {
|
||||||
void ICollection<TValue>.Clear() {
|
throw new NotSupportedException(
|
||||||
throw new NotSupportedException(
|
"The values collection of a dictionary cannot be cleared"
|
||||||
"The values collection of a dictionary cannot be cleared"
|
);
|
||||||
);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Throws a NotSupportedException</summary>
|
||||||
/// <summary>Throws a NotSupportedException</summary>
|
/// <param name="item">Not used</param>
|
||||||
/// <param name="item">Not used</param>
|
/// <returns>Nothing, since the method always throws an exception</returns>
|
||||||
/// <returns>Nothing, since the method always throws an exception</returns>
|
bool ICollection<TValue>.Contains(TValue item) {
|
||||||
bool ICollection<TValue>.Contains(TValue item) {
|
throw new NotImplementedException();
|
||||||
throw new NotImplementedException();
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Not supported</summary>
|
||||||
/// <summary>Not supported</summary>
|
/// <param name="item">Item that will not be removed</param>
|
||||||
/// <param name="item">Item that will not be removed</param>
|
/// <returns>Nothing because the method throws an exception</returns>
|
||||||
/// <returns>Nothing because the method throws an exception</returns>
|
bool ICollection<TValue>.Remove(TValue item) {
|
||||||
bool ICollection<TValue>.Remove(TValue item) {
|
throw new NotSupportedException(
|
||||||
throw new NotSupportedException(
|
"Items cannot be removed from a dictionary through its values collection"
|
||||||
"Items cannot be removed from a dictionary through its values collection"
|
);
|
||||||
);
|
}
|
||||||
}
|
|
||||||
|
#endregion ICollection<> implementation
|
||||||
#endregion ICollection<> implementation
|
|
||||||
|
#region ICollection implementation
|
||||||
#region ICollection implementation
|
|
||||||
|
/// <summary>Copies the contents of the collection into an array</summary>
|
||||||
/// <summary>Copies the contents of the collection into an array</summary>
|
/// <param name="array">Array the collection's contents are copied into</param>
|
||||||
/// <param name="array">Array the collection's contents are copied into</param>
|
/// <param name="arrayIndex">
|
||||||
/// <param name="arrayIndex">
|
/// Starting index in the array where writing will begin
|
||||||
/// Starting index in the array where writing will begin
|
/// </param>
|
||||||
/// </param>
|
void ICollection.CopyTo(Array array, int arrayIndex) {
|
||||||
void ICollection.CopyTo(Array array, int arrayIndex) {
|
foreach(ICollection<TValue> values in this.dictionary.Values) {
|
||||||
foreach(ICollection<TValue> values in this.dictionary.Values) {
|
foreach(TValue value in values) {
|
||||||
foreach(TValue value in values) {
|
array.SetValue(value, arrayIndex);
|
||||||
array.SetValue(value, arrayIndex);
|
++arrayIndex;
|
||||||
++arrayIndex;
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Whether the dictionary is thread-safe</summary>
|
||||||
/// <summary>Whether the dictionary is thread-safe</summary>
|
bool ICollection.IsSynchronized {
|
||||||
bool ICollection.IsSynchronized {
|
get { return this.dictionaryAsICollection.IsSynchronized; }
|
||||||
get { return this.dictionaryAsICollection.IsSynchronized; }
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// The synchronization root used by the dictionary for thread synchronization
|
||||||
/// The synchronization root used by the dictionary for thread synchronization
|
/// </summary>
|
||||||
/// </summary>
|
object ICollection.SyncRoot {
|
||||||
object ICollection.SyncRoot {
|
get { return this.dictionaryAsICollection.SyncRoot; }
|
||||||
get { return this.dictionaryAsICollection.SyncRoot; }
|
}
|
||||||
}
|
|
||||||
|
#endregion // ICollection implementation
|
||||||
#endregion // ICollection implementation
|
|
||||||
|
/// <summary>Dictionary whose values the collection represents</summary>
|
||||||
/// <summary>Dictionary whose values the collection represents</summary>
|
private MultiDictionary<TKey, TValue> dictionary;
|
||||||
private MultiDictionary<TKey, TValue> dictionary;
|
/// <summary>The dictionary under its ICollection interface</summary>
|
||||||
/// <summary>The dictionary under its ICollection interface</summary>
|
private ICollection dictionaryAsICollection;
|
||||||
private ICollection dictionaryAsICollection;
|
|
||||||
|
}
|
||||||
}
|
|
||||||
|
}
|
||||||
}
|
|
||||||
|
} // namespace Nuclex.Support.Collections
|
||||||
} // namespace Nuclex.Support.Collections
|
|
||||||
|
|
|
@ -1,415 +1,414 @@
|
||||||
#region CPL License
|
#region Apache License 2.0
|
||||||
/*
|
/*
|
||||||
Nuclex Framework
|
Nuclex .NET Framework
|
||||||
Copyright (C) 2002-2017 Nuclex Development Labs
|
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
|
||||||
|
|
||||||
This library is free software; you can redistribute it and/or
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
modify it under the terms of the IBM Common Public License as
|
you may not use this file except in compliance with the License.
|
||||||
published by the IBM Corporation; either version 1.0 of the
|
You may obtain a copy of the License at
|
||||||
License, or (at your option) any later version.
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
This library is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
Unless required by applicable law or agreed to in writing, software
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
IBM Common Public License for more details.
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
You should have received a copy of the IBM Common Public
|
limitations under the License.
|
||||||
License along with this library
|
*/
|
||||||
*/
|
#endregion // Apache License 2.0
|
||||||
#endregion
|
|
||||||
|
using System;
|
||||||
using System;
|
using System.Collections;
|
||||||
using System.Collections;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Generic;
|
using System.Collections.ObjectModel;
|
||||||
using System.Collections.ObjectModel;
|
|
||||||
|
namespace Nuclex.Support.Collections {
|
||||||
namespace Nuclex.Support.Collections {
|
|
||||||
|
/// <summary>Dictionary that can contain multiple values under the same key</summary>
|
||||||
/// <summary>Dictionary that can contain multiple values under the same key</summary>
|
/// <typeparam name="TKey">Type of keys used within the dictionary</typeparam>
|
||||||
/// <typeparam name="TKey">Type of keys used within the dictionary</typeparam>
|
/// <typeparam name="TValue">Type of values used within the dictionary</typeparam>
|
||||||
/// <typeparam name="TValue">Type of values used within the dictionary</typeparam>
|
public partial class MultiDictionary<TKey, TValue> : IMultiDictionary<TKey, TValue> {
|
||||||
public partial class MultiDictionary<TKey, TValue> : IMultiDictionary<TKey, TValue> {
|
|
||||||
|
#region class Enumerator
|
||||||
#region class Enumerator
|
|
||||||
|
/// <summary>Enumerates the values stored in a multi dictionary</summary>
|
||||||
/// <summary>Enumerates the values stored in a multi dictionary</summary>
|
private class Enumerator :
|
||||||
private class Enumerator :
|
IDictionaryEnumerator,
|
||||||
IDictionaryEnumerator,
|
IEnumerator<KeyValuePair<TKey, TValue>> {
|
||||||
IEnumerator<KeyValuePair<TKey, TValue>> {
|
|
||||||
|
/// <summary>Initializes a new multi dictionary enumerator</summary>
|
||||||
/// <summary>Initializes a new multi dictionary enumerator</summary>
|
/// <param name="dictionary">Dictionary that will be enumerated</param>
|
||||||
/// <param name="dictionary">Dictionary that will be enumerated</param>
|
public Enumerator(MultiDictionary<TKey, TValue> dictionary) {
|
||||||
public Enumerator(MultiDictionary<TKey, TValue> dictionary) {
|
this.dictionary = dictionary;
|
||||||
this.dictionary = dictionary;
|
|
||||||
|
Reset();
|
||||||
Reset();
|
}
|
||||||
}
|
|
||||||
|
/// <summary>The current entry the enumerator is pointing at</summary>
|
||||||
/// <summary>The current entry the enumerator is pointing at</summary>
|
public KeyValuePair<TKey, TValue> Current {
|
||||||
public KeyValuePair<TKey, TValue> Current {
|
get {
|
||||||
get {
|
if(this.currentValue == null) {
|
||||||
if(this.currentValue == null) {
|
throw new InvalidOperationException("Enumerator is not on a valid position");
|
||||||
throw new InvalidOperationException("Enumerator is not on a valid position");
|
}
|
||||||
}
|
|
||||||
|
return new KeyValuePair<TKey, TValue>(
|
||||||
return new KeyValuePair<TKey, TValue>(
|
this.currentCollection.Current.Key, this.currentValue.Current
|
||||||
this.currentCollection.Current.Key, this.currentValue.Current
|
);
|
||||||
);
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Immediately releases all resources owned by the instance</summary>
|
||||||
/// <summary>Immediately releases all resources owned by the instance</summary>
|
public void Dispose() {
|
||||||
public void Dispose() {
|
if(this.currentValue != null) {
|
||||||
if(this.currentValue != null) {
|
this.currentValue.Dispose();
|
||||||
this.currentValue.Dispose();
|
this.currentValue = null;
|
||||||
this.currentValue = null;
|
}
|
||||||
}
|
if(this.currentCollection != null) {
|
||||||
if(this.currentCollection != null) {
|
this.currentCollection.Dispose();
|
||||||
this.currentCollection.Dispose();
|
this.currentCollection = null;
|
||||||
this.currentCollection = null;
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Advances the enumerator to the entry</summary>
|
||||||
/// <summary>Advances the enumerator to the entry</summary>
|
/// <returns>
|
||||||
/// <returns>
|
/// True if there was a next entry, false if the end of the set has been reached
|
||||||
/// True if there was a next entry, false if the end of the set has been reached
|
/// </returns>
|
||||||
/// </returns>
|
public bool MoveNext() {
|
||||||
public bool MoveNext() {
|
if(this.currentCollection == null) {
|
||||||
if(this.currentCollection == null) {
|
return false;
|
||||||
return false;
|
}
|
||||||
}
|
|
||||||
|
for(; ; ) {
|
||||||
for(; ; ) {
|
|
||||||
|
// Try to move the enumerator in the current key's list to the next item
|
||||||
// Try to move the enumerator in the current key's list to the next item
|
if(this.currentValue != null) {
|
||||||
if(this.currentValue != null) {
|
if(this.currentValue.MoveNext()) {
|
||||||
if(this.currentValue.MoveNext()) {
|
return true; // We found the next item
|
||||||
return true; // We found the next item
|
} else {
|
||||||
} else {
|
this.currentValue.Dispose();
|
||||||
this.currentValue.Dispose();
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
// Enumerator for the current key's list reached the end, go to the next key
|
||||||
// Enumerator for the current key's list reached the end, go to the next key
|
if(this.currentCollection.MoveNext()) {
|
||||||
if(this.currentCollection.MoveNext()) {
|
this.currentValue = this.currentCollection.Current.Value.GetEnumerator();
|
||||||
this.currentValue = this.currentCollection.Current.Value.GetEnumerator();
|
} else {
|
||||||
} else {
|
this.currentValue = null; // Guaranteed to be disposed already
|
||||||
this.currentValue = null; // Guaranteed to be disposed already
|
this.currentCollection.Dispose();
|
||||||
this.currentCollection.Dispose();
|
this.currentCollection = null;
|
||||||
this.currentCollection = null;
|
return false;
|
||||||
return false;
|
}
|
||||||
}
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Resets the enumerator to its initial position</summary>
|
||||||
/// <summary>Resets the enumerator to its initial position</summary>
|
public void Reset() {
|
||||||
public void Reset() {
|
if(this.currentValue != null) {
|
||||||
if(this.currentValue != null) {
|
this.currentValue.Dispose();
|
||||||
this.currentValue.Dispose();
|
this.currentValue = null;
|
||||||
this.currentValue = null;
|
}
|
||||||
}
|
if(this.currentCollection != null) {
|
||||||
if(this.currentCollection != null) {
|
this.currentCollection.Dispose();
|
||||||
this.currentCollection.Dispose();
|
}
|
||||||
}
|
this.currentCollection = this.dictionary.GetEnumerator();
|
||||||
this.currentCollection = this.dictionary.GetEnumerator();
|
}
|
||||||
}
|
|
||||||
|
#region IEnumerator implementation
|
||||||
#region IEnumerator implementation
|
|
||||||
|
/// <summary>The item the enumerator is currently pointing at</summary>
|
||||||
/// <summary>The item the enumerator is currently pointing at</summary>
|
object IEnumerator.Current {
|
||||||
object IEnumerator.Current {
|
get { return Current; }
|
||||||
get { return Current; }
|
}
|
||||||
}
|
|
||||||
|
#endregion // IEnumerator implementation
|
||||||
#endregion // IEnumerator implementation
|
|
||||||
|
#region IDictionaryEnumerator implementation
|
||||||
#region IDictionaryEnumerator implementation
|
|
||||||
|
/// <summary>The current entry the enumerator is pointing to</summary>
|
||||||
/// <summary>The current entry the enumerator is pointing to</summary>
|
DictionaryEntry IDictionaryEnumerator.Entry {
|
||||||
DictionaryEntry IDictionaryEnumerator.Entry {
|
get {
|
||||||
get {
|
enforceEnumeratorOnValidPosition();
|
||||||
enforceEnumeratorOnValidPosition();
|
|
||||||
|
return new DictionaryEntry(
|
||||||
return new DictionaryEntry(
|
this.currentCollection.Current.Key, this.currentValue.Current
|
||||||
this.currentCollection.Current.Key, this.currentValue.Current
|
);
|
||||||
);
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
/// <summary>The current dictionary key</summary>
|
||||||
/// <summary>The current dictionary key</summary>
|
object IDictionaryEnumerator.Key {
|
||||||
object IDictionaryEnumerator.Key {
|
get {
|
||||||
get {
|
enforceEnumeratorOnValidPosition();
|
||||||
enforceEnumeratorOnValidPosition();
|
return this.currentCollection.Current.Key;
|
||||||
return this.currentCollection.Current.Key;
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
/// <summary>The current dictionary value</summary>
|
||||||
/// <summary>The current dictionary value</summary>
|
object IDictionaryEnumerator.Value {
|
||||||
object IDictionaryEnumerator.Value {
|
get {
|
||||||
get {
|
enforceEnumeratorOnValidPosition();
|
||||||
enforceEnumeratorOnValidPosition();
|
return this.currentValue.Current;
|
||||||
return this.currentValue.Current;
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
#endregion // IDictionaryEnumerator implementation
|
||||||
#endregion // IDictionaryEnumerator implementation
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Throws an exception if the enumerator is not on a valid position
|
||||||
/// Throws an exception if the enumerator is not on a valid position
|
/// </summary>
|
||||||
/// </summary>
|
private void enforceEnumeratorOnValidPosition() {
|
||||||
private void enforceEnumeratorOnValidPosition() {
|
if(this.currentValue == null) {
|
||||||
if(this.currentValue == null) {
|
throw new InvalidOperationException("Enumerator is not on a valid position");
|
||||||
throw new InvalidOperationException("Enumerator is not on a valid position");
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Dictionary over whose entries the enumerator is enumerating</summary>
|
||||||
/// <summary>Dictionary over whose entries the enumerator is enumerating</summary>
|
private IDictionary<TKey, ICollection<TValue>> dictionary;
|
||||||
private IDictionary<TKey, ICollection<TValue>> dictionary;
|
/// <summary>Current key the enumerator is at</summary>
|
||||||
/// <summary>Current key the enumerator is at</summary>
|
private IEnumerator<KeyValuePair<TKey, ICollection<TValue>>> currentCollection;
|
||||||
private IEnumerator<KeyValuePair<TKey, ICollection<TValue>>> currentCollection;
|
/// <summary>Current value in the current key the enumerator is at</summary>
|
||||||
/// <summary>Current value in the current key the enumerator is at</summary>
|
private IEnumerator<TValue> currentValue;
|
||||||
private IEnumerator<TValue> currentValue;
|
|
||||||
|
}
|
||||||
}
|
|
||||||
|
#endregion // class Enumerator
|
||||||
#endregion // class Enumerator
|
|
||||||
|
#region class ValueList
|
||||||
#region class ValueList
|
|
||||||
|
/// <summary>Stores the list of values for a dictionary key</summary>
|
||||||
/// <summary>Stores the list of values for a dictionary key</summary>
|
private class ValueList : Collection<TValue> {
|
||||||
private class ValueList : Collection<TValue> {
|
|
||||||
|
/// <summary>Initializes a new value list</summary>
|
||||||
/// <summary>Initializes a new value list</summary>
|
/// <param name="dictionary">Dictionary the value list belongs to</param>
|
||||||
/// <param name="dictionary">Dictionary the value list belongs to</param>
|
public ValueList(MultiDictionary<TKey, TValue> dictionary) {
|
||||||
public ValueList(MultiDictionary<TKey, TValue> dictionary) {
|
this.dictionary = dictionary;
|
||||||
this.dictionary = dictionary;
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Called when the value list is being cleared</summary>
|
||||||
/// <summary>Called when the value list is being cleared</summary>
|
protected override void ClearItems() {
|
||||||
protected override void ClearItems() {
|
this.dictionary.count -= Count;
|
||||||
this.dictionary.count -= Count;
|
base.ClearItems();
|
||||||
base.ClearItems();
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Called when an item is inserted into the value list</summary>
|
||||||
/// <summary>Called when an item is inserted into the value list</summary>
|
/// <param name="index">Index at which the item is being inserted</param>
|
||||||
/// <param name="index">Index at which the item is being inserted</param>
|
/// <param name="item">Item that is being inserted</param>
|
||||||
/// <param name="item">Item that is being inserted</param>
|
protected override void InsertItem(int index, TValue item) {
|
||||||
protected override void InsertItem(int index, TValue item) {
|
base.InsertItem(index, item);
|
||||||
base.InsertItem(index, item);
|
++this.dictionary.count;
|
||||||
++this.dictionary.count;
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Called when an item is removed from the value list</summary>
|
||||||
/// <summary>Called when an item is removed from the value list</summary>
|
/// <param name="index">Index at which the item is being removed</param>
|
||||||
/// <param name="index">Index at which the item is being removed</param>
|
protected override void RemoveItem(int index) {
|
||||||
protected override void RemoveItem(int index) {
|
base.RemoveItem(index);
|
||||||
base.RemoveItem(index);
|
--this.dictionary.count;
|
||||||
--this.dictionary.count;
|
}
|
||||||
}
|
|
||||||
|
/// <summary>The dictionary the value list belongs to</summary>
|
||||||
/// <summary>The dictionary the value list belongs to</summary>
|
private MultiDictionary<TKey, TValue> dictionary;
|
||||||
private MultiDictionary<TKey, TValue> dictionary;
|
|
||||||
|
}
|
||||||
}
|
|
||||||
|
#endregion // class ValueList
|
||||||
#endregion // class ValueList
|
|
||||||
|
/// <summary>Initializes a new multi dictionary</summary>
|
||||||
/// <summary>Initializes a new multi dictionary</summary>
|
public MultiDictionary() : this(new Dictionary<TKey, ICollection<TValue>>()) { }
|
||||||
public MultiDictionary() : this(new Dictionary<TKey, ICollection<TValue>>()) { }
|
|
||||||
|
/// <summary>Initializes a new multi dictionary</summary>
|
||||||
/// <summary>Initializes a new multi dictionary</summary>
|
/// <param name="dictionary">Dictionary the multi dictionary will be based on</param>
|
||||||
/// <param name="dictionary">Dictionary the multi dictionary will be based on</param>
|
internal MultiDictionary(IDictionary<TKey, ICollection<TValue>> dictionary) {
|
||||||
internal MultiDictionary(IDictionary<TKey, ICollection<TValue>> dictionary) {
|
this.typedDictionary = dictionary;
|
||||||
this.typedDictionary = dictionary;
|
this.objectDictionary = (this.typedDictionary as IDictionary);
|
||||||
this.objectDictionary = (this.typedDictionary as IDictionary);
|
|
||||||
|
foreach(ICollection<TValue> values in dictionary.Values) {
|
||||||
foreach(ICollection<TValue> values in dictionary.Values) {
|
this.count += values.Count;
|
||||||
this.count += values.Count;
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Whether the dictionary is write-protected</summary>
|
||||||
/// <summary>Whether the dictionary is write-protected</summary>
|
public bool IsReadOnly {
|
||||||
public bool IsReadOnly {
|
get { return this.typedDictionary.IsReadOnly; }
|
||||||
get { return this.typedDictionary.IsReadOnly; }
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Determines the number of values stored under the specified key</summary>
|
||||||
/// <summary>Determines the number of values stored under the specified key</summary>
|
/// <param name="key">Key whose values will be counted</param>
|
||||||
/// <param name="key">Key whose values will be counted</param>
|
/// <returns>The number of values stored under the specified key</returns>
|
||||||
/// <returns>The number of values stored under the specified key</returns>
|
public int CountValues(TKey key) {
|
||||||
public int CountValues(TKey key) {
|
ICollection<TValue> values;
|
||||||
ICollection<TValue> values;
|
if(this.typedDictionary.TryGetValue(key, out values)) {
|
||||||
if(this.typedDictionary.TryGetValue(key, out values)) {
|
return values.Count;
|
||||||
return values.Count;
|
} else {
|
||||||
} else {
|
return 0;
|
||||||
return 0;
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Determines whether the specified KeyValuePair is contained in the dictionary
|
||||||
/// Determines whether the specified KeyValuePair is contained in the dictionary
|
/// </summary>
|
||||||
/// </summary>
|
/// <param name="item">KeyValuePair that will be checked for</param>
|
||||||
/// <param name="item">KeyValuePair that will be checked for</param>
|
/// <returns>True if the provided KeyValuePair was contained in the dictionary</returns>
|
||||||
/// <returns>True if the provided KeyValuePair was contained in the dictionary</returns>
|
public bool Contains(KeyValuePair<TKey, TValue> item) {
|
||||||
public bool Contains(KeyValuePair<TKey, TValue> item) {
|
ICollection<TValue> values;
|
||||||
ICollection<TValue> values;
|
if(this.typedDictionary.TryGetValue(item.Key, out values)) {
|
||||||
if(this.typedDictionary.TryGetValue(item.Key, out values)) {
|
return values.Contains(item.Value);
|
||||||
return values.Contains(item.Value);
|
} else {
|
||||||
} else {
|
return false;
|
||||||
return false;
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Determines whether the Dictionary contains the specified key</summary>
|
||||||
/// <summary>Determines whether the Dictionary contains the specified key</summary>
|
/// <param name="key">Key that will be checked for</param>
|
||||||
/// <param name="key">Key that will be checked for</param>
|
/// <returns>
|
||||||
/// <returns>
|
/// True if an entry with the specified key was contained in the Dictionary
|
||||||
/// True if an entry with the specified key was contained in the Dictionary
|
/// </returns>
|
||||||
/// </returns>
|
public bool ContainsKey(TKey key) {
|
||||||
public bool ContainsKey(TKey key) {
|
return this.typedDictionary.ContainsKey(key);
|
||||||
return this.typedDictionary.ContainsKey(key);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Copies the contents of the Dictionary into an array</summary>
|
||||||
/// <summary>Copies the contents of the Dictionary into an array</summary>
|
/// <param name="array">Array the Dictionary will be copied into</param>
|
||||||
/// <param name="array">Array the Dictionary will be copied into</param>
|
/// <param name="arrayIndex">
|
||||||
/// <param name="arrayIndex">
|
/// Starting index at which to begin filling the destination array
|
||||||
/// Starting index at which to begin filling the destination array
|
/// </param>
|
||||||
/// </param>
|
public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex) {
|
||||||
public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex) {
|
foreach(KeyValuePair<TKey, ICollection<TValue>> item in this.typedDictionary) {
|
||||||
foreach(KeyValuePair<TKey, ICollection<TValue>> item in this.typedDictionary) {
|
foreach(TValue value in item.Value) {
|
||||||
foreach(TValue value in item.Value) {
|
array[arrayIndex] = new KeyValuePair<TKey, TValue>(item.Key, value);
|
||||||
array[arrayIndex] = new KeyValuePair<TKey, TValue>(item.Key, value);
|
++arrayIndex;
|
||||||
++arrayIndex;
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Number of elements contained in the multi dictionary</summary>
|
||||||
/// <summary>Number of elements contained in the multi dictionary</summary>
|
public int Count {
|
||||||
public int Count {
|
get { return this.count; }
|
||||||
get { return this.count; }
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Creates a new enumerator for the dictionary</summary>
|
||||||
/// <summary>Creates a new enumerator for the dictionary</summary>
|
/// <returns>The new dictionary enumerator</returns>
|
||||||
/// <returns>The new dictionary enumerator</returns>
|
public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator() {
|
||||||
public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator() {
|
return new Enumerator(this);
|
||||||
return new Enumerator(this);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Collection of all keys contained in the dictionary</summary>
|
||||||
/// <summary>Collection of all keys contained in the dictionary</summary>
|
public ICollection<TKey> Keys {
|
||||||
public ICollection<TKey> Keys {
|
get { return this.typedDictionary.Keys; }
|
||||||
get { return this.typedDictionary.Keys; }
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Collection of all values contained in the dictionary</summary>
|
||||||
/// <summary>Collection of all values contained in the dictionary</summary>
|
public ICollection<TValue> Values {
|
||||||
public ICollection<TValue> Values {
|
get {
|
||||||
get {
|
if(this.valueCollection == null) {
|
||||||
if(this.valueCollection == null) {
|
this.valueCollection = new ValueCollection(this);
|
||||||
this.valueCollection = new ValueCollection(this);
|
}
|
||||||
}
|
|
||||||
|
return this.valueCollection;
|
||||||
return this.valueCollection;
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Attempts to retrieve the item with the specified key from the dictionary
|
||||||
/// Attempts to retrieve the item with the specified key from the dictionary
|
/// </summary>
|
||||||
/// </summary>
|
/// <param name="key">Key of the item to attempt to retrieve</param>
|
||||||
/// <param name="key">Key of the item to attempt to retrieve</param>
|
/// <param name="values">
|
||||||
/// <param name="values">
|
/// Output parameter that will receive the values upon successful completion
|
||||||
/// Output parameter that will receive the values upon successful completion
|
/// </param>
|
||||||
/// </param>
|
/// <returns>
|
||||||
/// <returns>
|
/// True if the item was found and has been placed in the output parameter
|
||||||
/// True if the item was found and has been placed in the output parameter
|
/// </returns>
|
||||||
/// </returns>
|
public bool TryGetValue(TKey key, out ICollection<TValue> values) {
|
||||||
public bool TryGetValue(TKey key, out ICollection<TValue> values) {
|
return this.typedDictionary.TryGetValue(key, out values);
|
||||||
return this.typedDictionary.TryGetValue(key, out values);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Accesses an item in the dictionary by its key</summary>
|
||||||
/// <summary>Accesses an item in the dictionary by its key</summary>
|
/// <param name="key">Key of the item that will be accessed</param>
|
||||||
/// <param name="key">Key of the item that will be accessed</param>
|
public ICollection<TValue> this[TKey key] {
|
||||||
public ICollection<TValue> this[TKey key] {
|
get { return this.typedDictionary[key]; }
|
||||||
get { return this.typedDictionary[key]; }
|
set {
|
||||||
set {
|
if(value == null) {
|
||||||
if(value == null) {
|
RemoveKey(key);
|
||||||
RemoveKey(key);
|
} else {
|
||||||
} else {
|
ICollection<TValue> currentValues;
|
||||||
ICollection<TValue> currentValues;
|
if(this.typedDictionary.TryGetValue(key, out currentValues)) {
|
||||||
if(this.typedDictionary.TryGetValue(key, out currentValues)) {
|
currentValues.Clear();
|
||||||
currentValues.Clear();
|
} else {
|
||||||
} else {
|
currentValues = new ValueList(this);
|
||||||
currentValues = new ValueList(this);
|
this.typedDictionary.Add(key, currentValues);
|
||||||
this.typedDictionary.Add(key, currentValues);
|
}
|
||||||
}
|
foreach(TValue addedValue in value) {
|
||||||
foreach(TValue addedValue in value) {
|
currentValues.Add(addedValue);
|
||||||
currentValues.Add(addedValue);
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Inserts an item into the dictionary</summary>
|
||||||
/// <summary>Inserts an item into the dictionary</summary>
|
/// <param name="key">Key under which to add the new item</param>
|
||||||
/// <param name="key">Key under which to add the new item</param>
|
/// <param name="value">Item that will be added to the dictionary</param>
|
||||||
/// <param name="value">Item that will be added to the dictionary</param>
|
public void Add(TKey key, TValue value) {
|
||||||
public void Add(TKey key, TValue value) {
|
ICollection<TValue> values;
|
||||||
ICollection<TValue> values;
|
if(!this.typedDictionary.TryGetValue(key, out values)) {
|
||||||
if(!this.typedDictionary.TryGetValue(key, out values)) {
|
values = new ValueList(this);
|
||||||
values = new ValueList(this);
|
this.typedDictionary.Add(key, values);
|
||||||
this.typedDictionary.Add(key, values);
|
}
|
||||||
}
|
|
||||||
|
values.Add(value);
|
||||||
values.Add(value);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Removes the item with the specified key and value from the dictionary
|
||||||
/// Removes the item with the specified key and value from the dictionary
|
/// </summary>
|
||||||
/// </summary>
|
/// <param name="key">Key of the item that will be removed</param>
|
||||||
/// <param name="key">Key of the item that will be removed</param>
|
/// <param name="value">Value of the item that will be removed</param>
|
||||||
/// <param name="value">Value of the item that will be removed</param>
|
/// <returns>
|
||||||
/// <returns>
|
/// True if the specified item was contained in the dictionary and was removed
|
||||||
/// True if the specified item was contained in the dictionary and was removed
|
/// </returns>
|
||||||
/// </returns>
|
/// <exception cref="NotSupportedException">If the dictionary is read-only</exception>
|
||||||
/// <exception cref="NotSupportedException">If the dictionary is read-only</exception>
|
public bool Remove(TKey key, TValue value) {
|
||||||
public bool Remove(TKey key, TValue value) {
|
ICollection<TValue> values;
|
||||||
ICollection<TValue> values;
|
if(this.typedDictionary.TryGetValue(key, out values)) {
|
||||||
if(this.typedDictionary.TryGetValue(key, out values)) {
|
values.Remove(value);
|
||||||
values.Remove(value);
|
if(values.Count == 0) {
|
||||||
if(values.Count == 0) {
|
this.typedDictionary.Remove(key);
|
||||||
this.typedDictionary.Remove(key);
|
}
|
||||||
}
|
return true;
|
||||||
return true;
|
} else {
|
||||||
} else {
|
return false;
|
||||||
return false;
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Removes all items with the specified key from the dictionary</summary>
|
||||||
/// <summary>Removes all items with the specified key from the dictionary</summary>
|
/// <param name="key">Key of the item that will be removed</param>
|
||||||
/// <param name="key">Key of the item that will be removed</param>
|
/// <returns>The number of items that have been removed from the dictionary</returns>
|
||||||
/// <returns>The number of items that have been removed from the dictionary</returns>
|
/// <exception cref="NotSupportedException">If the dictionary is read-only</exception>
|
||||||
/// <exception cref="NotSupportedException">If the dictionary is read-only</exception>
|
public int RemoveKey(TKey key) {
|
||||||
public int RemoveKey(TKey key) {
|
ICollection<TValue> values;
|
||||||
ICollection<TValue> values;
|
if(this.typedDictionary.TryGetValue(key, out values)) {
|
||||||
if(this.typedDictionary.TryGetValue(key, out values)) {
|
this.count -= values.Count;
|
||||||
this.count -= values.Count;
|
this.typedDictionary.Remove(key);
|
||||||
this.typedDictionary.Remove(key);
|
return values.Count;
|
||||||
return values.Count;
|
} else {
|
||||||
} else {
|
return 0;
|
||||||
return 0;
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Removes all items from the Dictionary</summary>
|
||||||
/// <summary>Removes all items from the Dictionary</summary>
|
public void Clear() {
|
||||||
public void Clear() {
|
this.typedDictionary.Clear();
|
||||||
this.typedDictionary.Clear();
|
this.count = 0;
|
||||||
this.count = 0;
|
}
|
||||||
}
|
|
||||||
|
/// <summary>The wrapped Dictionary under its type-safe interface</summary>
|
||||||
/// <summary>The wrapped Dictionary under its type-safe interface</summary>
|
private IDictionary<TKey, ICollection<TValue>> typedDictionary;
|
||||||
private IDictionary<TKey, ICollection<TValue>> typedDictionary;
|
/// <summary>The wrapped Dictionary under its object interface</summary>
|
||||||
/// <summary>The wrapped Dictionary under its object interface</summary>
|
private IDictionary objectDictionary;
|
||||||
private IDictionary objectDictionary;
|
/// <summary>The number of items currently in the multi dictionary</summary>
|
||||||
/// <summary>The number of items currently in the multi dictionary</summary>
|
private int count;
|
||||||
private int count;
|
/// <summary>Provides the values stores in the dictionary in sequence</summary>
|
||||||
/// <summary>Provides the values stores in the dictionary in sequence</summary>
|
private ValueCollection valueCollection;
|
||||||
private ValueCollection valueCollection;
|
|
||||||
|
}
|
||||||
}
|
|
||||||
|
} // namespace Nuclex.Support.Collections
|
||||||
} // namespace Nuclex.Support.Collections
|
|
||||||
|
|
|
@ -1,145 +1,144 @@
|
||||||
#region CPL License
|
#region Apache License 2.0
|
||||||
/*
|
/*
|
||||||
Nuclex Framework
|
Nuclex .NET Framework
|
||||||
Copyright (C) 2002-2017 Nuclex Development Labs
|
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
|
||||||
|
|
||||||
This library is free software; you can redistribute it and/or
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
modify it under the terms of the IBM Common Public License as
|
you may not use this file except in compliance with the License.
|
||||||
published by the IBM Corporation; either version 1.0 of the
|
You may obtain a copy of the License at
|
||||||
License, or (at your option) any later version.
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
This library is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
Unless required by applicable law or agreed to in writing, software
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
IBM Common Public License for more details.
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
You should have received a copy of the IBM Common Public
|
limitations under the License.
|
||||||
License along with this library
|
*/
|
||||||
*/
|
#endregion // Apache License 2.0
|
||||||
#endregion
|
|
||||||
|
#if !NO_NMOCK
|
||||||
#if !NO_NMOCK
|
|
||||||
|
using System;
|
||||||
using System;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Generic;
|
|
||||||
|
#if UNITTEST
|
||||||
#if UNITTEST
|
|
||||||
|
using NUnit.Framework;
|
||||||
using NUnit.Framework;
|
using NMock;
|
||||||
using NMock;
|
|
||||||
|
namespace Nuclex.Support.Collections {
|
||||||
namespace Nuclex.Support.Collections {
|
|
||||||
|
/// <summary>Unit Test for the observable collection class</summary>
|
||||||
/// <summary>Unit Test for the observable collection class</summary>
|
[TestFixture]
|
||||||
[TestFixture]
|
internal class ObservableCollectionTest {
|
||||||
internal class ObservableCollectionTest {
|
|
||||||
|
#region interface IObservableCollectionSubscriber
|
||||||
#region interface IObservableCollectionSubscriber
|
|
||||||
|
/// <summary>Interface used to test the observable collection</summary>
|
||||||
/// <summary>Interface used to test the observable collection</summary>
|
public interface IObservableCollectionSubscriber {
|
||||||
public interface IObservableCollectionSubscriber {
|
|
||||||
|
/// <summary>Called when the collection is about to clear its contents</summary>
|
||||||
/// <summary>Called when the collection is about to clear its contents</summary>
|
/// <param name="sender">Collection that is clearing its contents</param>
|
||||||
/// <param name="sender">Collection that is clearing its contents</param>
|
/// <param name="arguments">Not used</param>
|
||||||
/// <param name="arguments">Not used</param>
|
void Clearing(object sender, EventArgs arguments);
|
||||||
void Clearing(object sender, EventArgs arguments);
|
|
||||||
|
/// <summary>Called when the collection has been cleared of its contents</summary>
|
||||||
/// <summary>Called when the collection has been cleared of its contents</summary>
|
/// <param name="sender">Collection that was cleared of its contents</param>
|
||||||
/// <param name="sender">Collection that was cleared of its contents</param>
|
/// <param name="arguments">Not used</param>
|
||||||
/// <param name="arguments">Not used</param>
|
void Cleared(object sender, EventArgs arguments);
|
||||||
void Cleared(object sender, EventArgs arguments);
|
|
||||||
|
/// <summary>Called when an item is added to the collection</summary>
|
||||||
/// <summary>Called when an item is added to the collection</summary>
|
/// <param name="sender">Collection to which an item is being added</param>
|
||||||
/// <param name="sender">Collection to which an item is being added</param>
|
/// <param name="arguments">Contains the item that is being added</param>
|
||||||
/// <param name="arguments">Contains the item that is being added</param>
|
void ItemAdded(object sender, ItemEventArgs<int> arguments);
|
||||||
void ItemAdded(object sender, ItemEventArgs<int> arguments);
|
|
||||||
|
/// <summary>Called when an item is removed from the collection</summary>
|
||||||
/// <summary>Called when an item is removed from the collection</summary>
|
/// <param name="sender">Collection from which an item is being removed</param>
|
||||||
/// <param name="sender">Collection from which an item is being removed</param>
|
/// <param name="arguments">Contains the item that is being removed</param>
|
||||||
/// <param name="arguments">Contains the item that is being removed</param>
|
void ItemRemoved(object sender, ItemEventArgs<int> arguments);
|
||||||
void ItemRemoved(object sender, ItemEventArgs<int> arguments);
|
|
||||||
|
}
|
||||||
}
|
|
||||||
|
#endregion // interface IObservableCollectionSubscriber
|
||||||
#endregion // interface IObservableCollectionSubscriber
|
|
||||||
|
/// <summary>Initialization routine executed before each test is run</summary>
|
||||||
/// <summary>Initialization routine executed before each test is run</summary>
|
[SetUp]
|
||||||
[SetUp]
|
public void Setup() {
|
||||||
public void Setup() {
|
this.mockery = new MockFactory();
|
||||||
this.mockery = new MockFactory();
|
|
||||||
|
this.mockedSubscriber = this.mockery.CreateMock<IObservableCollectionSubscriber>();
|
||||||
this.mockedSubscriber = this.mockery.CreateMock<IObservableCollectionSubscriber>();
|
|
||||||
|
this.observedCollection = new ObservableCollection<int>();
|
||||||
this.observedCollection = new ObservableCollection<int>();
|
this.observedCollection.Clearing += new EventHandler(
|
||||||
this.observedCollection.Clearing += new EventHandler(
|
this.mockedSubscriber.MockObject.Clearing
|
||||||
this.mockedSubscriber.MockObject.Clearing
|
);
|
||||||
);
|
this.observedCollection.Cleared += new EventHandler(
|
||||||
this.observedCollection.Cleared += new EventHandler(
|
this.mockedSubscriber.MockObject.Cleared
|
||||||
this.mockedSubscriber.MockObject.Cleared
|
);
|
||||||
);
|
this.observedCollection.ItemAdded += new EventHandler<ItemEventArgs<int>>(
|
||||||
this.observedCollection.ItemAdded += new EventHandler<ItemEventArgs<int>>(
|
this.mockedSubscriber.MockObject.ItemAdded
|
||||||
this.mockedSubscriber.MockObject.ItemAdded
|
);
|
||||||
);
|
this.observedCollection.ItemRemoved += new EventHandler<ItemEventArgs<int>>(
|
||||||
this.observedCollection.ItemRemoved += new EventHandler<ItemEventArgs<int>>(
|
this.mockedSubscriber.MockObject.ItemRemoved
|
||||||
this.mockedSubscriber.MockObject.ItemRemoved
|
);
|
||||||
);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Tests whether the Clearing event is fired</summary>
|
||||||
/// <summary>Tests whether the Clearing event is fired</summary>
|
[Test]
|
||||||
[Test]
|
public void TestClearingEvent() {
|
||||||
public void TestClearingEvent() {
|
this.mockedSubscriber.Expects.One.Method(m => m.Clearing(null, null)).WithAnyArguments();
|
||||||
this.mockedSubscriber.Expects.One.Method(m => m.Clearing(null, null)).WithAnyArguments();
|
this.mockedSubscriber.Expects.One.Method(m => m.Cleared(null, null)).WithAnyArguments();
|
||||||
this.mockedSubscriber.Expects.One.Method(m => m.Cleared(null, null)).WithAnyArguments();
|
this.observedCollection.Clear();
|
||||||
this.observedCollection.Clear();
|
|
||||||
|
this.mockery.VerifyAllExpectationsHaveBeenMet();
|
||||||
this.mockery.VerifyAllExpectationsHaveBeenMet();
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Tests whether the ItemAdded event is fired</summary>
|
||||||
/// <summary>Tests whether the ItemAdded event is fired</summary>
|
[Test]
|
||||||
[Test]
|
public void TestItemAddedEvent() {
|
||||||
public void TestItemAddedEvent() {
|
this.mockedSubscriber.Expects.One.Method(m => m.ItemAdded(null, null)).WithAnyArguments();
|
||||||
this.mockedSubscriber.Expects.One.Method(m => m.ItemAdded(null, null)).WithAnyArguments();
|
|
||||||
|
this.observedCollection.Add(123);
|
||||||
this.observedCollection.Add(123);
|
|
||||||
|
this.mockery.VerifyAllExpectationsHaveBeenMet();
|
||||||
this.mockery.VerifyAllExpectationsHaveBeenMet();
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Tests whether the ItemRemoved event is fired</summary>
|
||||||
/// <summary>Tests whether the ItemRemoved event is fired</summary>
|
[Test]
|
||||||
[Test]
|
public void TestItemRemovedEvent() {
|
||||||
public void TestItemRemovedEvent() {
|
this.mockedSubscriber.Expects.One.Method(m => m.ItemAdded(null, null)).WithAnyArguments();
|
||||||
this.mockedSubscriber.Expects.One.Method(m => m.ItemAdded(null, null)).WithAnyArguments();
|
|
||||||
|
this.observedCollection.Add(123);
|
||||||
this.observedCollection.Add(123);
|
|
||||||
|
this.mockedSubscriber.Expects.One.Method(m => m.ItemRemoved(null, null)).WithAnyArguments();
|
||||||
this.mockedSubscriber.Expects.One.Method(m => m.ItemRemoved(null, null)).WithAnyArguments();
|
|
||||||
|
this.observedCollection.Remove(123);
|
||||||
this.observedCollection.Remove(123);
|
|
||||||
|
this.mockery.VerifyAllExpectationsHaveBeenMet();
|
||||||
this.mockery.VerifyAllExpectationsHaveBeenMet();
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Tests whether a the list constructor is working</summary>
|
||||||
/// <summary>Tests whether a the list constructor is working</summary>
|
[Test]
|
||||||
[Test]
|
public void TestListConstructor() {
|
||||||
public void TestListConstructor() {
|
int[] integers = new int[] { 12, 34, 56, 78 };
|
||||||
int[] integers = new int[] { 12, 34, 56, 78 };
|
|
||||||
|
var testCollection = new ObservableCollection<int>(integers);
|
||||||
var testCollection = new ObservableCollection<int>(integers);
|
|
||||||
|
CollectionAssert.AreEqual(integers, testCollection);
|
||||||
CollectionAssert.AreEqual(integers, testCollection);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Mock object factory</summary>
|
||||||
/// <summary>Mock object factory</summary>
|
private MockFactory mockery;
|
||||||
private MockFactory mockery;
|
/// <summary>The mocked observable collection subscriber</summary>
|
||||||
/// <summary>The mocked observable collection subscriber</summary>
|
private Mock<IObservableCollectionSubscriber> mockedSubscriber;
|
||||||
private Mock<IObservableCollectionSubscriber> mockedSubscriber;
|
/// <summary>An observable collection to which a mock will be subscribed</summary>
|
||||||
/// <summary>An observable collection to which a mock will be subscribed</summary>
|
private ObservableCollection<int> observedCollection;
|
||||||
private ObservableCollection<int> observedCollection;
|
|
||||||
|
}
|
||||||
}
|
|
||||||
|
} // namespace Nuclex.Support.Collections
|
||||||
} // namespace Nuclex.Support.Collections
|
|
||||||
|
#endif // UNITTEST
|
||||||
#endif // UNITTEST
|
|
||||||
|
#endif // !NO_NMOCK
|
||||||
#endif // !NO_NMOCK
|
|
||||||
|
|
|
@ -1,231 +1,230 @@
|
||||||
#region CPL License
|
#region Apache License 2.0
|
||||||
/*
|
/*
|
||||||
Nuclex Framework
|
Nuclex .NET Framework
|
||||||
Copyright (C) 2002-2017 Nuclex Development Labs
|
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
|
||||||
|
|
||||||
This library is free software; you can redistribute it and/or
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
modify it under the terms of the IBM Common Public License as
|
you may not use this file except in compliance with the License.
|
||||||
published by the IBM Corporation; either version 1.0 of the
|
You may obtain a copy of the License at
|
||||||
License, or (at your option) any later version.
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
This library is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
Unless required by applicable law or agreed to in writing, software
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
IBM Common Public License for more details.
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
You should have received a copy of the IBM Common Public
|
limitations under the License.
|
||||||
License along with this library
|
*/
|
||||||
*/
|
#endregion // Apache License 2.0
|
||||||
#endregion
|
|
||||||
|
using System;
|
||||||
using System;
|
using System.Collections;
|
||||||
using System.Collections;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Generic;
|
using System.Collections.ObjectModel;
|
||||||
using System.Collections.ObjectModel;
|
#if !NO_SPECIALIZED_COLLECTIONS
|
||||||
#if !NO_SPECIALIZED_COLLECTIONS
|
using System.Collections.Specialized;
|
||||||
using System.Collections.Specialized;
|
#endif
|
||||||
#endif
|
|
||||||
|
namespace Nuclex.Support.Collections {
|
||||||
namespace Nuclex.Support.Collections {
|
|
||||||
|
/// <summary>Collection which fires events when items are added or removed</summary>
|
||||||
/// <summary>Collection which fires events when items are added or removed</summary>
|
/// <typeparam name="TItem">Type of items the collection manages</typeparam>
|
||||||
/// <typeparam name="TItem">Type of items the collection manages</typeparam>
|
public class ObservableCollection<TItem> :
|
||||||
public class ObservableCollection<TItem> :
|
ICollection<TItem>,
|
||||||
ICollection<TItem>,
|
ICollection,
|
||||||
ICollection,
|
#if !NO_SPECIALIZED_COLLECTIONS
|
||||||
#if !NO_SPECIALIZED_COLLECTIONS
|
INotifyCollectionChanged,
|
||||||
INotifyCollectionChanged,
|
#endif
|
||||||
#endif
|
IObservableCollection<TItem> {
|
||||||
IObservableCollection<TItem> {
|
|
||||||
|
/// <summary>Raised when an item has been added to the collection</summary>
|
||||||
/// <summary>Raised when an item has been added to the collection</summary>
|
public event EventHandler<ItemEventArgs<TItem>> ItemAdded;
|
||||||
public event EventHandler<ItemEventArgs<TItem>> ItemAdded;
|
/// <summary>Raised when an item is removed from the collection</summary>
|
||||||
/// <summary>Raised when an item is removed from the collection</summary>
|
public event EventHandler<ItemEventArgs<TItem>> ItemRemoved;
|
||||||
public event EventHandler<ItemEventArgs<TItem>> ItemRemoved;
|
/// <summary>Raised when an item is replaced in the collection</summary>
|
||||||
/// <summary>Raised when an item is replaced in the collection</summary>
|
public event EventHandler<ItemReplaceEventArgs<TItem>> ItemReplaced {
|
||||||
public event EventHandler<ItemReplaceEventArgs<TItem>> ItemReplaced {
|
add { }
|
||||||
add { }
|
remove { }
|
||||||
remove { }
|
}
|
||||||
}
|
/// <summary>Raised when the collection is about to be cleared</summary>
|
||||||
/// <summary>Raised when the collection is about to be cleared</summary>
|
/// <remarks>
|
||||||
/// <remarks>
|
/// This could be covered by calling ItemRemoved for each item currently
|
||||||
/// This could be covered by calling ItemRemoved for each item currently
|
/// contained in the collection, but it is often simpler and more efficient
|
||||||
/// contained in the collection, but it is often simpler and more efficient
|
/// to process the clearing of the entire collection as a special operation.
|
||||||
/// to process the clearing of the entire collection as a special operation.
|
/// </remarks>
|
||||||
/// </remarks>
|
public event EventHandler Clearing;
|
||||||
public event EventHandler Clearing;
|
/// <summary>Raised when the collection has been cleared</summary>
|
||||||
/// <summary>Raised when the collection has been cleared</summary>
|
public event EventHandler Cleared;
|
||||||
public event EventHandler Cleared;
|
|
||||||
|
#if !NO_SPECIALIZED_COLLECTIONS
|
||||||
#if !NO_SPECIALIZED_COLLECTIONS
|
/// <summary>Called when the collection has changed</summary>
|
||||||
/// <summary>Called when the collection has changed</summary>
|
public event NotifyCollectionChangedEventHandler CollectionChanged;
|
||||||
public event NotifyCollectionChangedEventHandler CollectionChanged;
|
#endif
|
||||||
#endif
|
|
||||||
|
/// <summary>Initializes a new ObservableCollection with no items</summary>
|
||||||
/// <summary>Initializes a new ObservableCollection with no items</summary>
|
public ObservableCollection() : this(new Collection<TItem>()) { }
|
||||||
public ObservableCollection() : this(new Collection<TItem>()) { }
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Initializes a new ObservableCollection as a wrapper for an existing collection
|
||||||
/// Initializes a new ObservableCollection as a wrapper for an existing collection
|
/// </summary>
|
||||||
/// </summary>
|
/// <param name="collection">Collection that will be wrapped</param>
|
||||||
/// <param name="collection">Collection that will be wrapped</param>
|
/// <exception cref="System.ArgumentNullException">List is null</exception>
|
||||||
/// <exception cref="System.ArgumentNullException">List is null</exception>
|
public ObservableCollection(ICollection<TItem> collection) {
|
||||||
public ObservableCollection(ICollection<TItem> collection) {
|
this.typedCollection = collection;
|
||||||
this.typedCollection = collection;
|
this.objectCollection = (collection as ICollection);
|
||||||
this.objectCollection = (collection as ICollection);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Removes all elements from the Collection</summary>
|
||||||
/// <summary>Removes all elements from the Collection</summary>
|
public void Clear() {
|
||||||
public void Clear() {
|
OnClearing();
|
||||||
OnClearing();
|
this.typedCollection.Clear();
|
||||||
this.typedCollection.Clear();
|
OnCleared();
|
||||||
OnCleared();
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Adds an item to the collection</summary>
|
||||||
/// <summary>Adds an item to the collection</summary>
|
/// <param name="item">Collection an item will be added to</param>
|
||||||
/// <param name="item">Collection an item will be added to</param>
|
public void Add(TItem item) {
|
||||||
public void Add(TItem item) {
|
this.typedCollection.Add(item);
|
||||||
this.typedCollection.Add(item);
|
OnAdded(item);
|
||||||
OnAdded(item);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Determines whether the collection contains the specified item</summary>
|
||||||
/// <summary>Determines whether the collection contains the specified item</summary>
|
/// <param name="item">Item the collection will be searched for</param>
|
||||||
/// <param name="item">Item the collection will be searched for</param>
|
/// <returns>
|
||||||
/// <returns>
|
/// True if the collection contains the specified item, false otherwise
|
||||||
/// True if the collection contains the specified item, false otherwise
|
/// </returns>
|
||||||
/// </returns>
|
public bool Contains(TItem item) {
|
||||||
public bool Contains(TItem item) {
|
return this.typedCollection.Contains(item);
|
||||||
return this.typedCollection.Contains(item);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Copies the contents of the collection into an array</summary>
|
||||||
/// <summary>Copies the contents of the collection into an array</summary>
|
/// <param name="array">Array the collection's contents will be copied into</param>
|
||||||
/// <param name="array">Array the collection's contents will be copied into</param>
|
/// <param name="arrayIndex">
|
||||||
/// <param name="arrayIndex">
|
/// Index in the array where the collection's first item will be placed
|
||||||
/// Index in the array where the collection's first item will be placed
|
/// </param>
|
||||||
/// </param>
|
public void CopyTo(TItem[] array, int arrayIndex) {
|
||||||
public void CopyTo(TItem[] array, int arrayIndex) {
|
this.typedCollection.CopyTo(array, arrayIndex);
|
||||||
this.typedCollection.CopyTo(array, arrayIndex);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>The total number of items currently in the collection</summary>
|
||||||
/// <summary>The total number of items currently in the collection</summary>
|
public int Count {
|
||||||
public int Count {
|
get { return this.typedCollection.Count; }
|
||||||
get { return this.typedCollection.Count; }
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Whether the collection is read-only</summary>
|
||||||
/// <summary>Whether the collection is read-only</summary>
|
public bool IsReadOnly {
|
||||||
public bool IsReadOnly {
|
get { return this.typedCollection.IsReadOnly; }
|
||||||
get { return this.typedCollection.IsReadOnly; }
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Removes an item from the collection</summary>
|
||||||
/// <summary>Removes an item from the collection</summary>
|
/// <param name="item">Item that will be removed from the collection</param>
|
||||||
/// <param name="item">Item that will be removed from the collection</param>
|
/// <returns>True if the item was found and removed, false otherwise</returns>
|
||||||
/// <returns>True if the item was found and removed, false otherwise</returns>
|
public bool Remove(TItem item) {
|
||||||
public bool Remove(TItem item) {
|
bool wasRemoved = this.typedCollection.Remove(item);
|
||||||
bool wasRemoved = this.typedCollection.Remove(item);
|
if(wasRemoved) {
|
||||||
if(wasRemoved) {
|
OnRemoved(item);
|
||||||
OnRemoved(item);
|
}
|
||||||
}
|
|
||||||
|
return wasRemoved;
|
||||||
return wasRemoved;
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Returns an enumerator for the items in the collection</summary>
|
||||||
/// <summary>Returns an enumerator for the items in the collection</summary>
|
/// <returns>An enumeration for the items in the collection</returns>
|
||||||
/// <returns>An enumeration for the items in the collection</returns>
|
public IEnumerator<TItem> GetEnumerator() {
|
||||||
public IEnumerator<TItem> GetEnumerator() {
|
return this.typedCollection.GetEnumerator();
|
||||||
return this.typedCollection.GetEnumerator();
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Fires the 'ItemAdded' event</summary>
|
||||||
/// <summary>Fires the 'ItemAdded' event</summary>
|
/// <param name="item">Item that has been added to the collection</param>
|
||||||
/// <param name="item">Item that has been added to the collection</param>
|
protected virtual void OnAdded(TItem item) {
|
||||||
protected virtual void OnAdded(TItem item) {
|
if(ItemAdded != null) {
|
||||||
if(ItemAdded != null) {
|
ItemAdded(this, new ItemEventArgs<TItem>(item));
|
||||||
ItemAdded(this, new ItemEventArgs<TItem>(item));
|
}
|
||||||
}
|
#if !NO_SPECIALIZED_COLLECTIONS
|
||||||
#if !NO_SPECIALIZED_COLLECTIONS
|
if(CollectionChanged != null) {
|
||||||
if(CollectionChanged != null) {
|
CollectionChanged(
|
||||||
CollectionChanged(
|
this,
|
||||||
this,
|
new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item)
|
||||||
new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item)
|
);
|
||||||
);
|
}
|
||||||
}
|
#endif
|
||||||
#endif
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Fires the 'ItemRemoved' event</summary>
|
||||||
/// <summary>Fires the 'ItemRemoved' event</summary>
|
/// <param name="item">Item that has been removed from the collection</param>
|
||||||
/// <param name="item">Item that has been removed from the collection</param>
|
protected virtual void OnRemoved(TItem item) {
|
||||||
protected virtual void OnRemoved(TItem item) {
|
if(ItemRemoved != null) {
|
||||||
if(ItemRemoved != null) {
|
ItemRemoved(this, new ItemEventArgs<TItem>(item));
|
||||||
ItemRemoved(this, new ItemEventArgs<TItem>(item));
|
}
|
||||||
}
|
#if !NO_SPECIALIZED_COLLECTIONS
|
||||||
#if !NO_SPECIALIZED_COLLECTIONS
|
if(CollectionChanged != null) {
|
||||||
if(CollectionChanged != null) {
|
CollectionChanged(
|
||||||
CollectionChanged(
|
this,
|
||||||
this,
|
new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, item)
|
||||||
new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, item)
|
);
|
||||||
);
|
}
|
||||||
}
|
#endif
|
||||||
#endif
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Fires the 'Clearing' event</summary>
|
||||||
/// <summary>Fires the 'Clearing' event</summary>
|
protected virtual void OnClearing() {
|
||||||
protected virtual void OnClearing() {
|
if(Clearing != null) {
|
||||||
if(Clearing != null) {
|
Clearing(this, EventArgs.Empty);
|
||||||
Clearing(this, EventArgs.Empty);
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Fires the 'Cleared' event</summary>
|
||||||
/// <summary>Fires the 'Cleared' event</summary>
|
protected virtual void OnCleared() {
|
||||||
protected virtual void OnCleared() {
|
if(Cleared != null) {
|
||||||
if(Cleared != null) {
|
Cleared(this, EventArgs.Empty);
|
||||||
Cleared(this, EventArgs.Empty);
|
}
|
||||||
}
|
#if !NO_SPECIALIZED_COLLECTIONS
|
||||||
#if !NO_SPECIALIZED_COLLECTIONS
|
if(CollectionChanged != null) {
|
||||||
if(CollectionChanged != null) {
|
CollectionChanged(this, Constants.NotifyCollectionResetEventArgs);
|
||||||
CollectionChanged(this, Constants.NotifyCollectionResetEventArgs);
|
}
|
||||||
}
|
#endif
|
||||||
#endif
|
}
|
||||||
}
|
|
||||||
|
#region IEnumerable implementation
|
||||||
#region IEnumerable implementation
|
|
||||||
|
/// <summary>Returns an enumerator for the items in the collection</summary>
|
||||||
/// <summary>Returns an enumerator for the items in the collection</summary>
|
/// <returns>An enumeration for the items in the collection</returns>
|
||||||
/// <returns>An enumeration for the items in the collection</returns>
|
IEnumerator IEnumerable.GetEnumerator() {
|
||||||
IEnumerator IEnumerable.GetEnumerator() {
|
return this.objectCollection.GetEnumerator();
|
||||||
return this.objectCollection.GetEnumerator();
|
}
|
||||||
}
|
|
||||||
|
#endregion // IEnumerable implementation
|
||||||
#endregion // IEnumerable implementation
|
|
||||||
|
#region ICollection implementation
|
||||||
#region ICollection implementation
|
|
||||||
|
/// <summary>Copies the contents of the collection into an array</summary>
|
||||||
/// <summary>Copies the contents of the collection into an array</summary>
|
/// <param name="array">Array the collection's contents will be copied into</param>
|
||||||
/// <param name="array">Array the collection's contents will be copied into</param>
|
/// <param name="arrayIndex">
|
||||||
/// <param name="arrayIndex">
|
/// Index in the array where the collection's first item will be placed
|
||||||
/// Index in the array where the collection's first item will be placed
|
/// </param>
|
||||||
/// </param>
|
void ICollection.CopyTo(Array array, int arrayIndex) {
|
||||||
void ICollection.CopyTo(Array array, int arrayIndex) {
|
this.objectCollection.CopyTo(array, arrayIndex);
|
||||||
this.objectCollection.CopyTo(array, arrayIndex);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Whether the collection synchronizes accesses from multiple threads</summary>
|
||||||
/// <summary>Whether the collection synchronizes accesses from multiple threads</summary>
|
bool ICollection.IsSynchronized {
|
||||||
bool ICollection.IsSynchronized {
|
get { return this.objectCollection.IsSynchronized; }
|
||||||
get { return this.objectCollection.IsSynchronized; }
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Synchronization root used to synchronize threads accessing the collection
|
||||||
/// Synchronization root used to synchronize threads accessing the collection
|
/// </summary>
|
||||||
/// </summary>
|
object ICollection.SyncRoot {
|
||||||
object ICollection.SyncRoot {
|
get { return this.objectCollection.SyncRoot; }
|
||||||
get { return this.objectCollection.SyncRoot; }
|
}
|
||||||
}
|
|
||||||
|
#endregion // IEnumerable implementation
|
||||||
#endregion // IEnumerable implementation
|
|
||||||
|
/// <summary>The wrapped collection under its type-safe interface</summary>
|
||||||
/// <summary>The wrapped collection under its type-safe interface</summary>
|
private ICollection<TItem> typedCollection;
|
||||||
private ICollection<TItem> typedCollection;
|
/// <summary>The wrapped collection under its object interface</summary>
|
||||||
/// <summary>The wrapped collection under its object interface</summary>
|
private ICollection objectCollection;
|
||||||
private ICollection objectCollection;
|
|
||||||
|
}
|
||||||
}
|
|
||||||
|
} // namespace Nuclex.Support.Collections
|
||||||
} // namespace Nuclex.Support.Collections
|
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,489 +1,488 @@
|
||||||
#region CPL License
|
#region Apache License 2.0
|
||||||
/*
|
/*
|
||||||
Nuclex Framework
|
Nuclex .NET Framework
|
||||||
Copyright (C) 2002-2017 Nuclex Development Labs
|
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
|
||||||
|
|
||||||
This library is free software; you can redistribute it and/or
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
modify it under the terms of the IBM Common Public License as
|
you may not use this file except in compliance with the License.
|
||||||
published by the IBM Corporation; either version 1.0 of the
|
You may obtain a copy of the License at
|
||||||
License, or (at your option) any later version.
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
This library is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
Unless required by applicable law or agreed to in writing, software
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
IBM Common Public License for more details.
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
You should have received a copy of the IBM Common Public
|
limitations under the License.
|
||||||
License along with this library
|
*/
|
||||||
*/
|
#endregion // Apache License 2.0
|
||||||
#endregion
|
|
||||||
|
using System;
|
||||||
using System;
|
using System.Collections;
|
||||||
using System.Collections;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Generic;
|
#if !NO_SPECIALIZED_COLLECTIONS
|
||||||
#if !NO_SPECIALIZED_COLLECTIONS
|
using System.Collections.Specialized;
|
||||||
using System.Collections.Specialized;
|
#endif
|
||||||
#endif
|
using System.Runtime.Serialization;
|
||||||
using System.Runtime.Serialization;
|
|
||||||
|
namespace Nuclex.Support.Collections {
|
||||||
namespace Nuclex.Support.Collections {
|
|
||||||
|
/// <summary>A dictionary that sneds out change notifications</summary>
|
||||||
/// <summary>A dictionary that sneds out change notifications</summary>
|
/// <typeparam name="TKey">Type of the keys used in the dictionary</typeparam>
|
||||||
/// <typeparam name="TKey">Type of the keys used in the dictionary</typeparam>
|
/// <typeparam name="TValue">Type of the values used in the dictionary</typeparam>
|
||||||
/// <typeparam name="TValue">Type of the values used in the dictionary</typeparam>
|
#if !NO_SERIALIZATION
|
||||||
#if !NO_SERIALIZATION
|
[Serializable]
|
||||||
[Serializable]
|
#endif
|
||||||
#endif
|
public class ObservableDictionary<TKey, TValue> :
|
||||||
public class ObservableDictionary<TKey, TValue> :
|
#if !NO_SERIALIZATION
|
||||||
#if !NO_SERIALIZATION
|
ISerializable,
|
||||||
ISerializable,
|
IDeserializationCallback,
|
||||||
IDeserializationCallback,
|
#endif
|
||||||
#endif
|
IDictionary<TKey, TValue>,
|
||||||
IDictionary<TKey, TValue>,
|
IDictionary,
|
||||||
IDictionary,
|
#if !NO_SPECIALIZED_COLLECTIONS
|
||||||
#if !NO_SPECIALIZED_COLLECTIONS
|
INotifyCollectionChanged,
|
||||||
INotifyCollectionChanged,
|
#endif
|
||||||
#endif
|
IObservableCollection<KeyValuePair<TKey, TValue>> {
|
||||||
IObservableCollection<KeyValuePair<TKey, TValue>> {
|
|
||||||
|
#if !NO_SERIALIZATION
|
||||||
#if !NO_SERIALIZATION
|
#region class SerializedDictionary
|
||||||
#region class SerializedDictionary
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Dictionary wrapped used to reconstruct a serialized read only dictionary
|
||||||
/// Dictionary wrapped used to reconstruct a serialized read only dictionary
|
/// </summary>
|
||||||
/// </summary>
|
private class SerializedDictionary : Dictionary<TKey, TValue> {
|
||||||
private class SerializedDictionary : Dictionary<TKey, TValue> {
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Initializes a new instance of the System.WeakReference class, using deserialized
|
||||||
/// Initializes a new instance of the System.WeakReference class, using deserialized
|
/// data from the specified serialization and stream objects.
|
||||||
/// data from the specified serialization and stream objects.
|
/// </summary>
|
||||||
/// </summary>
|
/// <param name="info">
|
||||||
/// <param name="info">
|
/// An object that holds all the data needed to serialize or deserialize the
|
||||||
/// An object that holds all the data needed to serialize or deserialize the
|
/// current System.WeakReference object.
|
||||||
/// current System.WeakReference object.
|
/// </param>
|
||||||
/// </param>
|
/// <param name="context">
|
||||||
/// <param name="context">
|
/// (Reserved) Describes the source and destination of the serialized stream
|
||||||
/// (Reserved) Describes the source and destination of the serialized stream
|
/// specified by info.
|
||||||
/// specified by info.
|
/// </param>
|
||||||
/// </param>
|
/// <exception cref="System.ArgumentNullException">
|
||||||
/// <exception cref="System.ArgumentNullException">
|
/// The info parameter is null.
|
||||||
/// The info parameter is null.
|
/// </exception>
|
||||||
/// </exception>
|
public SerializedDictionary(SerializationInfo info, StreamingContext context) :
|
||||||
public SerializedDictionary(SerializationInfo info, StreamingContext context) :
|
base(info, context) { }
|
||||||
base(info, context) { }
|
|
||||||
|
}
|
||||||
}
|
|
||||||
|
#endregion // class SerializedDictionary
|
||||||
#endregion // class SerializedDictionary
|
#endif // !NO_SERIALIZATION
|
||||||
#endif // !NO_SERIALIZATION
|
|
||||||
|
/// <summary>Raised when an item has been added to the dictionary</summary>
|
||||||
/// <summary>Raised when an item has been added to the dictionary</summary>
|
public event EventHandler<ItemEventArgs<KeyValuePair<TKey, TValue>>> ItemAdded;
|
||||||
public event EventHandler<ItemEventArgs<KeyValuePair<TKey, TValue>>> ItemAdded;
|
/// <summary>Raised when an item is removed from the dictionary</summary>
|
||||||
/// <summary>Raised when an item is removed from the dictionary</summary>
|
public event EventHandler<ItemEventArgs<KeyValuePair<TKey, TValue>>> ItemRemoved;
|
||||||
public event EventHandler<ItemEventArgs<KeyValuePair<TKey, TValue>>> ItemRemoved;
|
/// <summary>Raised when an item is replaced in the collection</summary>
|
||||||
/// <summary>Raised when an item is replaced in the collection</summary>
|
public event EventHandler<ItemReplaceEventArgs<KeyValuePair<TKey, TValue>>> ItemReplaced;
|
||||||
public event EventHandler<ItemReplaceEventArgs<KeyValuePair<TKey, TValue>>> ItemReplaced;
|
/// <summary>Raised when the dictionary is about to be cleared</summary>
|
||||||
/// <summary>Raised when the dictionary is about to be cleared</summary>
|
public event EventHandler Clearing;
|
||||||
public event EventHandler Clearing;
|
/// <summary>Raised when the dictionary has been cleared</summary>
|
||||||
/// <summary>Raised when the dictionary has been cleared</summary>
|
public event EventHandler Cleared;
|
||||||
public event EventHandler Cleared;
|
|
||||||
|
#if !NO_SPECIALIZED_COLLECTIONS
|
||||||
#if !NO_SPECIALIZED_COLLECTIONS
|
/// <summary>Called when the collection has changed</summary>
|
||||||
/// <summary>Called when the collection has changed</summary>
|
public event NotifyCollectionChangedEventHandler CollectionChanged;
|
||||||
public event NotifyCollectionChangedEventHandler CollectionChanged;
|
#endif
|
||||||
#endif
|
|
||||||
|
/// <summary>Initializes a new observable dictionary</summary>
|
||||||
/// <summary>Initializes a new observable dictionary</summary>
|
public ObservableDictionary() : this(new Dictionary<TKey, TValue>()) { }
|
||||||
public ObservableDictionary() : this(new Dictionary<TKey, TValue>()) { }
|
|
||||||
|
/// <summary>Initializes a new observable Dictionary wrapper</summary>
|
||||||
/// <summary>Initializes a new observable Dictionary wrapper</summary>
|
/// <param name="dictionary">Dictionary that will be wrapped</param>
|
||||||
/// <param name="dictionary">Dictionary that will be wrapped</param>
|
public ObservableDictionary(IDictionary<TKey, TValue> dictionary) {
|
||||||
public ObservableDictionary(IDictionary<TKey, TValue> dictionary) {
|
this.typedDictionary = dictionary;
|
||||||
this.typedDictionary = dictionary;
|
this.objectDictionary = (this.typedDictionary as IDictionary);
|
||||||
this.objectDictionary = (this.typedDictionary as IDictionary);
|
}
|
||||||
}
|
|
||||||
|
#if !NO_SERIALIZATION
|
||||||
#if !NO_SERIALIZATION
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Initializes a new instance of the System.WeakReference class, using deserialized
|
||||||
/// Initializes a new instance of the System.WeakReference class, using deserialized
|
/// data from the specified serialization and stream objects.
|
||||||
/// data from the specified serialization and stream objects.
|
/// </summary>
|
||||||
/// </summary>
|
/// <param name="info">
|
||||||
/// <param name="info">
|
/// An object that holds all the data needed to serialize or deserialize the
|
||||||
/// An object that holds all the data needed to serialize or deserialize the
|
/// current System.WeakReference object.
|
||||||
/// current System.WeakReference object.
|
/// </param>
|
||||||
/// </param>
|
/// <param name="context">
|
||||||
/// <param name="context">
|
/// (Reserved) Describes the source and destination of the serialized stream
|
||||||
/// (Reserved) Describes the source and destination of the serialized stream
|
/// specified by info.
|
||||||
/// specified by info.
|
/// </param>
|
||||||
/// </param>
|
/// <exception cref="System.ArgumentNullException">
|
||||||
/// <exception cref="System.ArgumentNullException">
|
/// The info parameter is null.
|
||||||
/// The info parameter is null.
|
/// </exception>
|
||||||
/// </exception>
|
protected ObservableDictionary(SerializationInfo info, StreamingContext context) :
|
||||||
protected ObservableDictionary(SerializationInfo info, StreamingContext context) :
|
this(new SerializedDictionary(info, context)) { }
|
||||||
this(new SerializedDictionary(info, context)) { }
|
|
||||||
|
#endif // !NO_SERIALIZATION
|
||||||
#endif // !NO_SERIALIZATION
|
|
||||||
|
/// <summary>Whether the directory is write-protected</summary>
|
||||||
/// <summary>Whether the directory is write-protected</summary>
|
public bool IsReadOnly {
|
||||||
public bool IsReadOnly {
|
get { return this.typedDictionary.IsReadOnly; }
|
||||||
get { return this.typedDictionary.IsReadOnly; }
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Determines whether the specified KeyValuePair is contained in the Dictionary
|
||||||
/// Determines whether the specified KeyValuePair is contained in the Dictionary
|
/// </summary>
|
||||||
/// </summary>
|
/// <param name="item">KeyValuePair that will be checked for</param>
|
||||||
/// <param name="item">KeyValuePair that will be checked for</param>
|
/// <returns>True if the provided KeyValuePair was contained in the Dictionary</returns>
|
||||||
/// <returns>True if the provided KeyValuePair was contained in the Dictionary</returns>
|
public bool Contains(KeyValuePair<TKey, TValue> item) {
|
||||||
public bool Contains(KeyValuePair<TKey, TValue> item) {
|
return this.typedDictionary.Contains(item);
|
||||||
return this.typedDictionary.Contains(item);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Determines whether the Dictionary contains the specified key</summary>
|
||||||
/// <summary>Determines whether the Dictionary contains the specified key</summary>
|
/// <param name="key">Key that will be checked for</param>
|
||||||
/// <param name="key">Key that will be checked for</param>
|
/// <returns>
|
||||||
/// <returns>
|
/// True if an entry with the specified key was contained in the Dictionary
|
||||||
/// True if an entry with the specified key was contained in the Dictionary
|
/// </returns>
|
||||||
/// </returns>
|
public bool ContainsKey(TKey key) {
|
||||||
public bool ContainsKey(TKey key) {
|
return this.typedDictionary.ContainsKey(key);
|
||||||
return this.typedDictionary.ContainsKey(key);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Copies the contents of the Dictionary into an array</summary>
|
||||||
/// <summary>Copies the contents of the Dictionary into an array</summary>
|
/// <param name="array">Array the Dictionary will be copied into</param>
|
||||||
/// <param name="array">Array the Dictionary will be copied into</param>
|
/// <param name="arrayIndex">
|
||||||
/// <param name="arrayIndex">
|
/// Starting index at which to begin filling the destination array
|
||||||
/// Starting index at which to begin filling the destination array
|
/// </param>
|
||||||
/// </param>
|
public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex) {
|
||||||
public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex) {
|
this.typedDictionary.CopyTo(array, arrayIndex);
|
||||||
this.typedDictionary.CopyTo(array, arrayIndex);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Number of elements contained in the Dictionary</summary>
|
||||||
/// <summary>Number of elements contained in the Dictionary</summary>
|
public int Count {
|
||||||
public int Count {
|
get { return this.typedDictionary.Count; }
|
||||||
get { return this.typedDictionary.Count; }
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Creates a new enumerator for the Dictionary</summary>
|
||||||
/// <summary>Creates a new enumerator for the Dictionary</summary>
|
/// <returns>The new Dictionary enumerator</returns>
|
||||||
/// <returns>The new Dictionary enumerator</returns>
|
public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator() {
|
||||||
public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator() {
|
return this.typedDictionary.GetEnumerator();
|
||||||
return this.typedDictionary.GetEnumerator();
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Collection of all keys contained in the Dictionary</summary>
|
||||||
/// <summary>Collection of all keys contained in the Dictionary</summary>
|
public ICollection<TKey> Keys {
|
||||||
public ICollection<TKey> Keys {
|
get { return this.typedDictionary.Keys; }
|
||||||
get { return this.typedDictionary.Keys; }
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Collection of all values contained in the Dictionary</summary>
|
||||||
/// <summary>Collection of all values contained in the Dictionary</summary>
|
public ICollection<TValue> Values {
|
||||||
public ICollection<TValue> Values {
|
get { return this.typedDictionary.Values; }
|
||||||
get { return this.typedDictionary.Values; }
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Attempts to retrieve the item with the specified key from the Dictionary
|
||||||
/// Attempts to retrieve the item with the specified key from the Dictionary
|
/// </summary>
|
||||||
/// </summary>
|
/// <param name="key">Key of the item to attempt to retrieve</param>
|
||||||
/// <param name="key">Key of the item to attempt to retrieve</param>
|
/// <param name="value">
|
||||||
/// <param name="value">
|
/// Output parameter that will receive the key upon successful completion
|
||||||
/// Output parameter that will receive the key upon successful completion
|
/// </param>
|
||||||
/// </param>
|
/// <returns>
|
||||||
/// <returns>
|
/// True if the item was found and has been placed in the output parameter
|
||||||
/// True if the item was found and has been placed in the output parameter
|
/// </returns>
|
||||||
/// </returns>
|
public bool TryGetValue(TKey key, out TValue value) {
|
||||||
public bool TryGetValue(TKey key, out TValue value) {
|
return this.typedDictionary.TryGetValue(key, out value);
|
||||||
return this.typedDictionary.TryGetValue(key, out value);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Accesses an item in the Dictionary by its key</summary>
|
||||||
/// <summary>Accesses an item in the Dictionary by its key</summary>
|
/// <param name="key">Key of the item that will be accessed</param>
|
||||||
/// <param name="key">Key of the item that will be accessed</param>
|
public TValue this[TKey key] {
|
||||||
public TValue this[TKey key] {
|
get { return this.typedDictionary[key]; }
|
||||||
get { return this.typedDictionary[key]; }
|
set {
|
||||||
set {
|
bool removed;
|
||||||
bool removed;
|
TValue oldValue;
|
||||||
TValue oldValue;
|
removed = this.typedDictionary.TryGetValue(key, out oldValue);
|
||||||
removed = this.typedDictionary.TryGetValue(key, out oldValue);
|
|
||||||
|
this.typedDictionary[key] = value;
|
||||||
this.typedDictionary[key] = value;
|
|
||||||
|
if(removed) {
|
||||||
if(removed) {
|
OnReplaced(
|
||||||
OnReplaced(
|
new KeyValuePair<TKey, TValue>(key, oldValue),
|
||||||
new KeyValuePair<TKey, TValue>(key, oldValue),
|
new KeyValuePair<TKey, TValue>(key, value)
|
||||||
new KeyValuePair<TKey, TValue>(key, value)
|
);
|
||||||
);
|
} else {
|
||||||
} else {
|
OnAdded(new KeyValuePair<TKey, TValue>(key, value));
|
||||||
OnAdded(new KeyValuePair<TKey, TValue>(key, value));
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Inserts an item into the Dictionary</summary>
|
||||||
/// <summary>Inserts an item into the Dictionary</summary>
|
/// <param name="key">Key under which to add the new item</param>
|
||||||
/// <param name="key">Key under which to add the new item</param>
|
/// <param name="value">Item that will be added to the Dictionary</param>
|
||||||
/// <param name="value">Item that will be added to the Dictionary</param>
|
public void Add(TKey key, TValue value) {
|
||||||
public void Add(TKey key, TValue value) {
|
this.typedDictionary.Add(key, value);
|
||||||
this.typedDictionary.Add(key, value);
|
OnAdded(new KeyValuePair<TKey, TValue>(key, value));
|
||||||
OnAdded(new KeyValuePair<TKey, TValue>(key, value));
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Removes the item with the specified key from the Dictionary</summary>
|
||||||
/// <summary>Removes the item with the specified key from the Dictionary</summary>
|
/// <param name="key">Key of the elementes that will be removed</param>
|
||||||
/// <param name="key">Key of the elementes that will be removed</param>
|
/// <returns>True if an item with the specified key was found and removed</returns>
|
||||||
/// <returns>True if an item with the specified key was found and removed</returns>
|
public bool Remove(TKey key) {
|
||||||
public bool Remove(TKey key) {
|
TValue oldValue;
|
||||||
TValue oldValue;
|
this.typedDictionary.TryGetValue(key, out oldValue);
|
||||||
this.typedDictionary.TryGetValue(key, out oldValue);
|
|
||||||
|
bool removed = this.typedDictionary.Remove(key);
|
||||||
bool removed = this.typedDictionary.Remove(key);
|
if(removed) {
|
||||||
if(removed) {
|
OnRemoved(new KeyValuePair<TKey, TValue>(key, oldValue));
|
||||||
OnRemoved(new KeyValuePair<TKey, TValue>(key, oldValue));
|
}
|
||||||
}
|
return removed;
|
||||||
return removed;
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Removes all items from the Dictionary</summary>
|
||||||
/// <summary>Removes all items from the Dictionary</summary>
|
public void Clear() {
|
||||||
public void Clear() {
|
OnClearing();
|
||||||
OnClearing();
|
this.typedDictionary.Clear();
|
||||||
this.typedDictionary.Clear();
|
OnCleared();
|
||||||
OnCleared();
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Fires the 'ItemAdded' event</summary>
|
||||||
/// <summary>Fires the 'ItemAdded' event</summary>
|
/// <param name="item">Item that has been added to the collection</param>
|
||||||
/// <param name="item">Item that has been added to the collection</param>
|
protected virtual void OnAdded(KeyValuePair<TKey, TValue> item) {
|
||||||
protected virtual void OnAdded(KeyValuePair<TKey, TValue> item) {
|
if(ItemAdded != null)
|
||||||
if(ItemAdded != null)
|
ItemAdded(this, new ItemEventArgs<KeyValuePair<TKey, TValue>>(item));
|
||||||
ItemAdded(this, new ItemEventArgs<KeyValuePair<TKey, TValue>>(item));
|
|
||||||
|
#if !NO_SPECIALIZED_COLLECTIONS
|
||||||
#if !NO_SPECIALIZED_COLLECTIONS
|
if(CollectionChanged != null) {
|
||||||
if(CollectionChanged != null) {
|
CollectionChanged(
|
||||||
CollectionChanged(
|
this,
|
||||||
this,
|
new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item)
|
||||||
new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item)
|
);
|
||||||
);
|
}
|
||||||
}
|
#endif
|
||||||
#endif
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Fires the 'ItemRemoved' event</summary>
|
||||||
/// <summary>Fires the 'ItemRemoved' event</summary>
|
/// <param name="item">Item that has been removed from the collection</param>
|
||||||
/// <param name="item">Item that has been removed from the collection</param>
|
protected virtual void OnRemoved(KeyValuePair<TKey, TValue> item) {
|
||||||
protected virtual void OnRemoved(KeyValuePair<TKey, TValue> item) {
|
if(ItemRemoved != null) {
|
||||||
if(ItemRemoved != null) {
|
ItemRemoved(this, new ItemEventArgs<KeyValuePair<TKey, TValue>>(item));
|
||||||
ItemRemoved(this, new ItemEventArgs<KeyValuePair<TKey, TValue>>(item));
|
}
|
||||||
}
|
#if !NO_SPECIALIZED_COLLECTIONS
|
||||||
#if !NO_SPECIALIZED_COLLECTIONS
|
if(CollectionChanged != null) {
|
||||||
if(CollectionChanged != null) {
|
CollectionChanged(
|
||||||
CollectionChanged(
|
this,
|
||||||
this,
|
new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, item)
|
||||||
new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, item)
|
);
|
||||||
);
|
}
|
||||||
}
|
#endif
|
||||||
#endif
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Fires the 'ItemReplaced' event</summary>
|
||||||
/// <summary>Fires the 'ItemReplaced' event</summary>
|
/// <param name="oldItem">Item that has been replaced in the collection</param>
|
||||||
/// <param name="oldItem">Item that has been replaced in the collection</param>
|
/// <param name="newItem">Item with which the original item was replaced</param>
|
||||||
/// <param name="newItem">Item with which the original item was replaced</param>
|
protected virtual void OnReplaced(
|
||||||
protected virtual void OnReplaced(
|
KeyValuePair<TKey, TValue> oldItem, KeyValuePair<TKey, TValue> newItem
|
||||||
KeyValuePair<TKey, TValue> oldItem, KeyValuePair<TKey, TValue> newItem
|
) {
|
||||||
) {
|
if(ItemReplaced != null) {
|
||||||
if(ItemReplaced != null) {
|
ItemReplaced(
|
||||||
ItemReplaced(
|
this,
|
||||||
this,
|
new ItemReplaceEventArgs<KeyValuePair<TKey, TValue>>(oldItem, newItem)
|
||||||
new ItemReplaceEventArgs<KeyValuePair<TKey, TValue>>(oldItem, newItem)
|
);
|
||||||
);
|
}
|
||||||
}
|
#if !NO_SPECIALIZED_COLLECTIONS
|
||||||
#if !NO_SPECIALIZED_COLLECTIONS
|
if(CollectionChanged != null) {
|
||||||
if(CollectionChanged != null) {
|
CollectionChanged(
|
||||||
CollectionChanged(
|
this, new NotifyCollectionChangedEventArgs(
|
||||||
this, new NotifyCollectionChangedEventArgs(
|
NotifyCollectionChangedAction.Replace, newItem, oldItem
|
||||||
NotifyCollectionChangedAction.Replace, newItem, oldItem
|
)
|
||||||
)
|
);
|
||||||
);
|
}
|
||||||
}
|
#endif
|
||||||
#endif
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Fires the 'Clearing' event</summary>
|
||||||
/// <summary>Fires the 'Clearing' event</summary>
|
protected virtual void OnClearing() {
|
||||||
protected virtual void OnClearing() {
|
if(Clearing != null) {
|
||||||
if(Clearing != null) {
|
Clearing(this, EventArgs.Empty);
|
||||||
Clearing(this, EventArgs.Empty);
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Fires the 'Cleared' event</summary>
|
||||||
/// <summary>Fires the 'Cleared' event</summary>
|
protected virtual void OnCleared() {
|
||||||
protected virtual void OnCleared() {
|
if(Cleared != null) {
|
||||||
if(Cleared != null) {
|
Cleared(this, EventArgs.Empty);
|
||||||
Cleared(this, EventArgs.Empty);
|
}
|
||||||
}
|
#if !NO_SPECIALIZED_COLLECTIONS
|
||||||
#if !NO_SPECIALIZED_COLLECTIONS
|
if(CollectionChanged != null) {
|
||||||
if(CollectionChanged != null) {
|
CollectionChanged(this, Constants.NotifyCollectionResetEventArgs);
|
||||||
CollectionChanged(this, Constants.NotifyCollectionResetEventArgs);
|
}
|
||||||
}
|
#endif
|
||||||
#endif
|
}
|
||||||
}
|
|
||||||
|
#region IEnumerable implementation
|
||||||
#region IEnumerable implementation
|
|
||||||
|
/// <summary>Returns a new object enumerator for the Dictionary</summary>
|
||||||
/// <summary>Returns a new object enumerator for the Dictionary</summary>
|
/// <returns>The new object enumerator</returns>
|
||||||
/// <returns>The new object enumerator</returns>
|
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() {
|
||||||
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() {
|
return (this.typedDictionary as IEnumerable).GetEnumerator();
|
||||||
return (this.typedDictionary as IEnumerable).GetEnumerator();
|
}
|
||||||
}
|
|
||||||
|
#endregion
|
||||||
#endregion
|
|
||||||
|
#region IDictionary implementation
|
||||||
#region IDictionary implementation
|
|
||||||
|
/// <summary>Adds an item into the Dictionary</summary>
|
||||||
/// <summary>Adds an item into the Dictionary</summary>
|
/// <param name="key">Key under which the item will be added</param>
|
||||||
/// <param name="key">Key under which the item will be added</param>
|
/// <param name="value">Item that will be added</param>
|
||||||
/// <param name="value">Item that will be added</param>
|
void IDictionary.Add(object key, object value) {
|
||||||
void IDictionary.Add(object key, object value) {
|
this.objectDictionary.Add(key, value);
|
||||||
this.objectDictionary.Add(key, value);
|
OnAdded(new KeyValuePair<TKey, TValue>((TKey)key, (TValue)value));
|
||||||
OnAdded(new KeyValuePair<TKey, TValue>((TKey)key, (TValue)value));
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Determines whether the specified key exists in the Dictionary</summary>
|
||||||
/// <summary>Determines whether the specified key exists in the Dictionary</summary>
|
/// <param name="key">Key that will be checked for</param>
|
||||||
/// <param name="key">Key that will be checked for</param>
|
/// <returns>True if an item with the specified key exists in the Dictionary</returns>
|
||||||
/// <returns>True if an item with the specified key exists in the Dictionary</returns>
|
bool IDictionary.Contains(object key) {
|
||||||
bool IDictionary.Contains(object key) {
|
return this.objectDictionary.Contains(key);
|
||||||
return this.objectDictionary.Contains(key);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Whether the size of the Dictionary is fixed</summary>
|
||||||
/// <summary>Whether the size of the Dictionary is fixed</summary>
|
bool IDictionary.IsFixedSize {
|
||||||
bool IDictionary.IsFixedSize {
|
get { return this.objectDictionary.IsFixedSize; }
|
||||||
get { return this.objectDictionary.IsFixedSize; }
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Returns a collection of all keys in the Dictionary</summary>
|
||||||
/// <summary>Returns a collection of all keys in the Dictionary</summary>
|
ICollection IDictionary.Keys {
|
||||||
ICollection IDictionary.Keys {
|
get { return this.objectDictionary.Keys; }
|
||||||
get { return this.objectDictionary.Keys; }
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Returns a collection of all values stored in the Dictionary</summary>
|
||||||
/// <summary>Returns a collection of all values stored in the Dictionary</summary>
|
ICollection IDictionary.Values {
|
||||||
ICollection IDictionary.Values {
|
get { return this.objectDictionary.Values; }
|
||||||
get { return this.objectDictionary.Values; }
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Removes an item from the Dictionary</summary>
|
||||||
/// <summary>Removes an item from the Dictionary</summary>
|
/// <param name="key">Key of the item that will be removed</param>
|
||||||
/// <param name="key">Key of the item that will be removed</param>
|
void IDictionary.Remove(object key) {
|
||||||
void IDictionary.Remove(object key) {
|
TValue value;
|
||||||
TValue value;
|
bool removed = this.typedDictionary.TryGetValue((TKey)key, out value);
|
||||||
bool removed = this.typedDictionary.TryGetValue((TKey)key, out value);
|
this.objectDictionary.Remove(key);
|
||||||
this.objectDictionary.Remove(key);
|
if(removed) {
|
||||||
if(removed) {
|
OnRemoved(new KeyValuePair<TKey, TValue>((TKey)key, (TValue)value));
|
||||||
OnRemoved(new KeyValuePair<TKey, TValue>((TKey)key, (TValue)value));
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Accesses an item in the Dictionary by its key</summary>
|
||||||
/// <summary>Accesses an item in the Dictionary by its key</summary>
|
/// <param name="key">Key of the item that will be accessed</param>
|
||||||
/// <param name="key">Key of the item that will be accessed</param>
|
/// <returns>The item with the specified key</returns>
|
||||||
/// <returns>The item with the specified key</returns>
|
object IDictionary.this[object key] {
|
||||||
object IDictionary.this[object key] {
|
get { return this.objectDictionary[key]; }
|
||||||
get { return this.objectDictionary[key]; }
|
set {
|
||||||
set {
|
bool removed;
|
||||||
bool removed;
|
TValue oldValue;
|
||||||
TValue oldValue;
|
removed = this.typedDictionary.TryGetValue((TKey)key, out oldValue);
|
||||||
removed = this.typedDictionary.TryGetValue((TKey)key, out oldValue);
|
|
||||||
|
this.objectDictionary[key] = value;
|
||||||
this.objectDictionary[key] = value;
|
|
||||||
|
if(removed) {
|
||||||
if(removed) {
|
OnRemoved(new KeyValuePair<TKey, TValue>((TKey)key, oldValue));
|
||||||
OnRemoved(new KeyValuePair<TKey, TValue>((TKey)key, oldValue));
|
}
|
||||||
}
|
OnAdded(new KeyValuePair<TKey, TValue>((TKey)key, (TValue)value));
|
||||||
OnAdded(new KeyValuePair<TKey, TValue>((TKey)key, (TValue)value));
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
#endregion // IDictionary implementation
|
||||||
#endregion // IDictionary implementation
|
|
||||||
|
#region IDictionaryEnumerator implementation
|
||||||
#region IDictionaryEnumerator implementation
|
|
||||||
|
/// <summary>Returns a new entry enumerator for the dictionary</summary>
|
||||||
/// <summary>Returns a new entry enumerator for the dictionary</summary>
|
/// <returns>The new entry enumerator</returns>
|
||||||
/// <returns>The new entry enumerator</returns>
|
IDictionaryEnumerator IDictionary.GetEnumerator() {
|
||||||
IDictionaryEnumerator IDictionary.GetEnumerator() {
|
return this.objectDictionary.GetEnumerator();
|
||||||
return this.objectDictionary.GetEnumerator();
|
}
|
||||||
}
|
|
||||||
|
#endregion // IDictionaryEnumerator implementation
|
||||||
#endregion // IDictionaryEnumerator implementation
|
|
||||||
|
#region ICollection<> implementation
|
||||||
#region ICollection<> implementation
|
|
||||||
|
/// <summary>Inserts an already prepared element into the Dictionary</summary>
|
||||||
/// <summary>Inserts an already prepared element into the Dictionary</summary>
|
/// <param name="item">Prepared element that will be added to the Dictionary</param>
|
||||||
/// <param name="item">Prepared element that will be added to the Dictionary</param>
|
void ICollection<KeyValuePair<TKey, TValue>>.Add(
|
||||||
void ICollection<KeyValuePair<TKey, TValue>>.Add(
|
KeyValuePair<TKey, TValue> item
|
||||||
KeyValuePair<TKey, TValue> item
|
) {
|
||||||
) {
|
this.typedDictionary.Add(item);
|
||||||
this.typedDictionary.Add(item);
|
OnAdded(item);
|
||||||
OnAdded(item);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Removes all items from the Dictionary</summary>
|
||||||
/// <summary>Removes all items from the Dictionary</summary>
|
void ICollection<KeyValuePair<TKey, TValue>>.Clear() {
|
||||||
void ICollection<KeyValuePair<TKey, TValue>>.Clear() {
|
OnClearing();
|
||||||
OnClearing();
|
this.typedDictionary.Clear();
|
||||||
this.typedDictionary.Clear();
|
OnCleared();
|
||||||
OnCleared();
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Removes all items from the Dictionary</summary>
|
||||||
/// <summary>Removes all items from the Dictionary</summary>
|
/// <param name="itemToRemove">Item that will be removed from the Dictionary</param>
|
||||||
/// <param name="itemToRemove">Item that will be removed from the Dictionary</param>
|
bool ICollection<KeyValuePair<TKey, TValue>>.Remove(
|
||||||
bool ICollection<KeyValuePair<TKey, TValue>>.Remove(
|
KeyValuePair<TKey, TValue> itemToRemove
|
||||||
KeyValuePair<TKey, TValue> itemToRemove
|
) {
|
||||||
) {
|
bool removed = this.typedDictionary.Remove(itemToRemove);
|
||||||
bool removed = this.typedDictionary.Remove(itemToRemove);
|
if(removed) {
|
||||||
if(removed) {
|
OnRemoved(itemToRemove);
|
||||||
OnRemoved(itemToRemove);
|
}
|
||||||
}
|
return removed;
|
||||||
return removed;
|
}
|
||||||
}
|
|
||||||
|
#endregion
|
||||||
#endregion
|
|
||||||
|
#region ICollection implementation
|
||||||
#region ICollection implementation
|
|
||||||
|
/// <summary>Copies the contents of the Dictionary into an array</summary>
|
||||||
/// <summary>Copies the contents of the Dictionary into an array</summary>
|
/// <param name="array">Array the Dictionary contents will be copied into</param>
|
||||||
/// <param name="array">Array the Dictionary contents will be copied into</param>
|
/// <param name="index">
|
||||||
/// <param name="index">
|
/// Starting index at which to begin filling the destination array
|
||||||
/// Starting index at which to begin filling the destination array
|
/// </param>
|
||||||
/// </param>
|
void ICollection.CopyTo(Array array, int index) {
|
||||||
void ICollection.CopyTo(Array array, int index) {
|
this.objectDictionary.CopyTo(array, index);
|
||||||
this.objectDictionary.CopyTo(array, index);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Whether the Dictionary is synchronized for multi-threaded usage</summary>
|
||||||
/// <summary>Whether the Dictionary is synchronized for multi-threaded usage</summary>
|
bool ICollection.IsSynchronized {
|
||||||
bool ICollection.IsSynchronized {
|
get { return this.objectDictionary.IsSynchronized; }
|
||||||
get { return this.objectDictionary.IsSynchronized; }
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Synchronization root on which the Dictionary locks</summary>
|
||||||
/// <summary>Synchronization root on which the Dictionary locks</summary>
|
object ICollection.SyncRoot {
|
||||||
object ICollection.SyncRoot {
|
get { return this.objectDictionary.SyncRoot; }
|
||||||
get { return this.objectDictionary.SyncRoot; }
|
}
|
||||||
}
|
|
||||||
|
#endregion
|
||||||
#endregion
|
|
||||||
|
#if !NO_SERIALIZATION
|
||||||
#if !NO_SERIALIZATION
|
#region ISerializable implementation
|
||||||
#region ISerializable implementation
|
|
||||||
|
/// <summary>Serializes the Dictionary</summary>
|
||||||
/// <summary>Serializes the Dictionary</summary>
|
/// <param name="info">
|
||||||
/// <param name="info">
|
/// Provides the container into which the Dictionary will serialize itself
|
||||||
/// Provides the container into which the Dictionary will serialize itself
|
/// </param>
|
||||||
/// </param>
|
/// <param name="context">
|
||||||
/// <param name="context">
|
/// Contextual informations about the serialization environment
|
||||||
/// Contextual informations about the serialization environment
|
/// </param>
|
||||||
/// </param>
|
void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context) {
|
||||||
void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context) {
|
(this.typedDictionary as ISerializable).GetObjectData(info, context);
|
||||||
(this.typedDictionary as ISerializable).GetObjectData(info, context);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Called after all objects have been successfully deserialized</summary>
|
||||||
/// <summary>Called after all objects have been successfully deserialized</summary>
|
/// <param name="sender">Nicht unterstützt</param>
|
||||||
/// <param name="sender">Nicht unterstützt</param>
|
void IDeserializationCallback.OnDeserialization(object sender) {
|
||||||
void IDeserializationCallback.OnDeserialization(object sender) {
|
(this.typedDictionary as IDeserializationCallback).OnDeserialization(sender);
|
||||||
(this.typedDictionary as IDeserializationCallback).OnDeserialization(sender);
|
}
|
||||||
}
|
|
||||||
|
#endregion
|
||||||
#endregion
|
#endif //!NO_SERIALIZATION
|
||||||
#endif //!NO_SERIALIZATION
|
|
||||||
|
/// <summary>The wrapped Dictionary under its type-safe interface</summary>
|
||||||
/// <summary>The wrapped Dictionary under its type-safe interface</summary>
|
private IDictionary<TKey, TValue> typedDictionary;
|
||||||
private IDictionary<TKey, TValue> typedDictionary;
|
/// <summary>The wrapped Dictionary under its object interface</summary>
|
||||||
/// <summary>The wrapped Dictionary under its object interface</summary>
|
private IDictionary objectDictionary;
|
||||||
private IDictionary objectDictionary;
|
|
||||||
|
}
|
||||||
}
|
|
||||||
|
} // namespace Nuclex.Support.Collections
|
||||||
} // namespace Nuclex.Support.Collections
|
|
||||||
|
|
|
@ -1,176 +1,175 @@
|
||||||
#region CPL License
|
#region Apache License 2.0
|
||||||
/*
|
/*
|
||||||
Nuclex Framework
|
Nuclex .NET Framework
|
||||||
Copyright (C) 2002-2017 Nuclex Development Labs
|
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
|
||||||
|
|
||||||
This library is free software; you can redistribute it and/or
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
modify it under the terms of the IBM Common Public License as
|
you may not use this file except in compliance with the License.
|
||||||
published by the IBM Corporation; either version 1.0 of the
|
You may obtain a copy of the License at
|
||||||
License, or (at your option) any later version.
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
This library is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
Unless required by applicable law or agreed to in writing, software
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
IBM Common Public License for more details.
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
You should have received a copy of the IBM Common Public
|
limitations under the License.
|
||||||
License along with this library
|
*/
|
||||||
*/
|
#endregion // Apache License 2.0
|
||||||
#endregion
|
|
||||||
|
#if !NO_NMOCK
|
||||||
#if !NO_NMOCK
|
|
||||||
|
using System;
|
||||||
using System;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Generic;
|
|
||||||
|
#if UNITTEST
|
||||||
#if UNITTEST
|
|
||||||
|
using NUnit.Framework;
|
||||||
using NUnit.Framework;
|
using NMock;
|
||||||
using NMock;
|
|
||||||
|
namespace Nuclex.Support.Collections {
|
||||||
namespace Nuclex.Support.Collections {
|
|
||||||
|
/// <summary>Unit Test for the observable list class</summary>
|
||||||
/// <summary>Unit Test for the observable list class</summary>
|
[TestFixture]
|
||||||
[TestFixture]
|
internal class ObservableListTest {
|
||||||
internal class ObservableListTest {
|
|
||||||
|
#region interface IObservableCollectionSubscriber
|
||||||
#region interface IObservableCollectionSubscriber
|
|
||||||
|
/// <summary>Interface used to test the observable collection</summary>
|
||||||
/// <summary>Interface used to test the observable collection</summary>
|
public interface IObservableCollectionSubscriber {
|
||||||
public interface IObservableCollectionSubscriber {
|
|
||||||
|
/// <summary>Called when the collection is about to clear its contents</summary>
|
||||||
/// <summary>Called when the collection is about to clear its contents</summary>
|
/// <param name="sender">Collection that is clearing its contents</param>
|
||||||
/// <param name="sender">Collection that is clearing its contents</param>
|
/// <param name="arguments">Not used</param>
|
||||||
/// <param name="arguments">Not used</param>
|
void Clearing(object sender, EventArgs arguments);
|
||||||
void Clearing(object sender, EventArgs arguments);
|
|
||||||
|
/// <summary>Called when the collection has been cleared of its contents</summary>
|
||||||
/// <summary>Called when the collection has been cleared of its contents</summary>
|
/// <param name="sender">Collection that was cleared of its contents</param>
|
||||||
/// <param name="sender">Collection that was cleared of its contents</param>
|
/// <param name="arguments">Not used</param>
|
||||||
/// <param name="arguments">Not used</param>
|
void Cleared(object sender, EventArgs arguments);
|
||||||
void Cleared(object sender, EventArgs arguments);
|
|
||||||
|
/// <summary>Called when an item is added to the collection</summary>
|
||||||
/// <summary>Called when an item is added to the collection</summary>
|
/// <param name="sender">Collection to which an item is being added</param>
|
||||||
/// <param name="sender">Collection to which an item is being added</param>
|
/// <param name="arguments">Contains the item that is being added</param>
|
||||||
/// <param name="arguments">Contains the item that is being added</param>
|
void ItemAdded(object sender, ItemEventArgs<int> arguments);
|
||||||
void ItemAdded(object sender, ItemEventArgs<int> arguments);
|
|
||||||
|
/// <summary>Called when an item is removed from the collection</summary>
|
||||||
/// <summary>Called when an item is removed from the collection</summary>
|
/// <param name="sender">Collection from which an item is being removed</param>
|
||||||
/// <param name="sender">Collection from which an item is being removed</param>
|
/// <param name="arguments">Contains the item that is being removed</param>
|
||||||
/// <param name="arguments">Contains the item that is being removed</param>
|
void ItemRemoved(object sender, ItemEventArgs<int> arguments);
|
||||||
void ItemRemoved(object sender, ItemEventArgs<int> arguments);
|
|
||||||
|
/// <summary>Called when an item is replaced in the dictionary</summary>
|
||||||
/// <summary>Called when an item is replaced in the dictionary</summary>
|
/// <param name="sender">Dictionary in which an item is being replaced</param>
|
||||||
/// <param name="sender">Dictionary in which an item is being replaced</param>
|
/// <param name="arguments">Contains the replaced item and its replacement</param>
|
||||||
/// <param name="arguments">Contains the replaced item and its replacement</param>
|
void ItemReplaced(object sender, ItemReplaceEventArgs<int> arguments);
|
||||||
void ItemReplaced(object sender, ItemReplaceEventArgs<int> arguments);
|
|
||||||
|
}
|
||||||
}
|
|
||||||
|
#endregion // interface IObservableCollectionSubscriber
|
||||||
#endregion // interface IObservableCollectionSubscriber
|
|
||||||
|
/// <summary>Initialization routine executed before each test is run</summary>
|
||||||
/// <summary>Initialization routine executed before each test is run</summary>
|
[SetUp]
|
||||||
[SetUp]
|
public void Setup() {
|
||||||
public void Setup() {
|
this.mockery = new MockFactory();
|
||||||
this.mockery = new MockFactory();
|
|
||||||
|
this.mockedSubscriber = this.mockery.CreateMock<IObservableCollectionSubscriber>();
|
||||||
this.mockedSubscriber = this.mockery.CreateMock<IObservableCollectionSubscriber>();
|
|
||||||
|
this.observedList = new ObservableList<int>();
|
||||||
this.observedList = new ObservableList<int>();
|
this.observedList.Clearing += new EventHandler(
|
||||||
this.observedList.Clearing += new EventHandler(
|
this.mockedSubscriber.MockObject.Clearing
|
||||||
this.mockedSubscriber.MockObject.Clearing
|
);
|
||||||
);
|
this.observedList.Cleared += new EventHandler(
|
||||||
this.observedList.Cleared += new EventHandler(
|
this.mockedSubscriber.MockObject.Cleared
|
||||||
this.mockedSubscriber.MockObject.Cleared
|
);
|
||||||
);
|
this.observedList.ItemAdded += new EventHandler<ItemEventArgs<int>>(
|
||||||
this.observedList.ItemAdded += new EventHandler<ItemEventArgs<int>>(
|
this.mockedSubscriber.MockObject.ItemAdded
|
||||||
this.mockedSubscriber.MockObject.ItemAdded
|
);
|
||||||
);
|
this.observedList.ItemRemoved += new EventHandler<ItemEventArgs<int>>(
|
||||||
this.observedList.ItemRemoved += new EventHandler<ItemEventArgs<int>>(
|
this.mockedSubscriber.MockObject.ItemRemoved
|
||||||
this.mockedSubscriber.MockObject.ItemRemoved
|
);
|
||||||
);
|
this.observedList.ItemReplaced += new EventHandler<ItemReplaceEventArgs<int>>(
|
||||||
this.observedList.ItemReplaced += new EventHandler<ItemReplaceEventArgs<int>>(
|
this.mockedSubscriber.MockObject.ItemReplaced
|
||||||
this.mockedSubscriber.MockObject.ItemReplaced
|
);
|
||||||
);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Tests whether the Clearing event is fired</summary>
|
||||||
/// <summary>Tests whether the Clearing event is fired</summary>
|
[Test]
|
||||||
[Test]
|
public void TestClearingEvent() {
|
||||||
public void TestClearingEvent() {
|
this.mockedSubscriber.Expects.One.Method(m => m.Clearing(null, null)).WithAnyArguments();
|
||||||
this.mockedSubscriber.Expects.One.Method(m => m.Clearing(null, null)).WithAnyArguments();
|
this.mockedSubscriber.Expects.One.Method(m => m.Cleared(null, null)).WithAnyArguments();
|
||||||
this.mockedSubscriber.Expects.One.Method(m => m.Cleared(null, null)).WithAnyArguments();
|
this.observedList.Clear();
|
||||||
this.observedList.Clear();
|
|
||||||
|
this.mockery.VerifyAllExpectationsHaveBeenMet();
|
||||||
this.mockery.VerifyAllExpectationsHaveBeenMet();
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Tests whether the ItemAdded event is fired</summary>
|
||||||
/// <summary>Tests whether the ItemAdded event is fired</summary>
|
[Test]
|
||||||
[Test]
|
public void TestItemAddedEvent() {
|
||||||
public void TestItemAddedEvent() {
|
this.mockedSubscriber.Expects.One.Method(m => m.ItemAdded(null, null)).WithAnyArguments();
|
||||||
this.mockedSubscriber.Expects.One.Method(m => m.ItemAdded(null, null)).WithAnyArguments();
|
|
||||||
|
this.observedList.Add(123);
|
||||||
this.observedList.Add(123);
|
|
||||||
|
this.mockery.VerifyAllExpectationsHaveBeenMet();
|
||||||
this.mockery.VerifyAllExpectationsHaveBeenMet();
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Tests whether the ItemRemoved event is fired</summary>
|
||||||
/// <summary>Tests whether the ItemRemoved event is fired</summary>
|
[Test]
|
||||||
[Test]
|
public void TestItemRemovedEvent() {
|
||||||
public void TestItemRemovedEvent() {
|
this.mockedSubscriber.Expects.One.Method(m => m.ItemAdded(null, null)).WithAnyArguments();
|
||||||
this.mockedSubscriber.Expects.One.Method(m => m.ItemAdded(null, null)).WithAnyArguments();
|
|
||||||
|
this.observedList.Add(123);
|
||||||
this.observedList.Add(123);
|
|
||||||
|
this.mockedSubscriber.Expects.One.Method(m => m.ItemRemoved(null, null)).WithAnyArguments();
|
||||||
this.mockedSubscriber.Expects.One.Method(m => m.ItemRemoved(null, null)).WithAnyArguments();
|
|
||||||
|
this.observedList.Remove(123);
|
||||||
this.observedList.Remove(123);
|
|
||||||
|
this.mockery.VerifyAllExpectationsHaveBeenMet();
|
||||||
this.mockery.VerifyAllExpectationsHaveBeenMet();
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Tests whether items in the collection can be replaced</summary>
|
||||||
/// <summary>Tests whether items in the collection can be replaced</summary>
|
[Test]
|
||||||
[Test]
|
public void TestItemReplacement() {
|
||||||
public void TestItemReplacement() {
|
this.mockedSubscriber.Expects.Exactly(3).Method(
|
||||||
this.mockedSubscriber.Expects.Exactly(3).Method(
|
m => m.ItemAdded(null, null)
|
||||||
m => m.ItemAdded(null, null)
|
).WithAnyArguments();
|
||||||
).WithAnyArguments();
|
|
||||||
|
this.observedList.Add(1);
|
||||||
this.observedList.Add(1);
|
this.observedList.Add(2);
|
||||||
this.observedList.Add(2);
|
this.observedList.Add(3);
|
||||||
this.observedList.Add(3);
|
|
||||||
|
this.mockedSubscriber.Expects.One.Method(m => m.ItemReplaced(null, null)).WithAnyArguments();
|
||||||
this.mockedSubscriber.Expects.One.Method(m => m.ItemReplaced(null, null)).WithAnyArguments();
|
|
||||||
|
// Replace the middle item with something else
|
||||||
// Replace the middle item with something else
|
this.observedList[1] = 4;
|
||||||
this.observedList[1] = 4;
|
|
||||||
|
Assert.AreEqual(
|
||||||
Assert.AreEqual(
|
1, this.observedList.IndexOf(4)
|
||||||
1, this.observedList.IndexOf(4)
|
);
|
||||||
);
|
|
||||||
|
this.mockery.VerifyAllExpectationsHaveBeenMet();
|
||||||
this.mockery.VerifyAllExpectationsHaveBeenMet();
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Tests whether a the list constructor is working</summary>
|
||||||
/// <summary>Tests whether a the list constructor is working</summary>
|
[Test]
|
||||||
[Test]
|
public void TestListConstructor() {
|
||||||
public void TestListConstructor() {
|
int[] integers = new int[] { 12, 34, 56, 78 };
|
||||||
int[] integers = new int[] { 12, 34, 56, 78 };
|
|
||||||
|
var testList = new ObservableList<int>(integers);
|
||||||
var testList = new ObservableList<int>(integers);
|
|
||||||
|
CollectionAssert.AreEqual(integers, testList);
|
||||||
CollectionAssert.AreEqual(integers, testList);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Mock object factory</summary>
|
||||||
/// <summary>Mock object factory</summary>
|
private MockFactory mockery;
|
||||||
private MockFactory mockery;
|
/// <summary>The mocked observable collection subscriber</summary>
|
||||||
/// <summary>The mocked observable collection subscriber</summary>
|
private Mock<IObservableCollectionSubscriber> mockedSubscriber;
|
||||||
private Mock<IObservableCollectionSubscriber> mockedSubscriber;
|
/// <summary>An observable collection to which a mock will be subscribed</summary>
|
||||||
/// <summary>An observable collection to which a mock will be subscribed</summary>
|
private ObservableList<int> observedList;
|
||||||
private ObservableList<int> observedList;
|
|
||||||
|
}
|
||||||
}
|
|
||||||
|
} // namespace Nuclex.Support.Collections
|
||||||
} // namespace Nuclex.Support.Collections
|
|
||||||
|
#endif // UNITTEST
|
||||||
#endif // UNITTEST
|
|
||||||
|
#endif // !NO_NMOCK
|
||||||
#endif // !NO_NMOCK
|
|
||||||
|
|
|
@ -1,361 +1,360 @@
|
||||||
#region CPL License
|
#region Apache License 2.0
|
||||||
/*
|
/*
|
||||||
Nuclex Framework
|
Nuclex .NET Framework
|
||||||
Copyright (C) 2002-2017 Nuclex Development Labs
|
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
|
||||||
|
|
||||||
This library is free software; you can redistribute it and/or
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
modify it under the terms of the IBM Common Public License as
|
you may not use this file except in compliance with the License.
|
||||||
published by the IBM Corporation; either version 1.0 of the
|
You may obtain a copy of the License at
|
||||||
License, or (at your option) any later version.
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
This library is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
Unless required by applicable law or agreed to in writing, software
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
IBM Common Public License for more details.
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
You should have received a copy of the IBM Common Public
|
limitations under the License.
|
||||||
License along with this library
|
*/
|
||||||
*/
|
#endregion // Apache License 2.0
|
||||||
#endregion
|
|
||||||
|
using System;
|
||||||
using System;
|
using System.Collections;
|
||||||
using System.Collections;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Generic;
|
|
||||||
|
#if !NO_SPECIALIZED_COLLECTIONS
|
||||||
#if !NO_SPECIALIZED_COLLECTIONS
|
using System.Collections.Specialized;
|
||||||
using System.Collections.Specialized;
|
#endif
|
||||||
#endif
|
|
||||||
|
namespace Nuclex.Support.Collections {
|
||||||
namespace Nuclex.Support.Collections {
|
|
||||||
|
/// <summary>List which fires events when items are added or removed</summary>
|
||||||
/// <summary>List which fires events when items are added or removed</summary>
|
/// <typeparam name="TItem">Type of items the collection manages</typeparam>
|
||||||
/// <typeparam name="TItem">Type of items the collection manages</typeparam>
|
public class ObservableList<TItem> :
|
||||||
public class ObservableList<TItem> :
|
IList<TItem>,
|
||||||
IList<TItem>,
|
IList,
|
||||||
IList,
|
ICollection,
|
||||||
ICollection,
|
#if !NO_SPECIALIZED_COLLECTIONS
|
||||||
#if !NO_SPECIALIZED_COLLECTIONS
|
INotifyCollectionChanged,
|
||||||
INotifyCollectionChanged,
|
#endif
|
||||||
#endif
|
IObservableCollection<TItem> {
|
||||||
IObservableCollection<TItem> {
|
|
||||||
|
/// <summary>Raised when an item has been added to the collection</summary>
|
||||||
/// <summary>Raised when an item has been added to the collection</summary>
|
public event EventHandler<ItemEventArgs<TItem>> ItemAdded;
|
||||||
public event EventHandler<ItemEventArgs<TItem>> ItemAdded;
|
/// <summary>Raised when an item is removed from the collection</summary>
|
||||||
/// <summary>Raised when an item is removed from the collection</summary>
|
public event EventHandler<ItemEventArgs<TItem>> ItemRemoved;
|
||||||
public event EventHandler<ItemEventArgs<TItem>> ItemRemoved;
|
/// <summary>Raised when an item is replaced in the collection</summary>
|
||||||
/// <summary>Raised when an item is replaced in the collection</summary>
|
public event EventHandler<ItemReplaceEventArgs<TItem>> ItemReplaced;
|
||||||
public event EventHandler<ItemReplaceEventArgs<TItem>> ItemReplaced;
|
/// <summary>Raised when the collection is about to be cleared</summary>
|
||||||
/// <summary>Raised when the collection is about to be cleared</summary>
|
/// <remarks>
|
||||||
/// <remarks>
|
/// This could be covered by calling ItemRemoved for each item currently
|
||||||
/// This could be covered by calling ItemRemoved for each item currently
|
/// contained in the collection, but it is often simpler and more efficient
|
||||||
/// contained in the collection, but it is often simpler and more efficient
|
/// to process the clearing of the entire collection as a special operation.
|
||||||
/// to process the clearing of the entire collection as a special operation.
|
/// </remarks>
|
||||||
/// </remarks>
|
public event EventHandler Clearing;
|
||||||
public event EventHandler Clearing;
|
/// <summary>Raised when the collection has been cleared</summary>
|
||||||
/// <summary>Raised when the collection has been cleared</summary>
|
public event EventHandler Cleared;
|
||||||
public event EventHandler Cleared;
|
|
||||||
|
#if !NO_SPECIALIZED_COLLECTIONS
|
||||||
#if !NO_SPECIALIZED_COLLECTIONS
|
/// <summary>Called when the collection has changed</summary>
|
||||||
/// <summary>Called when the collection has changed</summary>
|
public event NotifyCollectionChangedEventHandler CollectionChanged;
|
||||||
public event NotifyCollectionChangedEventHandler CollectionChanged;
|
#endif
|
||||||
#endif
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Initializes a new instance of the ObservableList class that is empty.
|
||||||
/// Initializes a new instance of the ObservableList class that is empty.
|
/// </summary>
|
||||||
/// </summary>
|
public ObservableList() : this(new List<TItem>()) { }
|
||||||
public ObservableList() : this(new List<TItem>()) { }
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Initializes a new instance of the ObservableList class as a wrapper
|
||||||
/// Initializes a new instance of the ObservableList class as a wrapper
|
/// for the specified list.
|
||||||
/// for the specified list.
|
/// </summary>
|
||||||
/// </summary>
|
/// <param name="list">The list that is wrapped by the new collection.</param>
|
||||||
/// <param name="list">The list that is wrapped by the new collection.</param>
|
/// <exception cref="System.ArgumentNullException">List is null</exception>
|
||||||
/// <exception cref="System.ArgumentNullException">List is null</exception>
|
public ObservableList(IList<TItem> list) {
|
||||||
public ObservableList(IList<TItem> list) {
|
this.typedList = list;
|
||||||
this.typedList = list;
|
this.objectList = list as IList; // Gah!
|
||||||
this.objectList = list as IList; // Gah!
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Determines the index of the specified item in the list</summary>
|
||||||
/// <summary>Determines the index of the specified item in the list</summary>
|
/// <param name="item">Item whose index will be determined</param>
|
||||||
/// <param name="item">Item whose index will be determined</param>
|
/// <returns>The index of the item in the list or -1 if not found</returns>
|
||||||
/// <returns>The index of the item in the list or -1 if not found</returns>
|
public int IndexOf(TItem item) {
|
||||||
public int IndexOf(TItem item) {
|
return this.typedList.IndexOf(item);
|
||||||
return this.typedList.IndexOf(item);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Inserts an item into the list at the specified index</summary>
|
||||||
/// <summary>Inserts an item into the list at the specified index</summary>
|
/// <param name="index">Index the item will be insertted at</param>
|
||||||
/// <param name="index">Index the item will be insertted at</param>
|
/// <param name="item">Item that will be inserted into the list</param>
|
||||||
/// <param name="item">Item that will be inserted into the list</param>
|
public void Insert(int index, TItem item) {
|
||||||
public void Insert(int index, TItem item) {
|
this.typedList.Insert(index, item);
|
||||||
this.typedList.Insert(index, item);
|
OnAdded(item, index);
|
||||||
OnAdded(item, index);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Removes the item at the specified index from the list</summary>
|
||||||
/// <summary>Removes the item at the specified index from the list</summary>
|
/// <param name="index">Index at which the item will be removed</param>
|
||||||
/// <param name="index">Index at which the item will be removed</param>
|
public void RemoveAt(int index) {
|
||||||
public void RemoveAt(int index) {
|
TItem item = this.typedList[index];
|
||||||
TItem item = this.typedList[index];
|
this.typedList.RemoveAt(index);
|
||||||
this.typedList.RemoveAt(index);
|
OnRemoved(item, index);
|
||||||
OnRemoved(item, index);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Accesses the item at the specified index in the list</summary>
|
||||||
/// <summary>Accesses the item at the specified index in the list</summary>
|
/// <param name="index">Index of the item that will be accessed</param>
|
||||||
/// <param name="index">Index of the item that will be accessed</param>
|
/// <returns>The item at the specified index</returns>
|
||||||
/// <returns>The item at the specified index</returns>
|
public TItem this[int index] {
|
||||||
public TItem this[int index] {
|
get { return this.typedList[index]; }
|
||||||
get { return this.typedList[index]; }
|
set {
|
||||||
set {
|
TItem oldItem = this.typedList[index];
|
||||||
TItem oldItem = this.typedList[index];
|
this.typedList[index] = value;
|
||||||
this.typedList[index] = value;
|
OnReplaced(oldItem, value, index);
|
||||||
OnReplaced(oldItem, value, index);
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Adds an item to the end of the list</summary>
|
||||||
/// <summary>Adds an item to the end of the list</summary>
|
/// <param name="item">Item that will be added to the list</param>
|
||||||
/// <param name="item">Item that will be added to the list</param>
|
public void Add(TItem item) {
|
||||||
public void Add(TItem item) {
|
this.typedList.Add(item);
|
||||||
this.typedList.Add(item);
|
OnAdded(item, this.typedList.Count - 1);
|
||||||
OnAdded(item, this.typedList.Count - 1);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Removes all items from the list</summary>
|
||||||
/// <summary>Removes all items from the list</summary>
|
public void Clear() {
|
||||||
public void Clear() {
|
OnClearing();
|
||||||
OnClearing();
|
this.typedList.Clear();
|
||||||
this.typedList.Clear();
|
OnCleared();
|
||||||
OnCleared();
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Checks whether the list contains the specified item</summary>
|
||||||
/// <summary>Checks whether the list contains the specified item</summary>
|
/// <param name="item">Item the list will be checked for</param>
|
||||||
/// <param name="item">Item the list will be checked for</param>
|
/// <returns>True if the list contains the specified items</returns>
|
||||||
/// <returns>True if the list contains the specified items</returns>
|
public bool Contains(TItem item) {
|
||||||
public bool Contains(TItem item) {
|
return this.typedList.Contains(item);
|
||||||
return this.typedList.Contains(item);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Copies the contents of the list into an array</summary>
|
||||||
/// <summary>Copies the contents of the list into an array</summary>
|
/// <param name="array">Array the list will be copied into</param>
|
||||||
/// <param name="array">Array the list will be copied into</param>
|
/// <param name="arrayIndex">
|
||||||
/// <param name="arrayIndex">
|
/// Index in the target array where the first item will be copied to
|
||||||
/// Index in the target array where the first item will be copied to
|
/// </param>
|
||||||
/// </param>
|
public void CopyTo(TItem[] array, int arrayIndex) {
|
||||||
public void CopyTo(TItem[] array, int arrayIndex) {
|
this.typedList.CopyTo(array, arrayIndex);
|
||||||
this.typedList.CopyTo(array, arrayIndex);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Total number of items in the list</summary>
|
||||||
/// <summary>Total number of items in the list</summary>
|
public int Count {
|
||||||
public int Count {
|
get { return this.typedList.Count; }
|
||||||
get { return this.typedList.Count; }
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Whether the list is a read-only list</summary>
|
||||||
/// <summary>Whether the list is a read-only list</summary>
|
public bool IsReadOnly {
|
||||||
public bool IsReadOnly {
|
get { return this.typedList.IsReadOnly; }
|
||||||
get { return this.typedList.IsReadOnly; }
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Removes the specified item from the list</summary>
|
||||||
/// <summary>Removes the specified item from the list</summary>
|
/// <param name="item">Item that will be removed from the list</param>
|
||||||
/// <param name="item">Item that will be removed from the list</param>
|
/// <returns>
|
||||||
/// <returns>
|
/// True if the item was found and removed from the list, false otherwise
|
||||||
/// True if the item was found and removed from the list, false otherwise
|
/// </returns>
|
||||||
/// </returns>
|
public bool Remove(TItem item) {
|
||||||
public bool Remove(TItem item) {
|
int index = this.typedList.IndexOf(item);
|
||||||
int index = this.typedList.IndexOf(item);
|
if(index == -1) {
|
||||||
if(index == -1) {
|
return false;
|
||||||
return false;
|
}
|
||||||
}
|
|
||||||
|
TItem removedItem = this.typedList[index];
|
||||||
TItem removedItem = this.typedList[index];
|
this.typedList.RemoveAt(index);
|
||||||
this.typedList.RemoveAt(index);
|
OnRemoved(removedItem, index);
|
||||||
OnRemoved(removedItem, index);
|
|
||||||
|
return true;
|
||||||
return true;
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Returns an enumerator for the items in the list</summary>
|
||||||
/// <summary>Returns an enumerator for the items in the list</summary>
|
/// <returns>An enumerator for the list's items</returns>
|
||||||
/// <returns>An enumerator for the list's items</returns>
|
public IEnumerator<TItem> GetEnumerator() {
|
||||||
public IEnumerator<TItem> GetEnumerator() {
|
return this.typedList.GetEnumerator();
|
||||||
return this.typedList.GetEnumerator();
|
}
|
||||||
}
|
|
||||||
|
#region IEnumerable implementation
|
||||||
#region IEnumerable implementation
|
|
||||||
|
/// <summary>Returns an enumerator for the items in the list</summary>
|
||||||
/// <summary>Returns an enumerator for the items in the list</summary>
|
/// <returns>An enumerator for the list's items</returns>
|
||||||
/// <returns>An enumerator for the list's items</returns>
|
IEnumerator IEnumerable.GetEnumerator() {
|
||||||
IEnumerator IEnumerable.GetEnumerator() {
|
return this.objectList.GetEnumerator();
|
||||||
return this.objectList.GetEnumerator();
|
}
|
||||||
}
|
|
||||||
|
#endregion // IEnumerable implementation
|
||||||
#endregion // IEnumerable implementation
|
|
||||||
|
#region ICollection implementation
|
||||||
#region ICollection implementation
|
|
||||||
|
/// <summary>Copies the contents of the list into an array</summary>
|
||||||
/// <summary>Copies the contents of the list into an array</summary>
|
/// <param name="array">Array the list will be copied into</param>
|
||||||
/// <param name="array">Array the list will be copied into</param>
|
/// <param name="arrayIndex">
|
||||||
/// <param name="arrayIndex">
|
/// Index in the target array where the first item will be copied to
|
||||||
/// Index in the target array where the first item will be copied to
|
/// </param>
|
||||||
/// </param>
|
void ICollection.CopyTo(Array array, int arrayIndex) {
|
||||||
void ICollection.CopyTo(Array array, int arrayIndex) {
|
this.objectList.CopyTo(array, arrayIndex);
|
||||||
this.objectList.CopyTo(array, arrayIndex);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Whether this list performs thread synchronization</summary>
|
||||||
/// <summary>Whether this list performs thread synchronization</summary>
|
bool ICollection.IsSynchronized {
|
||||||
bool ICollection.IsSynchronized {
|
get { return this.objectList.IsSynchronized; }
|
||||||
get { return this.objectList.IsSynchronized; }
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Synchronization root used by the list to synchronize threads</summary>
|
||||||
/// <summary>Synchronization root used by the list to synchronize threads</summary>
|
object ICollection.SyncRoot {
|
||||||
object ICollection.SyncRoot {
|
get { return this.objectList.SyncRoot; }
|
||||||
get { return this.objectList.SyncRoot; }
|
}
|
||||||
}
|
|
||||||
|
#endregion // ICollection implementation
|
||||||
#endregion // ICollection implementation
|
|
||||||
|
#region IList implementation
|
||||||
#region IList implementation
|
|
||||||
|
/// <summary>Adds an item to the list</summary>
|
||||||
/// <summary>Adds an item to the list</summary>
|
/// <param name="value">Item that will be added to the list</param>
|
||||||
/// <param name="value">Item that will be added to the list</param>
|
/// <returns>
|
||||||
/// <returns>
|
/// The position at which the item has been inserted or -1 if the item was not inserted
|
||||||
/// The position at which the item has been inserted or -1 if the item was not inserted
|
/// </returns>
|
||||||
/// </returns>
|
int IList.Add(object value) {
|
||||||
int IList.Add(object value) {
|
int index = this.objectList.Add(value);
|
||||||
int index = this.objectList.Add(value);
|
TItem addedItem = this.typedList[index];
|
||||||
TItem addedItem = this.typedList[index];
|
OnAdded(addedItem, index);
|
||||||
OnAdded(addedItem, index);
|
return index;
|
||||||
return index;
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Checks whether the list contains the specified item</summary>
|
||||||
/// <summary>Checks whether the list contains the specified item</summary>
|
/// <param name="item">Item the list will be checked for</param>
|
||||||
/// <param name="item">Item the list will be checked for</param>
|
/// <returns>True if the list contains the specified items</returns>
|
||||||
/// <returns>True if the list contains the specified items</returns>
|
bool IList.Contains(object item) {
|
||||||
bool IList.Contains(object item) {
|
return this.objectList.Contains(item);
|
||||||
return this.objectList.Contains(item);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Determines the index of the specified item in the list</summary>
|
||||||
/// <summary>Determines the index of the specified item in the list</summary>
|
/// <param name="item">Item whose index will be determined</param>
|
||||||
/// <param name="item">Item whose index will be determined</param>
|
/// <returns>The index of the item in the list or -1 if not found</returns>
|
||||||
/// <returns>The index of the item in the list or -1 if not found</returns>
|
int IList.IndexOf(object item) {
|
||||||
int IList.IndexOf(object item) {
|
return this.objectList.IndexOf(item);
|
||||||
return this.objectList.IndexOf(item);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Inserts an item into the list at the specified index</summary>
|
||||||
/// <summary>Inserts an item into the list at the specified index</summary>
|
/// <param name="index">Index the item will be insertted at</param>
|
||||||
/// <param name="index">Index the item will be insertted at</param>
|
/// <param name="item">Item that will be inserted into the list</param>
|
||||||
/// <param name="item">Item that will be inserted into the list</param>
|
void IList.Insert(int index, object item) {
|
||||||
void IList.Insert(int index, object item) {
|
this.objectList.Insert(index, item);
|
||||||
this.objectList.Insert(index, item);
|
TItem addedItem = this.typedList[index];
|
||||||
TItem addedItem = this.typedList[index];
|
OnAdded(addedItem, index);
|
||||||
OnAdded(addedItem, index);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Whether the list is of a fixed size</summary>
|
||||||
/// <summary>Whether the list is of a fixed size</summary>
|
bool IList.IsFixedSize {
|
||||||
bool IList.IsFixedSize {
|
get { return this.objectList.IsFixedSize; }
|
||||||
get { return this.objectList.IsFixedSize; }
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Removes the specified item from the list</summary>
|
||||||
/// <summary>Removes the specified item from the list</summary>
|
/// <param name="item">Item that will be removed from the list</param>
|
||||||
/// <param name="item">Item that will be removed from the list</param>
|
void IList.Remove(object item) {
|
||||||
void IList.Remove(object item) {
|
int index = this.objectList.IndexOf(item);
|
||||||
int index = this.objectList.IndexOf(item);
|
if(index == -1) {
|
||||||
if(index == -1) {
|
return;
|
||||||
return;
|
}
|
||||||
}
|
|
||||||
|
TItem removedItem = this.typedList[index];
|
||||||
TItem removedItem = this.typedList[index];
|
this.objectList.RemoveAt(index);
|
||||||
this.objectList.RemoveAt(index);
|
OnRemoved(removedItem, index);
|
||||||
OnRemoved(removedItem, index);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Accesses the item at the specified index in the list</summary>
|
||||||
/// <summary>Accesses the item at the specified index in the list</summary>
|
/// <param name="index">Index of the item that will be accessed</param>
|
||||||
/// <param name="index">Index of the item that will be accessed</param>
|
/// <returns>The item at the specified index</returns>
|
||||||
/// <returns>The item at the specified index</returns>
|
object IList.this[int index] {
|
||||||
object IList.this[int index] {
|
get { return this.objectList[index]; }
|
||||||
get { return this.objectList[index]; }
|
set {
|
||||||
set {
|
TItem oldItem = this.typedList[index];
|
||||||
TItem oldItem = this.typedList[index];
|
this.objectList[index] = value;
|
||||||
this.objectList[index] = value;
|
TItem newItem = this.typedList[index];
|
||||||
TItem newItem = this.typedList[index];
|
OnReplaced(oldItem, newItem, index);
|
||||||
OnReplaced(oldItem, newItem, index);
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
#endregion // IList implementation
|
||||||
#endregion // IList implementation
|
|
||||||
|
/// <summary>Fires the 'ItemAdded' event</summary>
|
||||||
/// <summary>Fires the 'ItemAdded' event</summary>
|
/// <param name="item">Item that has been added to the collection</param>
|
||||||
/// <param name="item">Item that has been added to the collection</param>
|
/// <param name="index">Index of the added item</param>
|
||||||
/// <param name="index">Index of the added item</param>
|
protected virtual void OnAdded(TItem item, int index) {
|
||||||
protected virtual void OnAdded(TItem item, int index) {
|
if(ItemAdded != null) {
|
||||||
if(ItemAdded != null) {
|
ItemAdded(this, new ItemEventArgs<TItem>(item));
|
||||||
ItemAdded(this, new ItemEventArgs<TItem>(item));
|
}
|
||||||
}
|
#if !NO_SPECIALIZED_COLLECTIONS
|
||||||
#if !NO_SPECIALIZED_COLLECTIONS
|
if(CollectionChanged != null) {
|
||||||
if(CollectionChanged != null) {
|
CollectionChanged(
|
||||||
CollectionChanged(
|
this,
|
||||||
this,
|
new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item, index)
|
||||||
new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item, index)
|
);
|
||||||
);
|
}
|
||||||
}
|
#endif
|
||||||
#endif
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Fires the 'ItemRemoved' event</summary>
|
||||||
/// <summary>Fires the 'ItemRemoved' event</summary>
|
/// <param name="item">Item that has been removed from the collection</param>
|
||||||
/// <param name="item">Item that has been removed from the collection</param>
|
/// <param name="index">Index the item has been removed from</param>
|
||||||
/// <param name="index">Index the item has been removed from</param>
|
protected virtual void OnRemoved(TItem item, int index) {
|
||||||
protected virtual void OnRemoved(TItem item, int index) {
|
if(ItemRemoved != null) {
|
||||||
if(ItemRemoved != null) {
|
ItemRemoved(this, new ItemEventArgs<TItem>(item));
|
||||||
ItemRemoved(this, new ItemEventArgs<TItem>(item));
|
}
|
||||||
}
|
#if !NO_SPECIALIZED_COLLECTIONS
|
||||||
#if !NO_SPECIALIZED_COLLECTIONS
|
if(CollectionChanged != null) {
|
||||||
if(CollectionChanged != null) {
|
CollectionChanged(
|
||||||
CollectionChanged(
|
this,
|
||||||
this,
|
new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, item, index)
|
||||||
new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, item, index)
|
);
|
||||||
);
|
}
|
||||||
}
|
#endif
|
||||||
#endif
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Fires the 'ItemReplaced' event</summary>
|
||||||
/// <summary>Fires the 'ItemReplaced' event</summary>
|
/// <param name="oldItem">Item that has been replaced</param>
|
||||||
/// <param name="oldItem">Item that has been replaced</param>
|
/// <param name="newItem">New item the original item was replaced with</param>
|
||||||
/// <param name="newItem">New item the original item was replaced with</param>
|
/// <param name="index">Index of the replaced item</param>
|
||||||
/// <param name="index">Index of the replaced item</param>
|
protected virtual void OnReplaced(TItem oldItem, TItem newItem, int index) {
|
||||||
protected virtual void OnReplaced(TItem oldItem, TItem newItem, int index) {
|
if(ItemReplaced != null) {
|
||||||
if(ItemReplaced != null) {
|
ItemReplaced(this, new ItemReplaceEventArgs<TItem>(oldItem, newItem));
|
||||||
ItemReplaced(this, new ItemReplaceEventArgs<TItem>(oldItem, newItem));
|
}
|
||||||
}
|
#if !NO_SPECIALIZED_COLLECTIONS
|
||||||
#if !NO_SPECIALIZED_COLLECTIONS
|
if(CollectionChanged != null) {
|
||||||
if(CollectionChanged != null) {
|
CollectionChanged(
|
||||||
CollectionChanged(
|
this,
|
||||||
this,
|
new NotifyCollectionChangedEventArgs(
|
||||||
new NotifyCollectionChangedEventArgs(
|
NotifyCollectionChangedAction.Replace, newItem, oldItem, index
|
||||||
NotifyCollectionChangedAction.Replace, newItem, oldItem, index
|
)
|
||||||
)
|
);
|
||||||
);
|
}
|
||||||
}
|
#endif
|
||||||
#endif
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Fires the 'Clearing' event</summary>
|
||||||
/// <summary>Fires the 'Clearing' event</summary>
|
protected virtual void OnClearing() {
|
||||||
protected virtual void OnClearing() {
|
if(Clearing != null) {
|
||||||
if(Clearing != null) {
|
Clearing(this, EventArgs.Empty);
|
||||||
Clearing(this, EventArgs.Empty);
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Fires the 'Cleared' event</summary>
|
||||||
/// <summary>Fires the 'Cleared' event</summary>
|
protected virtual void OnCleared() {
|
||||||
protected virtual void OnCleared() {
|
if(Cleared != null) {
|
||||||
if(Cleared != null) {
|
Cleared(this, EventArgs.Empty);
|
||||||
Cleared(this, EventArgs.Empty);
|
}
|
||||||
}
|
#if !NO_SPECIALIZED_COLLECTIONS
|
||||||
#if !NO_SPECIALIZED_COLLECTIONS
|
if(CollectionChanged != null) {
|
||||||
if(CollectionChanged != null) {
|
CollectionChanged(this, Constants.NotifyCollectionResetEventArgs);
|
||||||
CollectionChanged(this, Constants.NotifyCollectionResetEventArgs);
|
}
|
||||||
}
|
#endif
|
||||||
#endif
|
}
|
||||||
}
|
|
||||||
|
/// <summary>The wrapped list under its type-safe interface</summary>
|
||||||
/// <summary>The wrapped list under its type-safe interface</summary>
|
private IList<TItem> typedList;
|
||||||
private IList<TItem> typedList;
|
/// <summary>The wrapped list under its object interface</summary>
|
||||||
/// <summary>The wrapped list under its object interface</summary>
|
private IList objectList;
|
||||||
private IList objectList;
|
|
||||||
|
}
|
||||||
}
|
|
||||||
|
} // namespace Nuclex.Support.Collections
|
||||||
} // namespace Nuclex.Support.Collections
|
|
||||||
|
|
|
@ -1,301 +1,300 @@
|
||||||
#region CPL License
|
#region Apache License 2.0
|
||||||
/*
|
/*
|
||||||
Nuclex Framework
|
Nuclex .NET Framework
|
||||||
Copyright (C) 2002-2017 Nuclex Development Labs
|
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
|
||||||
|
|
||||||
This library is free software; you can redistribute it and/or
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
modify it under the terms of the IBM Common Public License as
|
you may not use this file except in compliance with the License.
|
||||||
published by the IBM Corporation; either version 1.0 of the
|
You may obtain a copy of the License at
|
||||||
License, or (at your option) any later version.
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
This library is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
Unless required by applicable law or agreed to in writing, software
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
IBM Common Public License for more details.
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
You should have received a copy of the IBM Common Public
|
limitations under the License.
|
||||||
License along with this library
|
*/
|
||||||
*/
|
#endregion // Apache License 2.0
|
||||||
#endregion
|
|
||||||
|
#if !NO_SETS
|
||||||
#if !NO_SETS
|
|
||||||
|
#if UNITTEST
|
||||||
#if UNITTEST
|
|
||||||
|
using System;
|
||||||
using System;
|
using System.Collections;
|
||||||
using System.Collections;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Generic;
|
using System.IO;
|
||||||
using System.IO;
|
using System.Runtime.Serialization.Formatters.Binary;
|
||||||
using System.Runtime.Serialization.Formatters.Binary;
|
|
||||||
|
using NUnit.Framework;
|
||||||
using NUnit.Framework;
|
using NMock;
|
||||||
using NMock;
|
|
||||||
|
namespace Nuclex.Support.Collections {
|
||||||
namespace Nuclex.Support.Collections {
|
|
||||||
|
/// <summary>Unit Test for the observable set wrapper</summary>
|
||||||
/// <summary>Unit Test for the observable set wrapper</summary>
|
[TestFixture]
|
||||||
[TestFixture]
|
internal class ObservableSetTest {
|
||||||
internal class ObservableSetTest {
|
|
||||||
|
#region interface IObservableCollectionSubscriber<TItem>
|
||||||
#region interface IObservableCollectionSubscriber<TItem>
|
|
||||||
|
public interface IObservableCollectionSubscriber<TItem> {
|
||||||
public interface IObservableCollectionSubscriber<TItem> {
|
|
||||||
|
/// <summary>Called when an item has been added to the collection</summary>
|
||||||
/// <summary>Called when an item has been added to the collection</summary>
|
void ItemAdded(object sender, ItemEventArgs<TItem> arguments);
|
||||||
void ItemAdded(object sender, ItemEventArgs<TItem> arguments);
|
/// <summary>Called when an item is removed from the collection</summary>
|
||||||
/// <summary>Called when an item is removed from the collection</summary>
|
void ItemRemoved(object sender, ItemEventArgs<TItem> arguments);
|
||||||
void ItemRemoved(object sender, ItemEventArgs<TItem> arguments);
|
/// <summary>Called when an item is replaced in the collection</summary>
|
||||||
/// <summary>Called when an item is replaced in the collection</summary>
|
void ItemReplaced(object sender, ItemReplaceEventArgs<TItem> arguments);
|
||||||
void ItemReplaced(object sender, ItemReplaceEventArgs<TItem> arguments);
|
/// <summary>Called when the collection is about to be cleared</summary>
|
||||||
/// <summary>Called when the collection is about to be cleared</summary>
|
void Clearing(object sender, EventArgs arguments);
|
||||||
void Clearing(object sender, EventArgs arguments);
|
/// <summary>Called when the collection has been cleared</summary>
|
||||||
/// <summary>Called when the collection has been cleared</summary>
|
void Cleared(object sender, EventArgs arguments);
|
||||||
void Cleared(object sender, EventArgs arguments);
|
|
||||||
|
}
|
||||||
}
|
|
||||||
|
#endregion // interface IObservableCollectionSubscriber<TItem>
|
||||||
#endregion // interface IObservableCollectionSubscriber<TItem>
|
|
||||||
|
/// <summary>Called before each test is run</summary>
|
||||||
/// <summary>Called before each test is run</summary>
|
[SetUp]
|
||||||
[SetUp]
|
public void Setup() {
|
||||||
public void Setup() {
|
this.mockFactory = new MockFactory();
|
||||||
this.mockFactory = new MockFactory();
|
this.observableSet = new ObservableSet<int>();
|
||||||
this.observableSet = new ObservableSet<int>();
|
|
||||||
|
this.subscriber = this.mockFactory.CreateMock<IObservableCollectionSubscriber<int>>();
|
||||||
this.subscriber = this.mockFactory.CreateMock<IObservableCollectionSubscriber<int>>();
|
this.observableSet.ItemAdded += this.subscriber.MockObject.ItemAdded;
|
||||||
this.observableSet.ItemAdded += this.subscriber.MockObject.ItemAdded;
|
this.observableSet.ItemRemoved += this.subscriber.MockObject.ItemRemoved;
|
||||||
this.observableSet.ItemRemoved += this.subscriber.MockObject.ItemRemoved;
|
this.observableSet.ItemReplaced += this.subscriber.MockObject.ItemReplaced;
|
||||||
this.observableSet.ItemReplaced += this.subscriber.MockObject.ItemReplaced;
|
this.observableSet.Clearing += this.subscriber.MockObject.Clearing;
|
||||||
this.observableSet.Clearing += this.subscriber.MockObject.Clearing;
|
this.observableSet.Cleared += this.subscriber.MockObject.Cleared;
|
||||||
this.observableSet.Cleared += this.subscriber.MockObject.Cleared;
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Called after each test has run</summary>
|
||||||
/// <summary>Called after each test has run</summary>
|
[TearDown]
|
||||||
[TearDown]
|
public void Teardown() {
|
||||||
public void Teardown() {
|
if(this.mockFactory != null) {
|
||||||
if(this.mockFactory != null) {
|
this.mockFactory.VerifyAllExpectationsHaveBeenMet();
|
||||||
this.mockFactory.VerifyAllExpectationsHaveBeenMet();
|
|
||||||
|
this.subscriber = null;
|
||||||
this.subscriber = null;
|
this.mockFactory.Dispose();
|
||||||
this.mockFactory.Dispose();
|
this.mockFactory = null;
|
||||||
this.mockFactory = null;
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Verifies that the observable set has a default constructor
|
||||||
/// Verifies that the observable set has a default constructor
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void HasDefaultConstructor() {
|
||||||
public void HasDefaultConstructor() {
|
Assert.IsNotNull(new ObservableSet<int>());
|
||||||
Assert.IsNotNull(new ObservableSet<int>());
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Verifies that adding items to the set triggers the 'ItemAdded' event
|
||||||
/// Verifies that adding items to the set triggers the 'ItemAdded' event
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void AddingItemsTriggersEvent() {
|
||||||
public void AddingItemsTriggersEvent() {
|
this.subscriber.Expects.One.Method((s) => s.ItemAdded(null, null)).WithAnyArguments();
|
||||||
this.subscriber.Expects.One.Method((s) => s.ItemAdded(null, null)).WithAnyArguments();
|
this.observableSet.Add(123);
|
||||||
this.observableSet.Add(123);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Verifies that removing items from the set triggers the 'ItemRemoved' event
|
||||||
/// Verifies that removing items from the set triggers the 'ItemRemoved' event
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void RemovingItemsTriggersEvent() {
|
||||||
public void RemovingItemsTriggersEvent() {
|
this.subscriber.Expects.One.Method((s) => s.ItemAdded(null, null)).WithAnyArguments();
|
||||||
this.subscriber.Expects.One.Method((s) => s.ItemAdded(null, null)).WithAnyArguments();
|
this.observableSet.Add(123);
|
||||||
this.observableSet.Add(123);
|
|
||||||
|
this.subscriber.Expects.One.Method((s) => s.ItemRemoved(null, null)).WithAnyArguments();
|
||||||
this.subscriber.Expects.One.Method((s) => s.ItemRemoved(null, null)).WithAnyArguments();
|
this.observableSet.Remove(123);
|
||||||
this.observableSet.Remove(123);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Verifies that adding items to the set triggers the 'ItemAdded' event
|
||||||
/// Verifies that adding items to the set triggers the 'ItemAdded' event
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void AddingAlreadyContainedItemDoesNotTriggerEvent() {
|
||||||
public void AddingAlreadyContainedItemDoesNotTriggerEvent() {
|
this.subscriber.Expects.One.Method((s) => s.ItemAdded(null, null)).WithAnyArguments();
|
||||||
this.subscriber.Expects.One.Method((s) => s.ItemAdded(null, null)).WithAnyArguments();
|
this.observableSet.Add(123);
|
||||||
this.observableSet.Add(123);
|
|
||||||
|
this.subscriber.Expects.No.Method((s) => s.ItemAdded(null, null)).WithAnyArguments();
|
||||||
this.subscriber.Expects.No.Method((s) => s.ItemAdded(null, null)).WithAnyArguments();
|
this.observableSet.Add(123);
|
||||||
this.observableSet.Add(123);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Verifies that excepting the set with itself empties the set
|
||||||
/// Verifies that excepting the set with itself empties the set
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void ExceptWithSelfEmptiesSet() {
|
||||||
public void ExceptWithSelfEmptiesSet() {
|
this.subscriber.Expects.Exactly(3).Method(
|
||||||
this.subscriber.Expects.Exactly(3).Method(
|
(s) => s.ItemAdded(null, null)
|
||||||
(s) => s.ItemAdded(null, null)
|
).WithAnyArguments();
|
||||||
).WithAnyArguments();
|
|
||||||
|
this.observableSet.Add(1);
|
||||||
this.observableSet.Add(1);
|
this.observableSet.Add(2);
|
||||||
this.observableSet.Add(2);
|
this.observableSet.Add(3);
|
||||||
this.observableSet.Add(3);
|
|
||||||
|
Assert.AreEqual(3, this.observableSet.Count);
|
||||||
Assert.AreEqual(3, this.observableSet.Count);
|
|
||||||
|
this.subscriber.Expects.One.Method((s) => s.Clearing(null, null)).WithAnyArguments();
|
||||||
this.subscriber.Expects.One.Method((s) => s.Clearing(null, null)).WithAnyArguments();
|
this.subscriber.Expects.One.Method((s) => s.Cleared(null, null)).WithAnyArguments();
|
||||||
this.subscriber.Expects.One.Method((s) => s.Cleared(null, null)).WithAnyArguments();
|
|
||||||
|
this.observableSet.ExceptWith(this.observableSet);
|
||||||
this.observableSet.ExceptWith(this.observableSet);
|
Assert.AreEqual(0, this.observableSet.Count);
|
||||||
Assert.AreEqual(0, this.observableSet.Count);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Verifies that a set can be excepted with a collection
|
||||||
/// Verifies that a set can be excepted with a collection
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void SetCanBeExceptedWithCollection() {
|
||||||
public void SetCanBeExceptedWithCollection() {
|
this.subscriber.Expects.Exactly(2).Method(
|
||||||
this.subscriber.Expects.Exactly(2).Method(
|
(s) => s.ItemAdded(null, null)
|
||||||
(s) => s.ItemAdded(null, null)
|
).WithAnyArguments();
|
||||||
).WithAnyArguments();
|
|
||||||
|
this.observableSet.Add(1);
|
||||||
this.observableSet.Add(1);
|
this.observableSet.Add(2);
|
||||||
this.observableSet.Add(2);
|
|
||||||
|
var collection = new List<int>() { 1 };
|
||||||
var collection = new List<int>() { 1 };
|
|
||||||
|
this.subscriber.Expects.One.Method((s) => s.ItemRemoved(null, null)).WithAnyArguments();
|
||||||
this.subscriber.Expects.One.Method((s) => s.ItemRemoved(null, null)).WithAnyArguments();
|
this.observableSet.ExceptWith(collection);
|
||||||
this.observableSet.ExceptWith(collection);
|
Assert.AreEqual(1, this.observableSet.Count);
|
||||||
Assert.AreEqual(1, this.observableSet.Count);
|
Assert.IsTrue(this.observableSet.Contains(2));
|
||||||
Assert.IsTrue(this.observableSet.Contains(2));
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Verifies that a set can be intersected with a collection
|
||||||
/// Verifies that a set can be intersected with a collection
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void SetCanBeIntersectedWithCollection() {
|
||||||
public void SetCanBeIntersectedWithCollection() {
|
this.subscriber.Expects.Exactly(2).Method(
|
||||||
this.subscriber.Expects.Exactly(2).Method(
|
(s) => s.ItemAdded(null, null)
|
||||||
(s) => s.ItemAdded(null, null)
|
).WithAnyArguments();
|
||||||
).WithAnyArguments();
|
|
||||||
|
this.observableSet.Add(1);
|
||||||
this.observableSet.Add(1);
|
this.observableSet.Add(2);
|
||||||
this.observableSet.Add(2);
|
|
||||||
|
var collection = new List<int>() { 1 };
|
||||||
var collection = new List<int>() { 1 };
|
|
||||||
|
this.subscriber.Expects.One.Method((s) => s.ItemRemoved(null, null)).WithAnyArguments();
|
||||||
this.subscriber.Expects.One.Method((s) => s.ItemRemoved(null, null)).WithAnyArguments();
|
this.observableSet.IntersectWith(collection);
|
||||||
this.observableSet.IntersectWith(collection);
|
Assert.AreEqual(1, this.observableSet.Count);
|
||||||
Assert.AreEqual(1, this.observableSet.Count);
|
Assert.IsTrue(this.observableSet.Contains(1));
|
||||||
Assert.IsTrue(this.observableSet.Contains(1));
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Verifies that it's possible to determine whether a set is a proper subset
|
||||||
/// Verifies that it's possible to determine whether a set is a proper subset
|
/// or superset of another set
|
||||||
/// or superset of another set
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void CanDetermineProperSubsetAndSuperset() {
|
||||||
public void CanDetermineProperSubsetAndSuperset() {
|
var set1 = new ObservableSet<int>() { 1, 2, 3 };
|
||||||
var set1 = new ObservableSet<int>() { 1, 2, 3 };
|
var set2 = new HashSet<int>() { 1, 3 };
|
||||||
var set2 = new HashSet<int>() { 1, 3 };
|
|
||||||
|
Assert.IsTrue(set1.IsProperSupersetOf(set2));
|
||||||
Assert.IsTrue(set1.IsProperSupersetOf(set2));
|
Assert.IsTrue(set2.IsProperSubsetOf(set1));
|
||||||
Assert.IsTrue(set2.IsProperSubsetOf(set1));
|
|
||||||
|
set2.Add(2);
|
||||||
set2.Add(2);
|
|
||||||
|
Assert.IsFalse(set1.IsProperSupersetOf(set2));
|
||||||
Assert.IsFalse(set1.IsProperSupersetOf(set2));
|
Assert.IsFalse(set2.IsProperSubsetOf(set1));
|
||||||
Assert.IsFalse(set2.IsProperSubsetOf(set1));
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Verifies that it's possible to determine whether a set is a subset
|
||||||
/// Verifies that it's possible to determine whether a set is a subset
|
/// or a superset of another set
|
||||||
/// or a superset of another set
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void CanDetermineSubsetAndSuperset() {
|
||||||
public void CanDetermineSubsetAndSuperset() {
|
var set1 = new ObservableSet<int>() { 1, 2, 3 };
|
||||||
var set1 = new ObservableSet<int>() { 1, 2, 3 };
|
var set2 = new HashSet<int>() { 1, 2, 3 };
|
||||||
var set2 = new HashSet<int>() { 1, 2, 3 };
|
|
||||||
|
Assert.IsTrue(set1.IsSupersetOf(set2));
|
||||||
Assert.IsTrue(set1.IsSupersetOf(set2));
|
Assert.IsTrue(set2.IsSubsetOf(set1));
|
||||||
Assert.IsTrue(set2.IsSubsetOf(set1));
|
|
||||||
|
set2.Add(4);
|
||||||
set2.Add(4);
|
|
||||||
|
Assert.IsFalse(set1.IsSupersetOf(set2));
|
||||||
Assert.IsFalse(set1.IsSupersetOf(set2));
|
Assert.IsFalse(set2.IsSubsetOf(set1));
|
||||||
Assert.IsFalse(set2.IsSubsetOf(set1));
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Verifies that a set can determine if another set overlaps with it
|
||||||
/// Verifies that a set can determine if another set overlaps with it
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void CanDetermineOverlap() {
|
||||||
public void CanDetermineOverlap() {
|
var set1 = new ObservableSet<int>() { 1, 3, 5 };
|
||||||
var set1 = new ObservableSet<int>() { 1, 3, 5 };
|
var set2 = new HashSet<int>() { 3 };
|
||||||
var set2 = new HashSet<int>() { 3 };
|
|
||||||
|
Assert.IsTrue(set1.Overlaps(set2));
|
||||||
Assert.IsTrue(set1.Overlaps(set2));
|
Assert.IsTrue(set2.Overlaps(set1));
|
||||||
Assert.IsTrue(set2.Overlaps(set1));
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Verifies that a set can determine if another set contains the same elements
|
||||||
/// Verifies that a set can determine if another set contains the same elements
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void CanDetermineSetEquality() {
|
||||||
public void CanDetermineSetEquality() {
|
var set1 = new ObservableSet<int>() { 1, 3, 5 };
|
||||||
var set1 = new ObservableSet<int>() { 1, 3, 5 };
|
var set2 = new HashSet<int>() { 3, 1, 5 };
|
||||||
var set2 = new HashSet<int>() { 3, 1, 5 };
|
|
||||||
|
Assert.IsTrue(set1.SetEquals(set2));
|
||||||
Assert.IsTrue(set1.SetEquals(set2));
|
Assert.IsTrue(set2.SetEquals(set1));
|
||||||
Assert.IsTrue(set2.SetEquals(set1));
|
|
||||||
|
set1.Add(7);
|
||||||
set1.Add(7);
|
|
||||||
|
Assert.IsFalse(set1.SetEquals(set2));
|
||||||
Assert.IsFalse(set1.SetEquals(set2));
|
Assert.IsFalse(set2.SetEquals(set1));
|
||||||
Assert.IsFalse(set2.SetEquals(set1));
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Verifies that a set can be symmetrically excepted with another set
|
||||||
/// Verifies that a set can be symmetrically excepted with another set
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void CanBeSymmetricallyExcepted() {
|
||||||
public void CanBeSymmetricallyExcepted() {
|
var set1 = new ObservableSet<int>() { 1, 2, 3 };
|
||||||
var set1 = new ObservableSet<int>() { 1, 2, 3 };
|
var set2 = new HashSet<int>() { 3, 4, 5 };
|
||||||
var set2 = new HashSet<int>() { 3, 4, 5 };
|
|
||||||
|
set1.SymmetricExceptWith(set2);
|
||||||
set1.SymmetricExceptWith(set2);
|
|
||||||
|
Assert.AreEqual(4, set1.Count);
|
||||||
Assert.AreEqual(4, set1.Count);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Verifies that a union of two sets can be built
|
||||||
/// Verifies that a union of two sets can be built
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void CanBeUnioned() {
|
||||||
public void CanBeUnioned() {
|
this.subscriber.Expects.Exactly(3).Method(
|
||||||
this.subscriber.Expects.Exactly(3).Method(
|
(s) => s.ItemAdded(null, null)
|
||||||
(s) => s.ItemAdded(null, null)
|
).WithAnyArguments();
|
||||||
).WithAnyArguments();
|
|
||||||
|
this.observableSet.Add(1);
|
||||||
this.observableSet.Add(1);
|
this.observableSet.Add(2);
|
||||||
this.observableSet.Add(2);
|
this.observableSet.Add(3);
|
||||||
this.observableSet.Add(3);
|
|
||||||
|
var set2 = new ObservableSet<int>() { 3, 4, 5 };
|
||||||
var set2 = new ObservableSet<int>() { 3, 4, 5 };
|
|
||||||
|
this.subscriber.Expects.Exactly(2).Method(
|
||||||
this.subscriber.Expects.Exactly(2).Method(
|
(s) => s.ItemAdded(null, null)
|
||||||
(s) => s.ItemAdded(null, null)
|
).WithAnyArguments();
|
||||||
).WithAnyArguments();
|
this.observableSet.UnionWith(set2);
|
||||||
this.observableSet.UnionWith(set2);
|
Assert.AreEqual(5, this.observableSet.Count);
|
||||||
Assert.AreEqual(5, this.observableSet.Count);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Creates mock object for the test</summary>
|
||||||
/// <summary>Creates mock object for the test</summary>
|
private MockFactory mockFactory;
|
||||||
private MockFactory mockFactory;
|
/// <summary>Observable set being tested</summary>
|
||||||
/// <summary>Observable set being tested</summary>
|
private ObservableSet<int> observableSet;
|
||||||
private ObservableSet<int> observableSet;
|
/// <summary>Subscriber for the observable set's events</summary>
|
||||||
/// <summary>Subscriber for the observable set's events</summary>
|
private Mock<IObservableCollectionSubscriber<int>> subscriber;
|
||||||
private Mock<IObservableCollectionSubscriber<int>> subscriber;
|
|
||||||
|
}
|
||||||
}
|
|
||||||
|
} // namespace Nuclex.Support.Collections
|
||||||
} // namespace Nuclex.Support.Collections
|
|
||||||
|
#endif // UNITTEST
|
||||||
#endif // UNITTEST
|
|
||||||
|
#endif // !NO_SETS
|
||||||
#endif // !NO_SETS
|
|
||||||
|
|
|
@ -1,340 +1,339 @@
|
||||||
#region CPL License
|
#region Apache License 2.0
|
||||||
/*
|
/*
|
||||||
Nuclex Framework
|
Nuclex .NET Framework
|
||||||
Copyright (C) 2002-2017 Nuclex Development Labs
|
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
|
||||||
|
|
||||||
This library is free software; you can redistribute it and/or
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
modify it under the terms of the IBM Common Public License as
|
you may not use this file except in compliance with the License.
|
||||||
published by the IBM Corporation; either version 1.0 of the
|
You may obtain a copy of the License at
|
||||||
License, or (at your option) any later version.
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
This library is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
Unless required by applicable law or agreed to in writing, software
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
IBM Common Public License for more details.
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
You should have received a copy of the IBM Common Public
|
limitations under the License.
|
||||||
License along with this library
|
*/
|
||||||
*/
|
#endregion // Apache License 2.0
|
||||||
#endregion
|
|
||||||
|
using System;
|
||||||
using System;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Generic;
|
using System.Collections;
|
||||||
using System.Collections;
|
|
||||||
|
#if !NO_SPECIALIZED_COLLECTIONS
|
||||||
#if !NO_SPECIALIZED_COLLECTIONS
|
using System.Collections.Specialized;
|
||||||
using System.Collections.Specialized;
|
#endif
|
||||||
#endif
|
|
||||||
|
#if !NO_SETS
|
||||||
#if !NO_SETS
|
|
||||||
|
namespace Nuclex.Support.Collections {
|
||||||
namespace Nuclex.Support.Collections {
|
|
||||||
|
/// <summary>Set which fires events when items are removed or added to it</summary>
|
||||||
/// <summary>Set which fires events when items are removed or added to it</summary>
|
/// <typeparam name="TItem">Type of items to manage in the set</typeparam>
|
||||||
/// <typeparam name="TItem">Type of items to manage in the set</typeparam>
|
public class ObservableSet<TItem> :
|
||||||
public class ObservableSet<TItem> :
|
ISet<TItem>,
|
||||||
ISet<TItem>,
|
ICollection<TItem>,
|
||||||
ICollection<TItem>,
|
#if !NO_SPECIALIZED_COLLECTIONS
|
||||||
#if !NO_SPECIALIZED_COLLECTIONS
|
INotifyCollectionChanged,
|
||||||
INotifyCollectionChanged,
|
#endif
|
||||||
#endif
|
IObservableCollection<TItem> {
|
||||||
IObservableCollection<TItem> {
|
|
||||||
|
/// <summary>Raised when an item has been added to the collection</summary>
|
||||||
/// <summary>Raised when an item has been added to the collection</summary>
|
public event EventHandler<ItemEventArgs<TItem>> ItemAdded;
|
||||||
public event EventHandler<ItemEventArgs<TItem>> ItemAdded;
|
/// <summary>Raised when an item is removed from the collection</summary>
|
||||||
/// <summary>Raised when an item is removed from the collection</summary>
|
public event EventHandler<ItemEventArgs<TItem>> ItemRemoved;
|
||||||
public event EventHandler<ItemEventArgs<TItem>> ItemRemoved;
|
/// <summary>Raised when an item is replaced in the collection</summary>
|
||||||
/// <summary>Raised when an item is replaced in the collection</summary>
|
public event EventHandler<ItemReplaceEventArgs<TItem>> ItemReplaced {
|
||||||
public event EventHandler<ItemReplaceEventArgs<TItem>> ItemReplaced {
|
add { }
|
||||||
add { }
|
remove { }
|
||||||
remove { }
|
}
|
||||||
}
|
/// <summary>Raised when the collection is about to be cleared</summary>
|
||||||
/// <summary>Raised when the collection is about to be cleared</summary>
|
/// <remarks>
|
||||||
/// <remarks>
|
/// This could be covered by calling ItemRemoved for each item currently
|
||||||
/// This could be covered by calling ItemRemoved for each item currently
|
/// contained in the collection, but it is often simpler and more efficient
|
||||||
/// contained in the collection, but it is often simpler and more efficient
|
/// to process the clearing of the entire collection as a special operation.
|
||||||
/// to process the clearing of the entire collection as a special operation.
|
/// </remarks>
|
||||||
/// </remarks>
|
public event EventHandler Clearing;
|
||||||
public event EventHandler Clearing;
|
/// <summary>Raised when the collection has been cleared</summary>
|
||||||
/// <summary>Raised when the collection has been cleared</summary>
|
public event EventHandler Cleared;
|
||||||
public event EventHandler Cleared;
|
|
||||||
|
#if !NO_SPECIALIZED_COLLECTIONS
|
||||||
#if !NO_SPECIALIZED_COLLECTIONS
|
/// <summary>Called when the collection has changed</summary>
|
||||||
/// <summary>Called when the collection has changed</summary>
|
public event NotifyCollectionChangedEventHandler CollectionChanged;
|
||||||
public event NotifyCollectionChangedEventHandler CollectionChanged;
|
#endif
|
||||||
#endif
|
|
||||||
|
/// <summary>Initializes a new observable set based on a hashed set</summary>
|
||||||
/// <summary>Initializes a new observable set based on a hashed set</summary>
|
public ObservableSet() : this(new HashSet<TItem>()) { }
|
||||||
public ObservableSet() : this(new HashSet<TItem>()) { }
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Initializes a new observable set forwarding operations to the specified set
|
||||||
/// Initializes a new observable set forwarding operations to the specified set
|
/// </summary>
|
||||||
/// </summary>
|
/// <param name="set">Set operations will be forwarded to</param>
|
||||||
/// <param name="set">Set operations will be forwarded to</param>
|
public ObservableSet(ISet<TItem> set) {
|
||||||
public ObservableSet(ISet<TItem> set) {
|
this.set = set;
|
||||||
this.set = set;
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Adds an item to the set</summary>
|
||||||
/// <summary>Adds an item to the set</summary>
|
/// <param name="item">Item that will be added to the set</param>
|
||||||
/// <param name="item">Item that will be added to the set</param>
|
/// <returns>
|
||||||
/// <returns>
|
/// True if the element was added, false if it was already contained in the set
|
||||||
/// True if the element was added, false if it was already contained in the set
|
/// </returns>
|
||||||
/// </returns>
|
public bool Add(TItem item) {
|
||||||
public bool Add(TItem item) {
|
bool wasAdded = this.set.Add(item);
|
||||||
bool wasAdded = this.set.Add(item);
|
if(wasAdded) {
|
||||||
if(wasAdded) {
|
OnAdded(item);
|
||||||
OnAdded(item);
|
}
|
||||||
}
|
return wasAdded;
|
||||||
return wasAdded;
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Removes all elements that are contained in the collection</summary>
|
||||||
/// <summary>Removes all elements that are contained in the collection</summary>
|
/// <param name="other">Collection whose elements will be removed from this set</param>
|
||||||
/// <param name="other">Collection whose elements will be removed from this set</param>
|
public void ExceptWith(IEnumerable<TItem> other) {
|
||||||
public void ExceptWith(IEnumerable<TItem> other) {
|
if(other == this) {
|
||||||
if(other == this) {
|
Clear();
|
||||||
Clear();
|
return;
|
||||||
return;
|
}
|
||||||
}
|
|
||||||
|
foreach(TItem item in other) {
|
||||||
foreach(TItem item in other) {
|
if(this.set.Remove(item)) {
|
||||||
if(this.set.Remove(item)) {
|
OnRemoved(item);
|
||||||
OnRemoved(item);
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Only keeps those elements in this set that are contained in the collection
|
||||||
/// Only keeps those elements in this set that are contained in the collection
|
/// </summary>
|
||||||
/// </summary>
|
/// <param name="other">Other set this set will be filtered by</param>
|
||||||
/// <param name="other">Other set this set will be filtered by</param>
|
public void IntersectWith(IEnumerable<TItem> other) {
|
||||||
public void IntersectWith(IEnumerable<TItem> other) {
|
var otherSet = other as ISet<TItem>;
|
||||||
var otherSet = other as ISet<TItem>;
|
if(otherSet == null) {
|
||||||
if(otherSet == null) {
|
otherSet = new HashSet<TItem>(other);
|
||||||
otherSet = new HashSet<TItem>(other);
|
}
|
||||||
}
|
|
||||||
|
var itemsToRemove = new List<TItem>();
|
||||||
var itemsToRemove = new List<TItem>();
|
foreach(TItem item in this.set) {
|
||||||
foreach(TItem item in this.set) {
|
if(!otherSet.Contains(item)) {
|
||||||
if(!otherSet.Contains(item)) {
|
itemsToRemove.Add(item);
|
||||||
itemsToRemove.Add(item);
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
for(int index = 0; index < itemsToRemove.Count; ++index) {
|
||||||
for(int index = 0; index < itemsToRemove.Count; ++index) {
|
this.set.Remove(itemsToRemove[index]);
|
||||||
this.set.Remove(itemsToRemove[index]);
|
OnRemoved(itemsToRemove[index]);
|
||||||
OnRemoved(itemsToRemove[index]);
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Determines whether the current set is a proper (strict) subset of a collection
|
||||||
/// Determines whether the current set is a proper (strict) subset of a collection
|
/// </summary>
|
||||||
/// </summary>
|
/// <param name="other">Collection against which the set will be tested</param>
|
||||||
/// <param name="other">Collection against which the set will be tested</param>
|
/// <returns>True if the set is a proper subset of the specified collection</returns>
|
||||||
/// <returns>True if the set is a proper subset of the specified collection</returns>
|
public bool IsProperSubsetOf(IEnumerable<TItem> other) {
|
||||||
public bool IsProperSubsetOf(IEnumerable<TItem> other) {
|
return this.set.IsProperSubsetOf(other);
|
||||||
return this.set.IsProperSubsetOf(other);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Determines whether the current set is a proper (strict) superset of a collection
|
||||||
/// Determines whether the current set is a proper (strict) superset of a collection
|
/// </summary>
|
||||||
/// </summary>
|
/// <param name="other">Collection against which the set will be tested</param>
|
||||||
/// <param name="other">Collection against which the set will be tested</param>
|
/// <returns>True if the set is a proper superset of the specified collection</returns>
|
||||||
/// <returns>True if the set is a proper superset of the specified collection</returns>
|
public bool IsProperSupersetOf(IEnumerable<TItem> other) {
|
||||||
public bool IsProperSupersetOf(IEnumerable<TItem> other) {
|
return this.set.IsProperSupersetOf(other);
|
||||||
return this.set.IsProperSupersetOf(other);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Determines whether the current set is a subset of a collection</summary>
|
||||||
/// <summary>Determines whether the current set is a subset of a collection</summary>
|
/// <param name="other">Collection against which the set will be tested</param>
|
||||||
/// <param name="other">Collection against which the set will be tested</param>
|
/// <returns>True if the set is a subset of the specified collection</returns>
|
||||||
/// <returns>True if the set is a subset of the specified collection</returns>
|
public bool IsSubsetOf(IEnumerable<TItem> other) {
|
||||||
public bool IsSubsetOf(IEnumerable<TItem> other) {
|
return this.set.IsSubsetOf(other);
|
||||||
return this.set.IsSubsetOf(other);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Determines whether the current set is a superset of a collection</summary>
|
||||||
/// <summary>Determines whether the current set is a superset of a collection</summary>
|
/// <param name="other">Collection against which the set will be tested</param>
|
||||||
/// <param name="other">Collection against which the set will be tested</param>
|
/// <returns>True if the set is a superset of the specified collection</returns>
|
||||||
/// <returns>True if the set is a superset of the specified collection</returns>
|
public bool IsSupersetOf(IEnumerable<TItem> other) {
|
||||||
public bool IsSupersetOf(IEnumerable<TItem> other) {
|
return this.set.IsSupersetOf(other);
|
||||||
return this.set.IsSupersetOf(other);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Determines if the set shares at least one common element with the collection
|
||||||
/// Determines if the set shares at least one common element with the collection
|
/// </summary>
|
||||||
/// </summary>
|
/// <param name="other">Collection the set will be tested against</param>
|
||||||
/// <param name="other">Collection the set will be tested against</param>
|
/// <returns>
|
||||||
/// <returns>
|
/// True if the set shares at least one common element with the collection
|
||||||
/// True if the set shares at least one common element with the collection
|
/// </returns>
|
||||||
/// </returns>
|
public bool Overlaps(IEnumerable<TItem> other) {
|
||||||
public bool Overlaps(IEnumerable<TItem> other) {
|
return this.set.Overlaps(other);
|
||||||
return this.set.Overlaps(other);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Determines whether the set contains the same elements as the specified collection
|
||||||
/// Determines whether the set contains the same elements as the specified collection
|
/// </summary>
|
||||||
/// </summary>
|
/// <param name="other">Collection the set will be tested against</param>
|
||||||
/// <param name="other">Collection the set will be tested against</param>
|
/// <returns>True if the set contains the same elements as the collection</returns>
|
||||||
/// <returns>True if the set contains the same elements as the collection</returns>
|
public bool SetEquals(IEnumerable<TItem> other) {
|
||||||
public bool SetEquals(IEnumerable<TItem> other) {
|
return this.set.SetEquals(other);
|
||||||
return this.set.SetEquals(other);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Modifies the current set so that it contains only elements that are present either
|
||||||
/// Modifies the current set so that it contains only elements that are present either
|
/// in the current set or in the specified collection, but not both
|
||||||
/// in the current set or in the specified collection, but not both
|
/// </summary>
|
||||||
/// </summary>
|
/// <param name="other">Collection the set will be excepted with</param>
|
||||||
/// <param name="other">Collection the set will be excepted with</param>
|
public void SymmetricExceptWith(IEnumerable<TItem> other) {
|
||||||
public void SymmetricExceptWith(IEnumerable<TItem> other) {
|
foreach(TItem item in other) {
|
||||||
foreach(TItem item in other) {
|
if(this.set.Remove(item)) {
|
||||||
if(this.set.Remove(item)) {
|
OnRemoved(item);
|
||||||
OnRemoved(item);
|
} else {
|
||||||
} else {
|
this.Add(item);
|
||||||
this.Add(item);
|
OnAdded(item);
|
||||||
OnAdded(item);
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Modifies the current set so that it contains all elements that are present in both
|
||||||
/// Modifies the current set so that it contains all elements that are present in both
|
/// the current set and in the specified collection
|
||||||
/// the current set and in the specified collection
|
/// </summary>
|
||||||
/// </summary>
|
/// <param name="other">Collection an union will be built with</param>
|
||||||
/// <param name="other">Collection an union will be built with</param>
|
public void UnionWith(IEnumerable<TItem> other) {
|
||||||
public void UnionWith(IEnumerable<TItem> other) {
|
foreach(TItem item in other) {
|
||||||
foreach(TItem item in other) {
|
if(this.set.Add(item)) {
|
||||||
if(this.set.Add(item)) {
|
OnAdded(item);
|
||||||
OnAdded(item);
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Removes all items from the set</summary>
|
||||||
/// <summary>Removes all items from the set</summary>
|
public void Clear() {
|
||||||
public void Clear() {
|
OnClearing();
|
||||||
OnClearing();
|
this.set.Clear();
|
||||||
this.set.Clear();
|
OnCleared();
|
||||||
OnCleared();
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Determines whether the set contains the specified item</summary>
|
||||||
/// <summary>Determines whether the set contains the specified item</summary>
|
/// <param name="item">Item the set will be tested for</param>
|
||||||
/// <param name="item">Item the set will be tested for</param>
|
/// <returns>True if the set contains the specified item</returns>
|
||||||
/// <returns>True if the set contains the specified item</returns>
|
public bool Contains(TItem item) {
|
||||||
public bool Contains(TItem item) {
|
return this.set.Contains(item);
|
||||||
return this.set.Contains(item);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Copies the contents of the set into an array</summary>
|
||||||
/// <summary>Copies the contents of the set into an array</summary>
|
/// <param name="array">Array the set's contents will be copied to</param>
|
||||||
/// <param name="array">Array the set's contents will be copied to</param>
|
/// <param name="arrayIndex">
|
||||||
/// <param name="arrayIndex">
|
/// Index in the array the first copied element will be written to
|
||||||
/// Index in the array the first copied element will be written to
|
/// </param>
|
||||||
/// </param>
|
public void CopyTo(TItem[] array, int arrayIndex) {
|
||||||
public void CopyTo(TItem[] array, int arrayIndex) {
|
this.set.CopyTo(array, arrayIndex);
|
||||||
this.set.CopyTo(array, arrayIndex);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Counts the number of items contained in the set</summary>
|
||||||
/// <summary>Counts the number of items contained in the set</summary>
|
public int Count {
|
||||||
public int Count {
|
get { return this.set.Count; }
|
||||||
get { return this.set.Count; }
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Determines whether the set is readonly</summary>
|
||||||
/// <summary>Determines whether the set is readonly</summary>
|
public bool IsReadOnly {
|
||||||
public bool IsReadOnly {
|
get { return this.set.IsReadOnly; }
|
||||||
get { return this.set.IsReadOnly; }
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Removes an item from the set</summary>
|
||||||
/// <summary>Removes an item from the set</summary>
|
/// <param name="item">Item that will be removed from the set</param>
|
||||||
/// <param name="item">Item that will be removed from the set</param>
|
/// <returns>
|
||||||
/// <returns>
|
/// True if the item was contained in the set and is now removed
|
||||||
/// True if the item was contained in the set and is now removed
|
/// </returns>
|
||||||
/// </returns>
|
public bool Remove(TItem item) {
|
||||||
public bool Remove(TItem item) {
|
bool wasRemoved = this.set.Remove(item);
|
||||||
bool wasRemoved = this.set.Remove(item);
|
if(wasRemoved) {
|
||||||
if(wasRemoved) {
|
OnRemoved(item);
|
||||||
OnRemoved(item);
|
}
|
||||||
}
|
return wasRemoved;
|
||||||
return wasRemoved;
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Creates an enumerator for the set's contents</summary>
|
||||||
/// <summary>Creates an enumerator for the set's contents</summary>
|
/// <returns>A new enumerator for the sets contents</returns>
|
||||||
/// <returns>A new enumerator for the sets contents</returns>
|
public IEnumerator<TItem> GetEnumerator() {
|
||||||
public IEnumerator<TItem> GetEnumerator() {
|
return this.set.GetEnumerator();
|
||||||
return this.set.GetEnumerator();
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Fires the 'ItemAdded' event</summary>
|
||||||
/// <summary>Fires the 'ItemAdded' event</summary>
|
/// <param name="item">Item that has been added to the collection</param>
|
||||||
/// <param name="item">Item that has been added to the collection</param>
|
protected virtual void OnAdded(TItem item) {
|
||||||
protected virtual void OnAdded(TItem item) {
|
if(ItemAdded != null) {
|
||||||
if(ItemAdded != null) {
|
ItemAdded(this, new ItemEventArgs<TItem>(item));
|
||||||
ItemAdded(this, new ItemEventArgs<TItem>(item));
|
}
|
||||||
}
|
#if !NO_SPECIALIZED_COLLECTIONS
|
||||||
#if !NO_SPECIALIZED_COLLECTIONS
|
if(CollectionChanged != null) {
|
||||||
if(CollectionChanged != null) {
|
CollectionChanged(
|
||||||
CollectionChanged(
|
this,
|
||||||
this,
|
new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item)
|
||||||
new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item)
|
);
|
||||||
);
|
}
|
||||||
}
|
#endif
|
||||||
#endif
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Fires the 'ItemRemoved' event</summary>
|
||||||
/// <summary>Fires the 'ItemRemoved' event</summary>
|
/// <param name="item">Item that has been removed from the collection</param>
|
||||||
/// <param name="item">Item that has been removed from the collection</param>
|
protected virtual void OnRemoved(TItem item) {
|
||||||
protected virtual void OnRemoved(TItem item) {
|
if(ItemRemoved != null) {
|
||||||
if(ItemRemoved != null) {
|
ItemRemoved(this, new ItemEventArgs<TItem>(item));
|
||||||
ItemRemoved(this, new ItemEventArgs<TItem>(item));
|
}
|
||||||
}
|
#if !NO_SPECIALIZED_COLLECTIONS
|
||||||
#if !NO_SPECIALIZED_COLLECTIONS
|
if(CollectionChanged != null) {
|
||||||
if(CollectionChanged != null) {
|
CollectionChanged(
|
||||||
CollectionChanged(
|
this,
|
||||||
this,
|
new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, item)
|
||||||
new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, item)
|
);
|
||||||
);
|
}
|
||||||
}
|
#endif
|
||||||
#endif
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Fires the 'Clearing' event</summary>
|
||||||
/// <summary>Fires the 'Clearing' event</summary>
|
protected virtual void OnClearing() {
|
||||||
protected virtual void OnClearing() {
|
if(Clearing != null) {
|
||||||
if(Clearing != null) {
|
Clearing(this, EventArgs.Empty);
|
||||||
Clearing(this, EventArgs.Empty);
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Fires the 'Cleared' event</summary>
|
||||||
/// <summary>Fires the 'Cleared' event</summary>
|
protected virtual void OnCleared() {
|
||||||
protected virtual void OnCleared() {
|
if(Cleared != null) {
|
||||||
if(Cleared != null) {
|
Cleared(this, EventArgs.Empty);
|
||||||
Cleared(this, EventArgs.Empty);
|
}
|
||||||
}
|
#if !NO_SPECIALIZED_COLLECTIONS
|
||||||
#if !NO_SPECIALIZED_COLLECTIONS
|
if(CollectionChanged != null) {
|
||||||
if(CollectionChanged != null) {
|
CollectionChanged(this, Constants.NotifyCollectionResetEventArgs);
|
||||||
CollectionChanged(this, Constants.NotifyCollectionResetEventArgs);
|
}
|
||||||
}
|
#endif
|
||||||
#endif
|
}
|
||||||
}
|
|
||||||
|
#region ICollection<T> implementation
|
||||||
#region ICollection<T> implementation
|
|
||||||
|
/// <summary>Adds an item to the set</summary>
|
||||||
/// <summary>Adds an item to the set</summary>
|
/// <param name="item">Item that will be added to the set</param>
|
||||||
/// <param name="item">Item that will be added to the set</param>
|
void ICollection<TItem>.Add(TItem item) {
|
||||||
void ICollection<TItem>.Add(TItem item) {
|
this.set.Add(item);
|
||||||
this.set.Add(item);
|
}
|
||||||
}
|
|
||||||
|
#endregion // ICollection<T> implementation
|
||||||
#endregion // ICollection<T> implementation
|
|
||||||
|
#region IEnumerable implementation
|
||||||
#region IEnumerable implementation
|
|
||||||
|
/// <summary>Creates an enumerator for the set's contents</summary>
|
||||||
/// <summary>Creates an enumerator for the set's contents</summary>
|
/// <returns>A new enumerator for the sets contents</returns>
|
||||||
/// <returns>A new enumerator for the sets contents</returns>
|
IEnumerator IEnumerable.GetEnumerator() {
|
||||||
IEnumerator IEnumerable.GetEnumerator() {
|
return this.set.GetEnumerator();
|
||||||
return this.set.GetEnumerator();
|
}
|
||||||
}
|
|
||||||
|
#endregion // IEnumerable implementation
|
||||||
#endregion // IEnumerable implementation
|
|
||||||
|
/// <summary>The set being wrapped</summary>
|
||||||
/// <summary>The set being wrapped</summary>
|
private ISet<TItem> set;
|
||||||
private ISet<TItem> set;
|
|
||||||
|
}
|
||||||
}
|
|
||||||
|
} // namespace Nuclex.Support.Collections
|
||||||
} // namespace Nuclex.Support.Collections
|
|
||||||
|
#endif // !NO_SETS
|
||||||
#endif // !NO_SETS
|
|
||||||
|
|
|
@ -1,151 +1,150 @@
|
||||||
#region CPL License
|
#region Apache License 2.0
|
||||||
/*
|
/*
|
||||||
Nuclex Framework
|
Nuclex .NET Framework
|
||||||
Copyright (C) 2002-2017 Nuclex Development Labs
|
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
|
||||||
|
|
||||||
This library is free software; you can redistribute it and/or
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
modify it under the terms of the IBM Common Public License as
|
you may not use this file except in compliance with the License.
|
||||||
published by the IBM Corporation; either version 1.0 of the
|
You may obtain a copy of the License at
|
||||||
License, or (at your option) any later version.
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
This library is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
Unless required by applicable law or agreed to in writing, software
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
IBM Common Public License for more details.
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
You should have received a copy of the IBM Common Public
|
limitations under the License.
|
||||||
License along with this library
|
*/
|
||||||
*/
|
#endregion // Apache License 2.0
|
||||||
#endregion
|
|
||||||
|
using System;
|
||||||
using System;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Generic;
|
|
||||||
|
#if UNITTEST
|
||||||
#if UNITTEST
|
|
||||||
|
using NUnit.Framework;
|
||||||
using NUnit.Framework;
|
|
||||||
|
namespace Nuclex.Support.Collections {
|
||||||
namespace Nuclex.Support.Collections {
|
|
||||||
|
/// <summary>Unit Test for the priority queue class</summary>
|
||||||
/// <summary>Unit Test for the priority queue class</summary>
|
[TestFixture]
|
||||||
[TestFixture]
|
internal class PairPriorityQueueTest {
|
||||||
internal class PairPriorityQueueTest {
|
|
||||||
|
/// <summary>Tests to ensure the count property is properly updated</summary>
|
||||||
/// <summary>Tests to ensure the count property is properly updated</summary>
|
[Test]
|
||||||
[Test]
|
public void TestCount() {
|
||||||
public void TestCount() {
|
PairPriorityQueue<float, string> testQueue = new PairPriorityQueue<float, string>();
|
||||||
PairPriorityQueue<float, string> testQueue = new PairPriorityQueue<float, string>();
|
|
||||||
|
Assert.AreEqual(0, testQueue.Count);
|
||||||
Assert.AreEqual(0, testQueue.Count);
|
testQueue.Enqueue(12.34f, "a");
|
||||||
testQueue.Enqueue(12.34f, "a");
|
Assert.AreEqual(1, testQueue.Count);
|
||||||
Assert.AreEqual(1, testQueue.Count);
|
testQueue.Enqueue(56.78f, "b");
|
||||||
testQueue.Enqueue(56.78f, "b");
|
Assert.AreEqual(2, testQueue.Count);
|
||||||
Assert.AreEqual(2, testQueue.Count);
|
testQueue.Dequeue();
|
||||||
testQueue.Dequeue();
|
Assert.AreEqual(1, testQueue.Count);
|
||||||
Assert.AreEqual(1, testQueue.Count);
|
testQueue.Enqueue(9.0f, "c");
|
||||||
testQueue.Enqueue(9.0f, "c");
|
Assert.AreEqual(2, testQueue.Count);
|
||||||
Assert.AreEqual(2, testQueue.Count);
|
testQueue.Clear();
|
||||||
testQueue.Clear();
|
Assert.AreEqual(0, testQueue.Count);
|
||||||
Assert.AreEqual(0, testQueue.Count);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Tests to ensure that the priority collection actually sorts items</summary>
|
||||||
/// <summary>Tests to ensure that the priority collection actually sorts items</summary>
|
[Test]
|
||||||
[Test]
|
public void TestOrdering() {
|
||||||
public void TestOrdering() {
|
PairPriorityQueue<float, string> testQueue = new PairPriorityQueue<float, string>();
|
||||||
PairPriorityQueue<float, string> testQueue = new PairPriorityQueue<float, string>();
|
|
||||||
|
testQueue.Enqueue(1.0f, "a");
|
||||||
testQueue.Enqueue(1.0f, "a");
|
testQueue.Enqueue(9.0f, "i");
|
||||||
testQueue.Enqueue(9.0f, "i");
|
testQueue.Enqueue(2.0f, "b");
|
||||||
testQueue.Enqueue(2.0f, "b");
|
testQueue.Enqueue(8.0f, "h");
|
||||||
testQueue.Enqueue(8.0f, "h");
|
testQueue.Enqueue(3.0f, "c");
|
||||||
testQueue.Enqueue(3.0f, "c");
|
testQueue.Enqueue(7.0f, "g");
|
||||||
testQueue.Enqueue(7.0f, "g");
|
testQueue.Enqueue(4.0f, "d");
|
||||||
testQueue.Enqueue(4.0f, "d");
|
testQueue.Enqueue(6.0f, "f");
|
||||||
testQueue.Enqueue(6.0f, "f");
|
testQueue.Enqueue(5.0f, "e");
|
||||||
testQueue.Enqueue(5.0f, "e");
|
|
||||||
|
Assert.AreEqual("i", testQueue.Dequeue().Item);
|
||||||
Assert.AreEqual("i", testQueue.Dequeue().Item);
|
Assert.AreEqual("h", testQueue.Dequeue().Item);
|
||||||
Assert.AreEqual("h", testQueue.Dequeue().Item);
|
Assert.AreEqual("g", testQueue.Dequeue().Item);
|
||||||
Assert.AreEqual("g", testQueue.Dequeue().Item);
|
Assert.AreEqual("f", testQueue.Dequeue().Item);
|
||||||
Assert.AreEqual("f", testQueue.Dequeue().Item);
|
Assert.AreEqual("e", testQueue.Dequeue().Item);
|
||||||
Assert.AreEqual("e", testQueue.Dequeue().Item);
|
Assert.AreEqual("d", testQueue.Dequeue().Item);
|
||||||
Assert.AreEqual("d", testQueue.Dequeue().Item);
|
Assert.AreEqual("c", testQueue.Dequeue().Item);
|
||||||
Assert.AreEqual("c", testQueue.Dequeue().Item);
|
Assert.AreEqual("b", testQueue.Dequeue().Item);
|
||||||
Assert.AreEqual("b", testQueue.Dequeue().Item);
|
Assert.AreEqual("a", testQueue.Dequeue().Item);
|
||||||
Assert.AreEqual("a", testQueue.Dequeue().Item);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Tests to ensure that the priority collection's Peek() method works</summary>
|
||||||
/// <summary>Tests to ensure that the priority collection's Peek() method works</summary>
|
[Test]
|
||||||
[Test]
|
public void TestPeek() {
|
||||||
public void TestPeek() {
|
PairPriorityQueue<float, string> testQueue = new PairPriorityQueue<float, string>();
|
||||||
PairPriorityQueue<float, string> testQueue = new PairPriorityQueue<float, string>();
|
|
||||||
|
testQueue.Enqueue(1.0f, "a");
|
||||||
testQueue.Enqueue(1.0f, "a");
|
testQueue.Enqueue(2.0f, "b");
|
||||||
testQueue.Enqueue(2.0f, "b");
|
testQueue.Enqueue(0.0f, "c");
|
||||||
testQueue.Enqueue(0.0f, "c");
|
|
||||||
|
Assert.AreEqual("b", testQueue.Peek().Item);
|
||||||
Assert.AreEqual("b", testQueue.Peek().Item);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Tests whether the priority collection can copy itself into an array</summary>
|
||||||
/// <summary>Tests whether the priority collection can copy itself into an array</summary>
|
[Test]
|
||||||
[Test]
|
public void TestCopyTo() {
|
||||||
public void TestCopyTo() {
|
PairPriorityQueue<float, string> testQueue = new PairPriorityQueue<float, string>();
|
||||||
PairPriorityQueue<float, string> testQueue = new PairPriorityQueue<float, string>();
|
|
||||||
|
testQueue.Enqueue(1.0f, "a");
|
||||||
testQueue.Enqueue(1.0f, "a");
|
testQueue.Enqueue(9.0f, "i");
|
||||||
testQueue.Enqueue(9.0f, "i");
|
testQueue.Enqueue(2.0f, "b");
|
||||||
testQueue.Enqueue(2.0f, "b");
|
testQueue.Enqueue(8.0f, "h");
|
||||||
testQueue.Enqueue(8.0f, "h");
|
testQueue.Enqueue(3.0f, "c");
|
||||||
testQueue.Enqueue(3.0f, "c");
|
testQueue.Enqueue(7.0f, "g");
|
||||||
testQueue.Enqueue(7.0f, "g");
|
testQueue.Enqueue(4.0f, "d");
|
||||||
testQueue.Enqueue(4.0f, "d");
|
testQueue.Enqueue(6.0f, "f");
|
||||||
testQueue.Enqueue(6.0f, "f");
|
testQueue.Enqueue(5.0f, "e");
|
||||||
testQueue.Enqueue(5.0f, "e");
|
|
||||||
|
PriorityItemPair<float, string>[] itemArray = new PriorityItemPair<float, string>[9];
|
||||||
PriorityItemPair<float, string>[] itemArray = new PriorityItemPair<float, string>[9];
|
testQueue.CopyTo(itemArray, 0);
|
||||||
testQueue.CopyTo(itemArray, 0);
|
|
||||||
|
CollectionAssert.AreEquivalent(testQueue, itemArray);
|
||||||
CollectionAssert.AreEquivalent(testQueue, itemArray);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Tests whether the priority collection provides a synchronization root
|
||||||
/// Tests whether the priority collection provides a synchronization root
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void TestSyncRoot() {
|
||||||
public void TestSyncRoot() {
|
PairPriorityQueue<int, int> testQueue = new PairPriorityQueue<int, int>();
|
||||||
PairPriorityQueue<int, int> testQueue = new PairPriorityQueue<int, int>();
|
|
||||||
|
// If IsSynchronized returns true, SyncRoot is allowed to be null
|
||||||
// If IsSynchronized returns true, SyncRoot is allowed to be null
|
if(!testQueue.IsSynchronized) {
|
||||||
if(!testQueue.IsSynchronized) {
|
lock(testQueue.SyncRoot) {
|
||||||
lock(testQueue.SyncRoot) {
|
testQueue.Clear();
|
||||||
testQueue.Clear();
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Tests whether the priority collection provides a working type-safe enumerator
|
||||||
/// Tests whether the priority collection provides a working type-safe enumerator
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void TestEnumerator() {
|
||||||
public void TestEnumerator() {
|
PairPriorityQueue<float, string> testQueue = new PairPriorityQueue<float, string>();
|
||||||
PairPriorityQueue<float, string> testQueue = new PairPriorityQueue<float, string>();
|
|
||||||
|
testQueue.Enqueue(1.0f, "a");
|
||||||
testQueue.Enqueue(1.0f, "a");
|
testQueue.Enqueue(2.0f, "b");
|
||||||
testQueue.Enqueue(2.0f, "b");
|
testQueue.Enqueue(0.0f, "c");
|
||||||
testQueue.Enqueue(0.0f, "c");
|
|
||||||
|
List<PriorityItemPair<float, string>> testList =
|
||||||
List<PriorityItemPair<float, string>> testList =
|
new List<PriorityItemPair<float,string>>();
|
||||||
new List<PriorityItemPair<float,string>>();
|
|
||||||
|
foreach(PriorityItemPair<float, string> entry in testQueue) {
|
||||||
foreach(PriorityItemPair<float, string> entry in testQueue) {
|
testList.Add(entry);
|
||||||
testList.Add(entry);
|
}
|
||||||
}
|
|
||||||
|
CollectionAssert.AreEquivalent(testQueue, testList);
|
||||||
CollectionAssert.AreEquivalent(testQueue, testList);
|
}
|
||||||
}
|
|
||||||
|
}
|
||||||
}
|
|
||||||
|
} // namespace Nuclex.Support.Collections
|
||||||
} // namespace Nuclex.Support.Collections
|
|
||||||
|
#endif // UNITTEST
|
||||||
#endif // UNITTEST
|
|
||||||
|
|
|
@ -1,144 +1,143 @@
|
||||||
#region CPL License
|
#region Apache License 2.0
|
||||||
/*
|
/*
|
||||||
Nuclex Framework
|
Nuclex .NET Framework
|
||||||
Copyright (C) 2002-2017 Nuclex Development Labs
|
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
|
||||||
|
|
||||||
This library is free software; you can redistribute it and/or
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
modify it under the terms of the IBM Common Public License as
|
you may not use this file except in compliance with the License.
|
||||||
published by the IBM Corporation; either version 1.0 of the
|
You may obtain a copy of the License at
|
||||||
License, or (at your option) any later version.
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
This library is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
Unless required by applicable law or agreed to in writing, software
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
IBM Common Public License for more details.
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
You should have received a copy of the IBM Common Public
|
limitations under the License.
|
||||||
License along with this library
|
*/
|
||||||
*/
|
#endregion // Apache License 2.0
|
||||||
#endregion
|
|
||||||
|
using System;
|
||||||
using System;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Generic;
|
using System.Collections;
|
||||||
using System.Collections;
|
|
||||||
|
namespace Nuclex.Support.Collections {
|
||||||
namespace Nuclex.Support.Collections {
|
|
||||||
|
/// <summary>Queue that dequeues items in order of their priority</summary>
|
||||||
/// <summary>Queue that dequeues items in order of their priority</summary>
|
/// <remarks>
|
||||||
/// <remarks>
|
/// This variant of the priority queue uses an external priority value. If the
|
||||||
/// This variant of the priority queue uses an external priority value. If the
|
/// priority data type implements the IComparable interface, the user does not
|
||||||
/// priority data type implements the IComparable interface, the user does not
|
/// even
|
||||||
/// even
|
/// </remarks>
|
||||||
/// </remarks>
|
public class PairPriorityQueue<TPriority, TItem> :
|
||||||
public class PairPriorityQueue<TPriority, TItem> :
|
ICollection, IEnumerable<PriorityItemPair<TPriority, TItem>> {
|
||||||
ICollection, IEnumerable<PriorityItemPair<TPriority, TItem>> {
|
|
||||||
|
#region class PairComparer
|
||||||
#region class PairComparer
|
|
||||||
|
/// <summary>Compares two priority queue entries based on their priority</summary>
|
||||||
/// <summary>Compares two priority queue entries based on their priority</summary>
|
private class PairComparer : IComparer<PriorityItemPair<TPriority, TItem>> {
|
||||||
private class PairComparer : IComparer<PriorityItemPair<TPriority, TItem>> {
|
|
||||||
|
/// <summary>Initializes a new entry comparer</summary>
|
||||||
/// <summary>Initializes a new entry comparer</summary>
|
/// <param name="priorityComparer">Comparer used to compare entry priorities</param>
|
||||||
/// <param name="priorityComparer">Comparer used to compare entry priorities</param>
|
public PairComparer(IComparer<TPriority> priorityComparer) {
|
||||||
public PairComparer(IComparer<TPriority> priorityComparer) {
|
this.priorityComparer = priorityComparer;
|
||||||
this.priorityComparer = priorityComparer;
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Compares the left entry to the right entry</summary>
|
||||||
/// <summary>Compares the left entry to the right entry</summary>
|
/// <param name="left">Entry on the left side</param>
|
||||||
/// <param name="left">Entry on the left side</param>
|
/// <param name="right">Entry on the right side</param>
|
||||||
/// <param name="right">Entry on the right side</param>
|
/// <returns>The relationship of the two entries</returns>
|
||||||
/// <returns>The relationship of the two entries</returns>
|
public int Compare(
|
||||||
public int Compare(
|
PriorityItemPair<TPriority, TItem> left,
|
||||||
PriorityItemPair<TPriority, TItem> left,
|
PriorityItemPair<TPriority, TItem> right
|
||||||
PriorityItemPair<TPriority, TItem> right
|
) {
|
||||||
) {
|
return this.priorityComparer.Compare(left.Priority, right.Priority);
|
||||||
return this.priorityComparer.Compare(left.Priority, right.Priority);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Comparer used to compare the priorities of the entries</summary>
|
||||||
/// <summary>Comparer used to compare the priorities of the entries</summary>
|
private IComparer<TPriority> priorityComparer;
|
||||||
private IComparer<TPriority> priorityComparer;
|
|
||||||
|
}
|
||||||
}
|
|
||||||
|
#endregion // class EntryComparer
|
||||||
#endregion // class EntryComparer
|
|
||||||
|
/// <summary>Initializes a new non-intrusive priority queue</summary>
|
||||||
/// <summary>Initializes a new non-intrusive priority queue</summary>
|
public PairPriorityQueue() : this(Comparer<TPriority>.Default) { }
|
||||||
public PairPriorityQueue() : this(Comparer<TPriority>.Default) { }
|
|
||||||
|
/// <summary>Initializes a new non-intrusive priority queue</summary>
|
||||||
/// <summary>Initializes a new non-intrusive priority queue</summary>
|
/// <param name="priorityComparer">Comparer used to compare the item priorities</param>
|
||||||
/// <param name="priorityComparer">Comparer used to compare the item priorities</param>
|
public PairPriorityQueue(IComparer<TPriority> priorityComparer) {
|
||||||
public PairPriorityQueue(IComparer<TPriority> priorityComparer) {
|
this.internalQueue = new PriorityQueue<PriorityItemPair<TPriority, TItem>>(
|
||||||
this.internalQueue = new PriorityQueue<PriorityItemPair<TPriority, TItem>>(
|
new PairComparer(priorityComparer)
|
||||||
new PairComparer(priorityComparer)
|
);
|
||||||
);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Returns the topmost item in the queue without dequeueing it</summary>
|
||||||
/// <summary>Returns the topmost item in the queue without dequeueing it</summary>
|
/// <returns>The topmost item in the queue</returns>
|
||||||
/// <returns>The topmost item in the queue</returns>
|
public PriorityItemPair<TPriority, TItem> Peek() {
|
||||||
public PriorityItemPair<TPriority, TItem> Peek() {
|
return this.internalQueue.Peek();
|
||||||
return this.internalQueue.Peek();
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Takes the item with the highest priority off from the queue</summary>
|
||||||
/// <summary>Takes the item with the highest priority off from the queue</summary>
|
/// <returns>The item with the highest priority in the list</returns>
|
||||||
/// <returns>The item with the highest priority in the list</returns>
|
public PriorityItemPair<TPriority, TItem> Dequeue() {
|
||||||
public PriorityItemPair<TPriority, TItem> Dequeue() {
|
return this.internalQueue.Dequeue();
|
||||||
return this.internalQueue.Dequeue();
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Puts an item into the priority queue</summary>
|
||||||
/// <summary>Puts an item into the priority queue</summary>
|
/// <param name="priority">Priority of the item to be queued</param>
|
||||||
/// <param name="priority">Priority of the item to be queued</param>
|
/// <param name="item">Item to be queued</param>
|
||||||
/// <param name="item">Item to be queued</param>
|
public void Enqueue(TPriority priority, TItem item) {
|
||||||
public void Enqueue(TPriority priority, TItem item) {
|
this.internalQueue.Enqueue(
|
||||||
this.internalQueue.Enqueue(
|
new PriorityItemPair<TPriority, TItem>(priority, item)
|
||||||
new PriorityItemPair<TPriority, TItem>(priority, item)
|
);
|
||||||
);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Removes all items from the priority queue</summary>
|
||||||
/// <summary>Removes all items from the priority queue</summary>
|
public void Clear() {
|
||||||
public void Clear() {
|
this.internalQueue.Clear();
|
||||||
this.internalQueue.Clear();
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Total number of items in the priority queue</summary>
|
||||||
/// <summary>Total number of items in the priority queue</summary>
|
public int Count {
|
||||||
public int Count {
|
get { return this.internalQueue.Count; }
|
||||||
get { return this.internalQueue.Count; }
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Copies the contents of the priority queue into an array</summary>
|
||||||
/// <summary>Copies the contents of the priority queue into an array</summary>
|
/// <param name="array">Array to copy the priority queue into</param>
|
||||||
/// <param name="array">Array to copy the priority queue into</param>
|
/// <param name="index">Starting index for the destination array</param>
|
||||||
/// <param name="index">Starting index for the destination array</param>
|
public void CopyTo(Array array, int index) {
|
||||||
public void CopyTo(Array array, int index) {
|
this.internalQueue.CopyTo(array, index);
|
||||||
this.internalQueue.CopyTo(array, index);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Obtains an object that can be used to synchronize accesses to the priority queue
|
||||||
/// Obtains an object that can be used to synchronize accesses to the priority queue
|
/// from different threads
|
||||||
/// from different threads
|
/// </summary>
|
||||||
/// </summary>
|
public object SyncRoot {
|
||||||
public object SyncRoot {
|
get { return this.internalQueue.SyncRoot; }
|
||||||
get { return this.internalQueue.SyncRoot; }
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Whether operations performed on this priority queue are thread safe</summary>
|
||||||
/// <summary>Whether operations performed on this priority queue are thread safe</summary>
|
public bool IsSynchronized {
|
||||||
public bool IsSynchronized {
|
get { return this.internalQueue.IsSynchronized; }
|
||||||
get { return this.internalQueue.IsSynchronized; }
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Returns a typesafe enumerator for the priority queue</summary>
|
||||||
/// <summary>Returns a typesafe enumerator for the priority queue</summary>
|
/// <returns>A new enumerator for the priority queue</returns>
|
||||||
/// <returns>A new enumerator for the priority queue</returns>
|
public IEnumerator<PriorityItemPair<TPriority, TItem>> GetEnumerator() {
|
||||||
public IEnumerator<PriorityItemPair<TPriority, TItem>> GetEnumerator() {
|
return this.internalQueue.GetEnumerator();
|
||||||
return this.internalQueue.GetEnumerator();
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Returns an enumerator for the priority queue</summary>
|
||||||
/// <summary>Returns an enumerator for the priority queue</summary>
|
/// <returns>A new enumerator for the priority queue</returns>
|
||||||
/// <returns>A new enumerator for the priority queue</returns>
|
IEnumerator IEnumerable.GetEnumerator() {
|
||||||
IEnumerator IEnumerable.GetEnumerator() {
|
return this.internalQueue.GetEnumerator();
|
||||||
return this.internalQueue.GetEnumerator();
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Intrusive priority queue being wrapped by this class</summary>
|
||||||
/// <summary>Intrusive priority queue being wrapped by this class</summary>
|
private PriorityQueue<PriorityItemPair<TPriority, TItem>> internalQueue;
|
||||||
private PriorityQueue<PriorityItemPair<TPriority, TItem>> internalQueue;
|
|
||||||
|
}
|
||||||
}
|
|
||||||
|
} // namespace Nuclex.Support.Collections
|
||||||
} // namespace Nuclex.Support.Collections
|
|
||||||
|
|
|
@ -1,101 +1,100 @@
|
||||||
#region CPL License
|
#region Apache License 2.0
|
||||||
/*
|
/*
|
||||||
Nuclex Framework
|
Nuclex .NET Framework
|
||||||
Copyright (C) 2002-2017 Nuclex Development Labs
|
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
|
||||||
|
|
||||||
This library is free software; you can redistribute it and/or
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
modify it under the terms of the IBM Common Public License as
|
you may not use this file except in compliance with the License.
|
||||||
published by the IBM Corporation; either version 1.0 of the
|
You may obtain a copy of the License at
|
||||||
License, or (at your option) any later version.
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
This library is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
Unless required by applicable law or agreed to in writing, software
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
IBM Common Public License for more details.
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
You should have received a copy of the IBM Common Public
|
limitations under the License.
|
||||||
License along with this library
|
*/
|
||||||
*/
|
#endregion // Apache License 2.0
|
||||||
#endregion
|
|
||||||
|
using System;
|
||||||
using System;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Generic;
|
|
||||||
|
#if UNITTEST
|
||||||
#if UNITTEST
|
|
||||||
|
using NUnit.Framework;
|
||||||
using NUnit.Framework;
|
|
||||||
|
namespace Nuclex.Support.Collections {
|
||||||
namespace Nuclex.Support.Collections {
|
|
||||||
|
/// <summary>Unit Test for the Parentable class</summary>
|
||||||
/// <summary>Unit Test for the Parentable class</summary>
|
[TestFixture]
|
||||||
[TestFixture]
|
internal class ParentableTest {
|
||||||
internal class ParentableTest {
|
|
||||||
|
#region class TestParentable
|
||||||
#region class TestParentable
|
|
||||||
|
/// <summary>Parentable object that can be the child of an int</summary>
|
||||||
/// <summary>Parentable object that can be the child of an int</summary>
|
private class TestParentable : Parentable<int> {
|
||||||
private class TestParentable : Parentable<int> {
|
|
||||||
|
/// <summary>Initializes a new instance of the parentable test class</summary>
|
||||||
/// <summary>Initializes a new instance of the parentable test class</summary>
|
public TestParentable() { }
|
||||||
public TestParentable() { }
|
|
||||||
|
/// <summary>The parent object that owns this instance</summary>
|
||||||
/// <summary>The parent object that owns this instance</summary>
|
public int GetParent() {
|
||||||
public int GetParent() {
|
return base.Parent;
|
||||||
return base.Parent;
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Invoked whenever the instance's owner changes</summary>
|
||||||
/// <summary>Invoked whenever the instance's owner changes</summary>
|
/// <remarks>
|
||||||
/// <remarks>
|
/// When items are parented for the first time, the oldParent argument will
|
||||||
/// When items are parented for the first time, the oldParent argument will
|
/// be null. Also, if the element is removed from the collection, the
|
||||||
/// be null. Also, if the element is removed from the collection, the
|
/// current parent will be null.
|
||||||
/// current parent will be null.
|
/// </remarks>
|
||||||
/// </remarks>
|
/// <param name="oldParent">Previous owner of the instance</param>
|
||||||
/// <param name="oldParent">Previous owner of the instance</param>
|
protected override void OnParentChanged(int oldParent) {
|
||||||
protected override void OnParentChanged(int oldParent) {
|
this.parentChangedCalled = true;
|
||||||
this.parentChangedCalled = true;
|
|
||||||
|
base.OnParentChanged(oldParent); // to satisfy NCover :-/
|
||||||
base.OnParentChanged(oldParent); // to satisfy NCover :-/
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Whether the OnParentChanged method has been called</summary>
|
||||||
/// <summary>Whether the OnParentChanged method has been called</summary>
|
public bool ParentChangedCalled {
|
||||||
public bool ParentChangedCalled {
|
get { return this.parentChangedCalled; }
|
||||||
get { return this.parentChangedCalled; }
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Whether the OnParentChanged method has been called</summary>
|
||||||
/// <summary>Whether the OnParentChanged method has been called</summary>
|
private bool parentChangedCalled;
|
||||||
private bool parentChangedCalled;
|
|
||||||
|
}
|
||||||
}
|
|
||||||
|
#endregion // class TestParentable
|
||||||
#endregion // class TestParentable
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Tests whether a parent can be assigned and then retrieved from
|
||||||
/// Tests whether a parent can be assigned and then retrieved from
|
/// the parentable object
|
||||||
/// the parentable object
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void TestParentAssignment() {
|
||||||
public void TestParentAssignment() {
|
TestParentable testParentable = new TestParentable();
|
||||||
TestParentable testParentable = new TestParentable();
|
|
||||||
|
testParentable.SetParent(12345);
|
||||||
testParentable.SetParent(12345);
|
Assert.AreEqual(12345, testParentable.GetParent());
|
||||||
Assert.AreEqual(12345, testParentable.GetParent());
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Tests whether a parent can be assigned and then retrieved from
|
||||||
/// Tests whether a parent can be assigned and then retrieved from
|
/// the parentable object
|
||||||
/// the parentable object
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void TestParentChangedNotification() {
|
||||||
public void TestParentChangedNotification() {
|
TestParentable testParentable = new TestParentable();
|
||||||
TestParentable testParentable = new TestParentable();
|
|
||||||
|
testParentable.SetParent(12345);
|
||||||
testParentable.SetParent(12345);
|
|
||||||
|
Assert.IsTrue(testParentable.ParentChangedCalled);
|
||||||
Assert.IsTrue(testParentable.ParentChangedCalled);
|
}
|
||||||
}
|
|
||||||
|
}
|
||||||
}
|
|
||||||
|
} // namespace Nuclex.Support.Collections
|
||||||
} // namespace Nuclex.Support.Collections
|
|
||||||
|
#endif // UNITTEST
|
||||||
#endif // UNITTEST
|
|
||||||
|
|
|
@ -1,56 +1,55 @@
|
||||||
#region CPL License
|
#region Apache License 2.0
|
||||||
/*
|
/*
|
||||||
Nuclex Framework
|
Nuclex .NET Framework
|
||||||
Copyright (C) 2002-2017 Nuclex Development Labs
|
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
|
||||||
|
|
||||||
This library is free software; you can redistribute it and/or
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
modify it under the terms of the IBM Common Public License as
|
you may not use this file except in compliance with the License.
|
||||||
published by the IBM Corporation; either version 1.0 of the
|
You may obtain a copy of the License at
|
||||||
License, or (at your option) any later version.
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
This library is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
Unless required by applicable law or agreed to in writing, software
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
IBM Common Public License for more details.
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
You should have received a copy of the IBM Common Public
|
limitations under the License.
|
||||||
License along with this library
|
*/
|
||||||
*/
|
#endregion // Apache License 2.0
|
||||||
#endregion
|
|
||||||
|
using System;
|
||||||
using System;
|
|
||||||
|
namespace Nuclex.Support.Collections {
|
||||||
namespace Nuclex.Support.Collections {
|
|
||||||
|
/// <summary>Base class for objects that can be parented to an owner</summary>
|
||||||
/// <summary>Base class for objects that can be parented to an owner</summary>
|
/// <typeparam name="TParent">Type of the parent object</typeparam>
|
||||||
/// <typeparam name="TParent">Type of the parent object</typeparam>
|
public class Parentable<TParent> {
|
||||||
public class Parentable<TParent> {
|
|
||||||
|
/// <summary>The parent object that owns this instance</summary>
|
||||||
/// <summary>The parent object that owns this instance</summary>
|
protected TParent Parent {
|
||||||
protected TParent Parent {
|
get { return this.parent; }
|
||||||
get { return this.parent; }
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Invoked whenever the instance's owner changes</summary>
|
||||||
/// <summary>Invoked whenever the instance's owner changes</summary>
|
/// <remarks>
|
||||||
/// <remarks>
|
/// When items are parented for the first time, the oldParent argument will
|
||||||
/// When items are parented for the first time, the oldParent argument will
|
/// be null. Also, if the element is removed from the collection, the
|
||||||
/// be null. Also, if the element is removed from the collection, the
|
/// current parent will be null.
|
||||||
/// current parent will be null.
|
/// </remarks>
|
||||||
/// </remarks>
|
/// <param name="oldParent">Previous owner of the instance</param>
|
||||||
/// <param name="oldParent">Previous owner of the instance</param>
|
protected virtual void OnParentChanged(TParent oldParent) { }
|
||||||
protected virtual void OnParentChanged(TParent oldParent) { }
|
|
||||||
|
/// <summary>Assigns a new parent to this instance</summary>
|
||||||
/// <summary>Assigns a new parent to this instance</summary>
|
internal void SetParent(TParent parent) {
|
||||||
internal void SetParent(TParent parent) {
|
TParent oldParent = this.parent;
|
||||||
TParent oldParent = this.parent;
|
this.parent = parent;
|
||||||
this.parent = parent;
|
|
||||||
|
OnParentChanged(oldParent);
|
||||||
OnParentChanged(oldParent);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Current parent of this object</summary>
|
||||||
/// <summary>Current parent of this object</summary>
|
private TParent parent;
|
||||||
private TParent parent;
|
|
||||||
|
}
|
||||||
}
|
|
||||||
|
} // namespace Nuclex.Support.Collections
|
||||||
} // namespace Nuclex.Support.Collections
|
|
||||||
|
|
|
@ -1,190 +1,189 @@
|
||||||
#region CPL License
|
#region Apache License 2.0
|
||||||
/*
|
/*
|
||||||
Nuclex Framework
|
Nuclex .NET Framework
|
||||||
Copyright (C) 2002-2017 Nuclex Development Labs
|
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
|
||||||
|
|
||||||
This library is free software; you can redistribute it and/or
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
modify it under the terms of the IBM Common Public License as
|
you may not use this file except in compliance with the License.
|
||||||
published by the IBM Corporation; either version 1.0 of the
|
You may obtain a copy of the License at
|
||||||
License, or (at your option) any later version.
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
This library is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
Unless required by applicable law or agreed to in writing, software
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
IBM Common Public License for more details.
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
You should have received a copy of the IBM Common Public
|
limitations under the License.
|
||||||
License along with this library
|
*/
|
||||||
*/
|
#endregion // Apache License 2.0
|
||||||
#endregion
|
|
||||||
|
using System;
|
||||||
using System;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Generic;
|
|
||||||
|
#if UNITTEST
|
||||||
#if UNITTEST
|
|
||||||
|
using NUnit.Framework;
|
||||||
using NUnit.Framework;
|
|
||||||
|
namespace Nuclex.Support.Collections {
|
||||||
namespace Nuclex.Support.Collections {
|
|
||||||
|
/// <summary>Unit Test for the Parenting Collection class</summary>
|
||||||
/// <summary>Unit Test for the Parenting Collection class</summary>
|
[TestFixture]
|
||||||
[TestFixture]
|
internal class ParentingCollectionTest {
|
||||||
internal class ParentingCollectionTest {
|
|
||||||
|
#region class TestParentable
|
||||||
#region class TestParentable
|
|
||||||
|
/// <summary>Parentable object that can be the child of an int</summary>
|
||||||
/// <summary>Parentable object that can be the child of an int</summary>
|
private class TestParentable : Parentable<int>, IDisposable {
|
||||||
private class TestParentable : Parentable<int>, IDisposable {
|
|
||||||
|
/// <summary>Initializes a new instance of the parentable test class</summary>
|
||||||
/// <summary>Initializes a new instance of the parentable test class</summary>
|
public TestParentable() { }
|
||||||
public TestParentable() { }
|
|
||||||
|
/// <summary>The parent object that owns this instance</summary>
|
||||||
/// <summary>The parent object that owns this instance</summary>
|
public int GetParent() {
|
||||||
public int GetParent() {
|
return base.Parent;
|
||||||
return base.Parent;
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Immediately releases all resources owned by the item</summary>
|
||||||
/// <summary>Immediately releases all resources owned by the item</summary>
|
public void Dispose() {
|
||||||
public void Dispose() {
|
this.disposeCalled = true;
|
||||||
this.disposeCalled = true;
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Whether Dispose() has been called on this item</summary>
|
||||||
/// <summary>Whether Dispose() has been called on this item</summary>
|
public bool DisposeCalled {
|
||||||
public bool DisposeCalled {
|
get { return this.disposeCalled; }
|
||||||
get { return this.disposeCalled; }
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Whether Dispose() has been called on this item</summary>
|
||||||
/// <summary>Whether Dispose() has been called on this item</summary>
|
private bool disposeCalled;
|
||||||
private bool disposeCalled;
|
|
||||||
|
}
|
||||||
}
|
|
||||||
|
#endregion // class TestParentable
|
||||||
#endregion // class TestParentable
|
|
||||||
|
#region class TestParentingCollection
|
||||||
#region class TestParentingCollection
|
|
||||||
|
/// <summary>Parentable object that can be the child of an int</summary>
|
||||||
/// <summary>Parentable object that can be the child of an int</summary>
|
private class TestParentingCollection : ParentingCollection<int, TestParentable> {
|
||||||
private class TestParentingCollection : ParentingCollection<int, TestParentable> {
|
|
||||||
|
/// <summary>Changes the parent of the collection</summary>
|
||||||
/// <summary>Changes the parent of the collection</summary>
|
/// <param name="parent">New parent to assign to the collection</param>
|
||||||
/// <param name="parent">New parent to assign to the collection</param>
|
public void SetParent(int parent) {
|
||||||
public void SetParent(int parent) {
|
base.Reparent(parent);
|
||||||
base.Reparent(parent);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Disposes all items contained in the collection</summary>
|
||||||
/// <summary>Disposes all items contained in the collection</summary>
|
public new void DisposeItems() {
|
||||||
public new void DisposeItems() {
|
base.DisposeItems();
|
||||||
base.DisposeItems();
|
}
|
||||||
}
|
|
||||||
|
}
|
||||||
}
|
|
||||||
|
#endregion // class TestParentingCollection
|
||||||
#endregion // class TestParentingCollection
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Tests whether the parenting collection propagates its parent to an item that
|
||||||
/// Tests whether the parenting collection propagates its parent to an item that
|
/// is added to the collection after the collection's aprent is already assigned
|
||||||
/// is added to the collection after the collection's aprent is already assigned
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void TestPropagatePreassignedParent() {
|
||||||
public void TestPropagatePreassignedParent() {
|
TestParentingCollection testCollection = new TestParentingCollection();
|
||||||
TestParentingCollection testCollection = new TestParentingCollection();
|
TestParentable testParentable = new TestParentable();
|
||||||
TestParentable testParentable = new TestParentable();
|
|
||||||
|
testCollection.SetParent(54321);
|
||||||
testCollection.SetParent(54321);
|
testCollection.Add(testParentable);
|
||||||
testCollection.Add(testParentable);
|
|
||||||
|
Assert.AreEqual(54321, testParentable.GetParent());
|
||||||
Assert.AreEqual(54321, testParentable.GetParent());
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Tests whether the parenting collection propagates a new parent to all items
|
||||||
/// Tests whether the parenting collection propagates a new parent to all items
|
/// contained in it when its parent is changed
|
||||||
/// contained in it when its parent is changed
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void TestPropagateParentChange() {
|
||||||
public void TestPropagateParentChange() {
|
TestParentingCollection testCollection = new TestParentingCollection();
|
||||||
TestParentingCollection testCollection = new TestParentingCollection();
|
TestParentable testParentable = new TestParentable();
|
||||||
TestParentable testParentable = new TestParentable();
|
|
||||||
|
testCollection.Add(testParentable);
|
||||||
testCollection.Add(testParentable);
|
testCollection.SetParent(54321);
|
||||||
testCollection.SetParent(54321);
|
|
||||||
|
Assert.AreEqual(54321, testParentable.GetParent());
|
||||||
Assert.AreEqual(54321, testParentable.GetParent());
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Tests whether the parenting collection propagates its parent to an item that
|
||||||
/// Tests whether the parenting collection propagates its parent to an item that
|
/// is added to the collection after the collection's aprent is already assigned
|
||||||
/// is added to the collection after the collection's aprent is already assigned
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void TestPropagateParentOnReplace() {
|
||||||
public void TestPropagateParentOnReplace() {
|
TestParentingCollection testCollection = new TestParentingCollection();
|
||||||
TestParentingCollection testCollection = new TestParentingCollection();
|
TestParentable testParentable1 = new TestParentable();
|
||||||
TestParentable testParentable1 = new TestParentable();
|
TestParentable testParentable2 = new TestParentable();
|
||||||
TestParentable testParentable2 = new TestParentable();
|
|
||||||
|
testCollection.SetParent(54321);
|
||||||
testCollection.SetParent(54321);
|
testCollection.Add(testParentable1);
|
||||||
testCollection.Add(testParentable1);
|
testCollection[0] = testParentable2;
|
||||||
testCollection[0] = testParentable2;
|
|
||||||
|
Assert.AreEqual(0, testParentable1.GetParent());
|
||||||
Assert.AreEqual(0, testParentable1.GetParent());
|
Assert.AreEqual(54321, testParentable2.GetParent());
|
||||||
Assert.AreEqual(54321, testParentable2.GetParent());
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Tests whether the parenting collection unsets the parent when an item is removed
|
||||||
/// Tests whether the parenting collection unsets the parent when an item is removed
|
/// from the collection
|
||||||
/// from the collection
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void TestUnsetParentOnRemoveItem() {
|
||||||
public void TestUnsetParentOnRemoveItem() {
|
TestParentingCollection testCollection = new TestParentingCollection();
|
||||||
TestParentingCollection testCollection = new TestParentingCollection();
|
TestParentable testParentable = new TestParentable();
|
||||||
TestParentable testParentable = new TestParentable();
|
|
||||||
|
testCollection.Add(testParentable);
|
||||||
testCollection.Add(testParentable);
|
testCollection.SetParent(54321);
|
||||||
testCollection.SetParent(54321);
|
|
||||||
|
Assert.AreEqual(54321, testParentable.GetParent());
|
||||||
Assert.AreEqual(54321, testParentable.GetParent());
|
|
||||||
|
testCollection.RemoveAt(0);
|
||||||
testCollection.RemoveAt(0);
|
|
||||||
|
Assert.AreEqual(0, testParentable.GetParent());
|
||||||
Assert.AreEqual(0, testParentable.GetParent());
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Tests whether the parenting collection unsets the parent when all item are
|
||||||
/// Tests whether the parenting collection unsets the parent when all item are
|
/// removed from the collection by clearing it
|
||||||
/// removed from the collection by clearing it
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void TestUnsetParentOnClear() {
|
||||||
public void TestUnsetParentOnClear() {
|
TestParentingCollection testCollection = new TestParentingCollection();
|
||||||
TestParentingCollection testCollection = new TestParentingCollection();
|
TestParentable testParentable = new TestParentable();
|
||||||
TestParentable testParentable = new TestParentable();
|
|
||||||
|
testCollection.Add(testParentable);
|
||||||
testCollection.Add(testParentable);
|
testCollection.SetParent(54321);
|
||||||
testCollection.SetParent(54321);
|
|
||||||
|
Assert.AreEqual(54321, testParentable.GetParent());
|
||||||
Assert.AreEqual(54321, testParentable.GetParent());
|
|
||||||
|
testCollection.Clear();
|
||||||
testCollection.Clear();
|
|
||||||
|
Assert.AreEqual(0, testParentable.GetParent());
|
||||||
Assert.AreEqual(0, testParentable.GetParent());
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Tests whether the parenting collection calls Dispose() on all contained items
|
||||||
/// Tests whether the parenting collection calls Dispose() on all contained items
|
/// that implement IDisposable when its DisposeItems() method is called
|
||||||
/// that implement IDisposable when its DisposeItems() method is called
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void TestDisposeItems() {
|
||||||
public void TestDisposeItems() {
|
TestParentingCollection testCollection = new TestParentingCollection();
|
||||||
TestParentingCollection testCollection = new TestParentingCollection();
|
TestParentable testParentable = new TestParentable();
|
||||||
TestParentable testParentable = new TestParentable();
|
|
||||||
|
testCollection.Add(testParentable);
|
||||||
testCollection.Add(testParentable);
|
|
||||||
|
testCollection.DisposeItems();
|
||||||
testCollection.DisposeItems();
|
|
||||||
|
Assert.IsTrue(testParentable.DisposeCalled);
|
||||||
Assert.IsTrue(testParentable.DisposeCalled);
|
}
|
||||||
}
|
|
||||||
|
}
|
||||||
}
|
|
||||||
|
} // namespace Nuclex.Support.Collections
|
||||||
} // namespace Nuclex.Support.Collections
|
|
||||||
|
#endif // UNITTEST
|
||||||
#endif // UNITTEST
|
|
||||||
|
|
|
@ -1,115 +1,114 @@
|
||||||
#region CPL License
|
#region Apache License 2.0
|
||||||
/*
|
/*
|
||||||
Nuclex Framework
|
Nuclex .NET Framework
|
||||||
Copyright (C) 2002-2017 Nuclex Development Labs
|
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
|
||||||
|
|
||||||
This library is free software; you can redistribute it and/or
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
modify it under the terms of the IBM Common Public License as
|
you may not use this file except in compliance with the License.
|
||||||
published by the IBM Corporation; either version 1.0 of the
|
You may obtain a copy of the License at
|
||||||
License, or (at your option) any later version.
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
This library is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
Unless required by applicable law or agreed to in writing, software
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
IBM Common Public License for more details.
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
You should have received a copy of the IBM Common Public
|
limitations under the License.
|
||||||
License along with this library
|
*/
|
||||||
*/
|
#endregion // Apache License 2.0
|
||||||
#endregion
|
|
||||||
|
using System;
|
||||||
using System;
|
using System.Collections.ObjectModel;
|
||||||
using System.Collections.ObjectModel;
|
|
||||||
|
namespace Nuclex.Support.Collections {
|
||||||
namespace Nuclex.Support.Collections {
|
|
||||||
|
/// <summary>Collection that automatically assigns an owner to all its elements</summary>
|
||||||
/// <summary>Collection that automatically assigns an owner to all its elements</summary>
|
/// <remarks>
|
||||||
/// <remarks>
|
/// This collection automatically assigns a parent object to elements that
|
||||||
/// This collection automatically assigns a parent object to elements that
|
/// are managed in it. The elements have to derive from the Parentable<>
|
||||||
/// are managed in it. The elements have to derive from the Parentable<>
|
/// base class.
|
||||||
/// base class.
|
/// </remarks>
|
||||||
/// </remarks>
|
/// <typeparam name="TParent">Type of the parent object to assign to items</typeparam>
|
||||||
/// <typeparam name="TParent">Type of the parent object to assign to items</typeparam>
|
/// <typeparam name="TItem">Type of the items being managed in the collection</typeparam>
|
||||||
/// <typeparam name="TItem">Type of the items being managed in the collection</typeparam>
|
public class ParentingCollection<TParent, TItem> : Collection<TItem>
|
||||||
public class ParentingCollection<TParent, TItem> : Collection<TItem>
|
where TItem : Parentable<TParent> {
|
||||||
where TItem : Parentable<TParent> {
|
|
||||||
|
/// <summary>Reparents all elements in the collection</summary>
|
||||||
/// <summary>Reparents all elements in the collection</summary>
|
/// <param name="parent">New parent to take ownership of the items</param>
|
||||||
/// <param name="parent">New parent to take ownership of the items</param>
|
protected void Reparent(TParent parent) {
|
||||||
protected void Reparent(TParent parent) {
|
this.parent = parent;
|
||||||
this.parent = parent;
|
|
||||||
|
for(int index = 0; index < Count; ++index)
|
||||||
for(int index = 0; index < Count; ++index)
|
base[index].SetParent(parent);
|
||||||
base[index].SetParent(parent);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Clears all elements from the collection</summary>
|
||||||
/// <summary>Clears all elements from the collection</summary>
|
protected override void ClearItems() {
|
||||||
protected override void ClearItems() {
|
for(int index = 0; index < Count; ++index)
|
||||||
for(int index = 0; index < Count; ++index)
|
base[index].SetParent(default(TParent));
|
||||||
base[index].SetParent(default(TParent));
|
|
||||||
|
base.ClearItems();
|
||||||
base.ClearItems();
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Inserts a new element into the collection</summary>
|
||||||
/// <summary>Inserts a new element into the collection</summary>
|
/// <param name="index">Index at which to insert the element</param>
|
||||||
/// <param name="index">Index at which to insert the element</param>
|
/// <param name="item">Item to be inserted</param>
|
||||||
/// <param name="item">Item to be inserted</param>
|
protected override void InsertItem(int index, TItem item) {
|
||||||
protected override void InsertItem(int index, TItem item) {
|
base.InsertItem(index, item);
|
||||||
base.InsertItem(index, item);
|
item.SetParent(this.parent);
|
||||||
item.SetParent(this.parent);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Removes an element from the collection</summary>
|
||||||
/// <summary>Removes an element from the collection</summary>
|
/// <param name="index">Index of the element to remove</param>
|
||||||
/// <param name="index">Index of the element to remove</param>
|
protected override void RemoveItem(int index) {
|
||||||
protected override void RemoveItem(int index) {
|
base[index].SetParent(default(TParent));
|
||||||
base[index].SetParent(default(TParent));
|
base.RemoveItem(index);
|
||||||
base.RemoveItem(index);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Takes over a new element that is directly assigned</summary>
|
||||||
/// <summary>Takes over a new element that is directly assigned</summary>
|
/// <param name="index">Index of the element that was assigned</param>
|
||||||
/// <param name="index">Index of the element that was assigned</param>
|
/// <param name="item">New item</param>
|
||||||
/// <param name="item">New item</param>
|
protected override void SetItem(int index, TItem item) {
|
||||||
protected override void SetItem(int index, TItem item) {
|
base[index].SetParent(default(TParent));
|
||||||
base[index].SetParent(default(TParent));
|
base.SetItem(index, item);
|
||||||
base.SetItem(index, item);
|
item.SetParent(this.parent);
|
||||||
item.SetParent(this.parent);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Disposes all items contained in the collection</summary>
|
||||||
/// <summary>Disposes all items contained in the collection</summary>
|
/// <remarks>
|
||||||
/// <remarks>
|
/// <para>
|
||||||
/// <para>
|
/// This method is intended to support collections that need to dispose their
|
||||||
/// This method is intended to support collections that need to dispose their
|
/// items. It will unparent all of the collection's items and call Dispose()
|
||||||
/// items. It will unparent all of the collection's items and call Dispose()
|
/// on any item that implements IDisposable.
|
||||||
/// on any item that implements IDisposable.
|
/// </para>
|
||||||
/// </para>
|
/// <para>
|
||||||
/// <para>
|
/// Do not call this method from your destructor as it will access the
|
||||||
/// Do not call this method from your destructor as it will access the
|
/// contained items in order to unparent and to Dispose() them, which leads
|
||||||
/// contained items in order to unparent and to Dispose() them, which leads
|
/// to undefined behavior since the object might have already been collected
|
||||||
/// to undefined behavior since the object might have already been collected
|
/// by the GC. Call it only if your object is being manually disposed.
|
||||||
/// by the GC. Call it only if your object is being manually disposed.
|
/// </para>
|
||||||
/// </para>
|
/// </remarks>
|
||||||
/// </remarks>
|
protected void DisposeItems() {
|
||||||
protected void DisposeItems() {
|
|
||||||
|
// Dispose all the items in the collection that implement IDisposable,
|
||||||
// Dispose all the items in the collection that implement IDisposable,
|
// starting from the last item in the assumption that this is the fastest
|
||||||
// starting from the last item in the assumption that this is the fastest
|
// way to empty a list without causing excessive shiftings in the array.
|
||||||
// way to empty a list without causing excessive shiftings in the array.
|
for(int index = base.Count - 1; index >= 0; --index) {
|
||||||
for(int index = base.Count - 1; index >= 0; --index) {
|
IDisposable disposable = base[index] as IDisposable;
|
||||||
IDisposable disposable = base[index] as IDisposable;
|
|
||||||
|
// If the item is disposable, destroy it now
|
||||||
// If the item is disposable, destroy it now
|
if(disposable != null) {
|
||||||
if(disposable != null) {
|
disposable.Dispose();
|
||||||
disposable.Dispose();
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
base.ClearItems();
|
||||||
base.ClearItems();
|
|
||||||
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Parent this collection currently belongs to</summary>
|
||||||
/// <summary>Parent this collection currently belongs to</summary>
|
private TParent parent;
|
||||||
private TParent parent;
|
|
||||||
|
}
|
||||||
}
|
|
||||||
|
} // namespace Nuclex.Support.Collections
|
||||||
} // namespace Nuclex.Support.Collections
|
|
||||||
|
|
|
@ -1,118 +1,117 @@
|
||||||
#region CPL License
|
#region Apache License 2.0
|
||||||
/*
|
/*
|
||||||
Nuclex Framework
|
Nuclex .NET Framework
|
||||||
Copyright (C) 2002-2017 Nuclex Development Labs
|
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
|
||||||
|
|
||||||
This library is free software; you can redistribute it and/or
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
modify it under the terms of the IBM Common Public License as
|
you may not use this file except in compliance with the License.
|
||||||
published by the IBM Corporation; either version 1.0 of the
|
You may obtain a copy of the License at
|
||||||
License, or (at your option) any later version.
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
This library is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
Unless required by applicable law or agreed to in writing, software
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
IBM Common Public License for more details.
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
You should have received a copy of the IBM Common Public
|
limitations under the License.
|
||||||
License along with this library
|
*/
|
||||||
*/
|
#endregion // Apache License 2.0
|
||||||
#endregion
|
|
||||||
|
#if UNITTEST
|
||||||
#if UNITTEST
|
|
||||||
|
using System;
|
||||||
using System;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Generic;
|
|
||||||
|
using NUnit.Framework;
|
||||||
using NUnit.Framework;
|
|
||||||
|
namespace Nuclex.Support.Collections {
|
||||||
namespace Nuclex.Support.Collections {
|
|
||||||
|
/// <summary>Unit tests for the Pool class</summary>
|
||||||
/// <summary>Unit tests for the Pool class</summary>
|
[TestFixture]
|
||||||
[TestFixture]
|
internal class PoolTest {
|
||||||
internal class PoolTest {
|
|
||||||
|
#region class TestClass
|
||||||
#region class TestClass
|
|
||||||
|
/// <summary>Used to test the pool</summary>
|
||||||
/// <summary>Used to test the pool</summary>
|
private class TestClass : IRecyclable {
|
||||||
private class TestClass : IRecyclable {
|
|
||||||
|
/// <summary>Returns the object to its initial state</summary>
|
||||||
/// <summary>Returns the object to its initial state</summary>
|
public void Recycle() {
|
||||||
public void Recycle() {
|
this.Recycled = true;
|
||||||
this.Recycled = true;
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Whether the instance has been recycled</summary>
|
||||||
/// <summary>Whether the instance has been recycled</summary>
|
public bool Recycled;
|
||||||
public bool Recycled;
|
|
||||||
|
}
|
||||||
}
|
|
||||||
|
#endregion // class TestClass
|
||||||
#endregion // class TestClass
|
|
||||||
|
#region class NoDefaultConstructor
|
||||||
#region class NoDefaultConstructor
|
|
||||||
|
/// <summary>Used to test the pool</summary>
|
||||||
/// <summary>Used to test the pool</summary>
|
private class NoDefaultConstructor {
|
||||||
private class NoDefaultConstructor {
|
|
||||||
|
/// <summary>Private constructor so no instances can be created</summary>
|
||||||
/// <summary>Private constructor so no instances can be created</summary>
|
private NoDefaultConstructor() { }
|
||||||
private NoDefaultConstructor() { }
|
|
||||||
|
}
|
||||||
}
|
|
||||||
|
#endregion // class NoDefaultConstructor
|
||||||
#endregion // class NoDefaultConstructor
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Verifies that the pool can return newly constructed objects
|
||||||
/// Verifies that the pool can return newly constructed objects
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void NewInstancesCanBeObtained() {
|
||||||
public void NewInstancesCanBeObtained() {
|
Pool<TestClass> pool = new Pool<TestClass>();
|
||||||
Pool<TestClass> pool = new Pool<TestClass>();
|
Assert.IsNotNull(pool.Get());
|
||||||
Assert.IsNotNull(pool.Get());
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Verifies that an exception is thrown if the pool's default instance creator is used
|
||||||
/// Verifies that an exception is thrown if the pool's default instance creator is used
|
/// on a type that doesn't have a default constructor
|
||||||
/// on a type that doesn't have a default constructor
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void UsingDefaultInstanceCreatorRequiresDefaultConstructor() {
|
||||||
public void UsingDefaultInstanceCreatorRequiresDefaultConstructor() {
|
Assert.Throws<ArgumentException>(
|
||||||
Assert.Throws<ArgumentException>(
|
delegate() { new Pool<NoDefaultConstructor>(); }
|
||||||
delegate() { new Pool<NoDefaultConstructor>(); }
|
);
|
||||||
);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Tests whether the pool can redeem objects that are no longer used
|
||||||
/// Tests whether the pool can redeem objects that are no longer used
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void InstancesCanBeRedeemed() {
|
||||||
public void InstancesCanBeRedeemed() {
|
Pool<TestClass> pool = new Pool<TestClass>();
|
||||||
Pool<TestClass> pool = new Pool<TestClass>();
|
pool.Redeem(new TestClass());
|
||||||
pool.Redeem(new TestClass());
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Tests whether the Recycle() method is called at the appropriate time
|
||||||
/// Tests whether the Recycle() method is called at the appropriate time
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void RedeemedItemsWillBeRecycled() {
|
||||||
public void RedeemedItemsWillBeRecycled() {
|
Pool<TestClass> pool = new Pool<TestClass>();
|
||||||
Pool<TestClass> pool = new Pool<TestClass>();
|
TestClass x = new TestClass();
|
||||||
TestClass x = new TestClass();
|
|
||||||
|
Assert.IsFalse(x.Recycled);
|
||||||
Assert.IsFalse(x.Recycled);
|
pool.Redeem(x);
|
||||||
pool.Redeem(x);
|
Assert.IsTrue(x.Recycled);
|
||||||
Assert.IsTrue(x.Recycled);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Verifies that the pool's Capacity is applied correctly</summary>
|
||||||
/// <summary>Verifies that the pool's Capacity is applied correctly</summary>
|
[Test]
|
||||||
[Test]
|
public void PoolCapacityCanBeAdjusted() {
|
||||||
public void PoolCapacityCanBeAdjusted() {
|
Pool<TestClass> pool = new Pool<TestClass>(123);
|
||||||
Pool<TestClass> pool = new Pool<TestClass>(123);
|
Assert.AreEqual(123, pool.Capacity);
|
||||||
Assert.AreEqual(123, pool.Capacity);
|
pool.Capacity = 321;
|
||||||
pool.Capacity = 321;
|
Assert.AreEqual(321, pool.Capacity);
|
||||||
Assert.AreEqual(321, pool.Capacity);
|
}
|
||||||
}
|
|
||||||
|
}
|
||||||
}
|
|
||||||
|
} // namespace Nuclex.Support.Collections
|
||||||
} // namespace Nuclex.Support.Collections
|
|
||||||
|
#endif // UNITTEST
|
||||||
#endif // UNITTEST
|
|
||||||
|
|
|
@ -1,175 +1,174 @@
|
||||||
#region CPL License
|
#region Apache License 2.0
|
||||||
/*
|
/*
|
||||||
Nuclex Framework
|
Nuclex .NET Framework
|
||||||
Copyright (C) 2002-2017 Nuclex Development Labs
|
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
|
||||||
|
|
||||||
This library is free software; you can redistribute it and/or
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
modify it under the terms of the IBM Common Public License as
|
you may not use this file except in compliance with the License.
|
||||||
published by the IBM Corporation; either version 1.0 of the
|
You may obtain a copy of the License at
|
||||||
License, or (at your option) any later version.
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
This library is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
Unless required by applicable law or agreed to in writing, software
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
IBM Common Public License for more details.
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
You should have received a copy of the IBM Common Public
|
limitations under the License.
|
||||||
License along with this library
|
*/
|
||||||
*/
|
#endregion // Apache License 2.0
|
||||||
#endregion
|
|
||||||
|
using System;
|
||||||
using System;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Generic;
|
|
||||||
|
namespace Nuclex.Support.Collections {
|
||||||
namespace Nuclex.Support.Collections {
|
|
||||||
|
/// <summary>Pool that recycles objects in order to avoid garbage build-up</summary>
|
||||||
/// <summary>Pool that recycles objects in order to avoid garbage build-up</summary>
|
/// <typeparam name="TItem">Type of objects being pooled</typeparam>
|
||||||
/// <typeparam name="TItem">Type of objects being pooled</typeparam>
|
/// <remarks>
|
||||||
/// <remarks>
|
/// <para>
|
||||||
/// <para>
|
/// Use this class to recycle objects instead of letting them become garbage,
|
||||||
/// Use this class to recycle objects instead of letting them become garbage,
|
/// creating new instances each time. The Pool class is designed to either be
|
||||||
/// creating new instances each time. The Pool class is designed to either be
|
/// used on its own or as a building block for a static class that wraps it.
|
||||||
/// used on its own or as a building block for a static class that wraps it.
|
/// </para>
|
||||||
/// </para>
|
/// <para>
|
||||||
/// <para>
|
/// Special care has to be taken to revert the entire state of a recycled
|
||||||
/// Special care has to be taken to revert the entire state of a recycled
|
/// object when it is returned to the pool. For example, events will need to
|
||||||
/// object when it is returned to the pool. For example, events will need to
|
/// have their subscriber lists emptied to avoid sending out events to the
|
||||||
/// have their subscriber lists emptied to avoid sending out events to the
|
/// wrong subscribers and accumulating more and more subscribers each time
|
||||||
/// wrong subscribers and accumulating more and more subscribers each time
|
/// they are reused.
|
||||||
/// they are reused.
|
/// </para>
|
||||||
/// </para>
|
/// <para>
|
||||||
/// <para>
|
/// To simplify such cleanup, pooled objects can implement the IRecyclable
|
||||||
/// To simplify such cleanup, pooled objects can implement the IRecyclable
|
/// interface. When an object is returned to the pool, the pool will
|
||||||
/// interface. When an object is returned to the pool, the pool will
|
/// automatically call its IRecyclable.Recycle() method.
|
||||||
/// automatically call its IRecyclable.Recycle() method.
|
/// </para>
|
||||||
/// </para>
|
/// </remarks>
|
||||||
/// </remarks>
|
public class Pool<TItem> {
|
||||||
public class Pool<TItem> {
|
|
||||||
|
/// <summary>Default number of recyclable objects the pool will store</summary>
|
||||||
/// <summary>Default number of recyclable objects the pool will store</summary>
|
public const int DefaultPoolSize = 64;
|
||||||
public const int DefaultPoolSize = 64;
|
|
||||||
|
/// <summary>Initializes a new pool using the default capacity</summary>
|
||||||
/// <summary>Initializes a new pool using the default capacity</summary>
|
public Pool() : this(DefaultPoolSize, null, null) { }
|
||||||
public Pool() : this(DefaultPoolSize, null, null) { }
|
|
||||||
|
/// <summary>Initializes a new pool using the default capacity</summary>
|
||||||
/// <summary>Initializes a new pool using the default capacity</summary>
|
/// <param name="createNewDelegate">Delegate that will be used to create new items</param>
|
||||||
/// <param name="createNewDelegate">Delegate that will be used to create new items</param>
|
public Pool(Func<TItem> createNewDelegate) :
|
||||||
public Pool(Func<TItem> createNewDelegate) :
|
this(DefaultPoolSize, createNewDelegate, null) { }
|
||||||
this(DefaultPoolSize, createNewDelegate, null) { }
|
|
||||||
|
/// <summary>Initializes a new pool using the default capacity</summary>
|
||||||
/// <summary>Initializes a new pool using the default capacity</summary>
|
/// <param name="createNewDelegate">Delegate that will be used to create new items</param>
|
||||||
/// <param name="createNewDelegate">Delegate that will be used to create new items</param>
|
/// <param name="recycleDelegate">Delegate that will be used to recycle items</param>
|
||||||
/// <param name="recycleDelegate">Delegate that will be used to recycle items</param>
|
public Pool(Func<TItem> createNewDelegate, Action<TItem> recycleDelegate) :
|
||||||
public Pool(Func<TItem> createNewDelegate, Action<TItem> recycleDelegate) :
|
this(DefaultPoolSize, createNewDelegate, recycleDelegate) { }
|
||||||
this(DefaultPoolSize, createNewDelegate, recycleDelegate) { }
|
|
||||||
|
/// <summary>Initializes a new pool using a user-specified capacity</summary>
|
||||||
/// <summary>Initializes a new pool using a user-specified capacity</summary>
|
/// <param name="capacity">Capacity of the pool</param>
|
||||||
/// <param name="capacity">Capacity of the pool</param>
|
public Pool(int capacity) :
|
||||||
public Pool(int capacity) :
|
this(capacity, null, null) { }
|
||||||
this(capacity, null, null) { }
|
|
||||||
|
/// <summary>Initializes a new pool using a user-specified capacity</summary>
|
||||||
/// <summary>Initializes a new pool using a user-specified capacity</summary>
|
/// <param name="capacity">Capacity of the pool</param>
|
||||||
/// <param name="capacity">Capacity of the pool</param>
|
/// <param name="createNewDelegate">Delegate that will be used to create new items</param>
|
||||||
/// <param name="createNewDelegate">Delegate that will be used to create new items</param>
|
public Pool(int capacity, Func<TItem> createNewDelegate) :
|
||||||
public Pool(int capacity, Func<TItem> createNewDelegate) :
|
this(capacity, createNewDelegate, null) { }
|
||||||
this(capacity, createNewDelegate, null) { }
|
|
||||||
|
/// <summary>Initializes a new pool using a user-specified capacity</summary>
|
||||||
/// <summary>Initializes a new pool using a user-specified capacity</summary>
|
/// <param name="capacity">Capacity of the pool</param>
|
||||||
/// <param name="capacity">Capacity of the pool</param>
|
/// <param name="createNewDelegate">Delegate that will be used to create new items</param>
|
||||||
/// <param name="createNewDelegate">Delegate that will be used to create new items</param>
|
/// <param name="recycleDelegate">Delegate that will be used to recycle items</param>
|
||||||
/// <param name="recycleDelegate">Delegate that will be used to recycle items</param>
|
public Pool(int capacity, Func<TItem> createNewDelegate, Action<TItem> recycleDelegate) {
|
||||||
public Pool(int capacity, Func<TItem> createNewDelegate, Action<TItem> recycleDelegate) {
|
Capacity = capacity;
|
||||||
Capacity = capacity;
|
|
||||||
|
if(createNewDelegate == null) {
|
||||||
if(createNewDelegate == null) {
|
if(!typeof(TItem).HasDefaultConstructor()) {
|
||||||
if(!typeof(TItem).HasDefaultConstructor()) {
|
throw new ArgumentException(
|
||||||
throw new ArgumentException(
|
"Type " + typeof(TItem).Name + " has no default constructor and " +
|
||||||
"Type " + typeof(TItem).Name + " has no default constructor and " +
|
"requires a custom 'create instance' delegate"
|
||||||
"requires a custom 'create instance' delegate"
|
);
|
||||||
);
|
}
|
||||||
}
|
createNewDelegate = new Func<TItem>(Activator.CreateInstance<TItem>);
|
||||||
createNewDelegate = new Func<TItem>(Activator.CreateInstance<TItem>);
|
}
|
||||||
}
|
if(recycleDelegate == null) {
|
||||||
if(recycleDelegate == null) {
|
recycleDelegate = new Action<TItem>(callRecycleIfSupported);
|
||||||
recycleDelegate = new Action<TItem>(callRecycleIfSupported);
|
}
|
||||||
}
|
|
||||||
|
this.createNewDelegate = createNewDelegate;
|
||||||
this.createNewDelegate = createNewDelegate;
|
this.recycleDelegate = recycleDelegate;
|
||||||
this.recycleDelegate = recycleDelegate;
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Returns a new or recycled instance of the types managed by the pool
|
||||||
/// Returns a new or recycled instance of the types managed by the pool
|
/// </summary>
|
||||||
/// </summary>
|
/// <returns>A new or recycled instance</returns>
|
||||||
/// <returns>A new or recycled instance</returns>
|
public TItem Get() {
|
||||||
public TItem Get() {
|
lock(this) {
|
||||||
lock(this) {
|
if(this.items.Count > 0) {
|
||||||
if(this.items.Count > 0) {
|
return this.items.Dequeue();
|
||||||
return this.items.Dequeue();
|
} else {
|
||||||
} else {
|
return this.createNewDelegate();
|
||||||
return this.createNewDelegate();
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Redeems an instance that is no longer used to be recycled by the pool
|
||||||
/// Redeems an instance that is no longer used to be recycled by the pool
|
/// </summary>
|
||||||
/// </summary>
|
/// <param name="item">The instance that will be redeemed</param>
|
||||||
/// <param name="item">The instance that will be redeemed</param>
|
public void Redeem(TItem item) {
|
||||||
public void Redeem(TItem item) {
|
|
||||||
|
// Call Recycle() when the object is redeemed (instead of when it leaves
|
||||||
// Call Recycle() when the object is redeemed (instead of when it leaves
|
// the pool again) in order to eliminate any references the object may hold
|
||||||
// the pool again) in order to eliminate any references the object may hold
|
// to other objects.
|
||||||
// to other objects.
|
this.recycleDelegate(item);
|
||||||
this.recycleDelegate(item);
|
|
||||||
|
lock(this) {
|
||||||
lock(this) {
|
if(this.items.Count < this.capacity) {
|
||||||
if(this.items.Count < this.capacity) {
|
this.items.Enqueue(item);
|
||||||
this.items.Enqueue(item);
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Number of objects the pool can retain</summary>
|
||||||
/// <summary>Number of objects the pool can retain</summary>
|
/// <remarks>
|
||||||
/// <remarks>
|
/// Changing this value causes the pool to be emtpied. It is recommended that
|
||||||
/// Changing this value causes the pool to be emtpied. It is recommended that
|
/// you only read the pool's capacity, never change it.
|
||||||
/// you only read the pool's capacity, never change it.
|
/// </remarks>
|
||||||
/// </remarks>
|
public int Capacity {
|
||||||
public int Capacity {
|
get { return this.capacity; }
|
||||||
get { return this.capacity; }
|
set {
|
||||||
set {
|
this.capacity = value;
|
||||||
this.capacity = value;
|
this.items = new Queue<TItem>(value);
|
||||||
this.items = new Queue<TItem>(value);
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Calls the Recycle() method on an objects if it implements
|
||||||
/// Calls the Recycle() method on an objects if it implements
|
/// the IRecyclable interface
|
||||||
/// the IRecyclable interface
|
/// </summary>
|
||||||
/// </summary>
|
/// <param name="item">
|
||||||
/// <param name="item">
|
/// Object whose Recycle() method will be called if supported by the object
|
||||||
/// Object whose Recycle() method will be called if supported by the object
|
/// </param>
|
||||||
/// </param>
|
private static void callRecycleIfSupported(TItem item) {
|
||||||
private static void callRecycleIfSupported(TItem item) {
|
IRecyclable recycleable = item as IRecyclable;
|
||||||
IRecyclable recycleable = item as IRecyclable;
|
if(recycleable != null) {
|
||||||
if(recycleable != null) {
|
recycleable.Recycle();
|
||||||
recycleable.Recycle();
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Objects being retained for recycling</summary>
|
||||||
/// <summary>Objects being retained for recycling</summary>
|
private Queue<TItem> items;
|
||||||
private Queue<TItem> items;
|
/// <summary>Capacity of the pool</summary>
|
||||||
/// <summary>Capacity of the pool</summary>
|
/// <remarks>
|
||||||
/// <remarks>
|
/// Required because the Queue class doesn't allow this value to be retrieved
|
||||||
/// Required because the Queue class doesn't allow this value to be retrieved
|
/// </remarks>
|
||||||
/// </remarks>
|
private int capacity;
|
||||||
private int capacity;
|
/// <summary>Delegate used to create new instances of the pool's type</summary>
|
||||||
/// <summary>Delegate used to create new instances of the pool's type</summary>
|
private Func<TItem> createNewDelegate;
|
||||||
private Func<TItem> createNewDelegate;
|
/// <summary>Delegate used to recycle instances</summary>
|
||||||
/// <summary>Delegate used to recycle instances</summary>
|
private Action<TItem> recycleDelegate;
|
||||||
private Action<TItem> recycleDelegate;
|
|
||||||
|
}
|
||||||
}
|
|
||||||
|
} // namespace Nuclex.Support.Collections
|
||||||
} // namespace Nuclex.Support.Collections
|
|
||||||
|
|
|
@ -1,100 +1,99 @@
|
||||||
#region CPL License
|
#region Apache License 2.0
|
||||||
/*
|
/*
|
||||||
Nuclex Framework
|
Nuclex .NET Framework
|
||||||
Copyright (C) 2002-2017 Nuclex Development Labs
|
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
|
||||||
|
|
||||||
This library is free software; you can redistribute it and/or
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
modify it under the terms of the IBM Common Public License as
|
you may not use this file except in compliance with the License.
|
||||||
published by the IBM Corporation; either version 1.0 of the
|
You may obtain a copy of the License at
|
||||||
License, or (at your option) any later version.
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
This library is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
Unless required by applicable law or agreed to in writing, software
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
IBM Common Public License for more details.
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
You should have received a copy of the IBM Common Public
|
limitations under the License.
|
||||||
License along with this library
|
*/
|
||||||
*/
|
#endregion // Apache License 2.0
|
||||||
#endregion
|
|
||||||
|
#if UNITTEST
|
||||||
#if UNITTEST
|
|
||||||
|
using System;
|
||||||
using System;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Generic;
|
|
||||||
|
using NUnit.Framework;
|
||||||
using NUnit.Framework;
|
|
||||||
|
namespace Nuclex.Support.Collections {
|
||||||
namespace Nuclex.Support.Collections {
|
|
||||||
|
/// <summary>Unit Test for the Priority/Item pair class</summary>
|
||||||
/// <summary>Unit Test for the Priority/Item pair class</summary>
|
[TestFixture]
|
||||||
[TestFixture]
|
internal class PriorityItemPairTest {
|
||||||
internal class PriorityItemPairTest {
|
|
||||||
|
#region class ToStringNullReturner
|
||||||
#region class ToStringNullReturner
|
|
||||||
|
/// <summary>Test class in which ToString() can return null</summary>
|
||||||
/// <summary>Test class in which ToString() can return null</summary>
|
private class ToStringNullReturner {
|
||||||
private class ToStringNullReturner {
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Returns a System.String that represents the current System.Object
|
||||||
/// Returns a System.String that represents the current System.Object
|
/// </summary>
|
||||||
/// </summary>
|
/// <returns>A System.String that represents the current System.Object</returns>
|
||||||
/// <returns>A System.String that represents the current System.Object</returns>
|
public override string ToString() { return null; }
|
||||||
public override string ToString() { return null; }
|
|
||||||
|
}
|
||||||
}
|
|
||||||
|
#endregion // class ToStringNullReturner
|
||||||
#endregion // class ToStringNullReturner
|
|
||||||
|
/// <summary>Tests whether the pair's default constructor works</summary>
|
||||||
/// <summary>Tests whether the pair's default constructor works</summary>
|
[Test]
|
||||||
[Test]
|
public void TestDefaultConstructor() {
|
||||||
public void TestDefaultConstructor() {
|
new PriorityItemPair<int, string>();
|
||||||
new PriorityItemPair<int, string>();
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Tests whether the priority can be retrieved from the pair</summary>
|
||||||
/// <summary>Tests whether the priority can be retrieved from the pair</summary>
|
[Test]
|
||||||
[Test]
|
public void TestPriorityRetrieval() {
|
||||||
public void TestPriorityRetrieval() {
|
PriorityItemPair<int, string> testPair = new PriorityItemPair<int, string>(
|
||||||
PriorityItemPair<int, string> testPair = new PriorityItemPair<int, string>(
|
12345, "hello world"
|
||||||
12345, "hello world"
|
);
|
||||||
);
|
|
||||||
|
Assert.AreEqual(12345, testPair.Priority);
|
||||||
Assert.AreEqual(12345, testPair.Priority);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Tests whether the item can be retrieved from the pair</summary>
|
||||||
/// <summary>Tests whether the item can be retrieved from the pair</summary>
|
[Test]
|
||||||
[Test]
|
public void TestItemRetrieval() {
|
||||||
public void TestItemRetrieval() {
|
PriorityItemPair<int, string> testPair = new PriorityItemPair<int, string>(
|
||||||
PriorityItemPair<int, string> testPair = new PriorityItemPair<int, string>(
|
12345, "hello world"
|
||||||
12345, "hello world"
|
);
|
||||||
);
|
|
||||||
|
Assert.AreEqual("hello world", testPair.Item);
|
||||||
Assert.AreEqual("hello world", testPair.Item);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Tests whether the ToString() methods works with valid strings</summary>
|
||||||
/// <summary>Tests whether the ToString() methods works with valid strings</summary>
|
[Test]
|
||||||
[Test]
|
public void TestToStringWithValidStrings() {
|
||||||
public void TestToStringWithValidStrings() {
|
PriorityItemPair<string, string> testPair = new PriorityItemPair<string, string>(
|
||||||
PriorityItemPair<string, string> testPair = new PriorityItemPair<string, string>(
|
"hello", "world"
|
||||||
"hello", "world"
|
);
|
||||||
);
|
|
||||||
|
Assert.AreEqual("[hello, world]", testPair.ToString());
|
||||||
Assert.AreEqual("[hello, world]", testPair.ToString());
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Tests whether the ToString() methods works with null strings</summary>
|
||||||
/// <summary>Tests whether the ToString() methods works with null strings</summary>
|
[Test]
|
||||||
[Test]
|
public void TestToStringWithNullStrings() {
|
||||||
public void TestToStringWithNullStrings() {
|
PriorityItemPair<ToStringNullReturner, ToStringNullReturner> testPair =
|
||||||
PriorityItemPair<ToStringNullReturner, ToStringNullReturner> testPair =
|
new PriorityItemPair<ToStringNullReturner, ToStringNullReturner>(
|
||||||
new PriorityItemPair<ToStringNullReturner, ToStringNullReturner>(
|
new ToStringNullReturner(), new ToStringNullReturner()
|
||||||
new ToStringNullReturner(), new ToStringNullReturner()
|
);
|
||||||
);
|
|
||||||
|
Assert.AreEqual("[, ]", testPair.ToString());
|
||||||
Assert.AreEqual("[, ]", testPair.ToString());
|
}
|
||||||
}
|
|
||||||
|
}
|
||||||
}
|
|
||||||
|
} // namespace Nuclex.Support.Collections
|
||||||
} // namespace Nuclex.Support.Collections
|
|
||||||
|
#endif // UNITTEST
|
||||||
#endif // UNITTEST
|
|
||||||
|
|
|
@ -1,75 +1,74 @@
|
||||||
#region CPL License
|
#region Apache License 2.0
|
||||||
/*
|
/*
|
||||||
Nuclex Framework
|
Nuclex .NET Framework
|
||||||
Copyright (C) 2002-2017 Nuclex Development Labs
|
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
|
||||||
|
|
||||||
This library is free software; you can redistribute it and/or
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
modify it under the terms of the IBM Common Public License as
|
you may not use this file except in compliance with the License.
|
||||||
published by the IBM Corporation; either version 1.0 of the
|
You may obtain a copy of the License at
|
||||||
License, or (at your option) any later version.
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
This library is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
Unless required by applicable law or agreed to in writing, software
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
IBM Common Public License for more details.
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
You should have received a copy of the IBM Common Public
|
limitations under the License.
|
||||||
License along with this library
|
*/
|
||||||
*/
|
#endregion // Apache License 2.0
|
||||||
#endregion
|
|
||||||
|
using System;
|
||||||
using System;
|
using System.Text;
|
||||||
using System.Text;
|
|
||||||
|
namespace Nuclex.Support.Collections {
|
||||||
namespace Nuclex.Support.Collections {
|
|
||||||
|
/// <summary>An pair of a priority and an item</summary>
|
||||||
/// <summary>An pair of a priority and an item</summary>
|
public struct PriorityItemPair<TPriority, TItem> {
|
||||||
public struct PriorityItemPair<TPriority, TItem> {
|
|
||||||
|
/// <summary>Initializes a new priority / item pair</summary>
|
||||||
/// <summary>Initializes a new priority / item pair</summary>
|
/// <param name="priority">Priority of the item in the pair</param>
|
||||||
/// <param name="priority">Priority of the item in the pair</param>
|
/// <param name="item">Item to be stored in the pair</param>
|
||||||
/// <param name="item">Item to be stored in the pair</param>
|
public PriorityItemPair(TPriority priority, TItem item) {
|
||||||
public PriorityItemPair(TPriority priority, TItem item) {
|
this.Priority = priority;
|
||||||
this.Priority = priority;
|
this.Item = item;
|
||||||
this.Item = item;
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Priority assigned to this priority / item pair</summary>
|
||||||
/// <summary>Priority assigned to this priority / item pair</summary>
|
public TPriority Priority;
|
||||||
public TPriority Priority;
|
/// <summary>Item contained in this priority / item pair</summary>
|
||||||
/// <summary>Item contained in this priority / item pair</summary>
|
public TItem Item;
|
||||||
public TItem Item;
|
|
||||||
|
/// <summary>Converts the priority / item pair into a string</summary>
|
||||||
/// <summary>Converts the priority / item pair into a string</summary>
|
/// <returns>A string describing the priority / item pair</returns>
|
||||||
/// <returns>A string describing the priority / item pair</returns>
|
public override string ToString() {
|
||||||
public override string ToString() {
|
int length = 4;
|
||||||
int length = 4;
|
|
||||||
|
// Convert the priority value into a string or use the empty string
|
||||||
// Convert the priority value into a string or use the empty string
|
// constant if the ToString() overload returns null
|
||||||
// constant if the ToString() overload returns null
|
string priorityString = this.Priority.ToString();
|
||||||
string priorityString = this.Priority.ToString();
|
if(priorityString != null)
|
||||||
if(priorityString != null)
|
length += priorityString.Length;
|
||||||
length += priorityString.Length;
|
else
|
||||||
else
|
priorityString = string.Empty;
|
||||||
priorityString = string.Empty;
|
|
||||||
|
// Convert the item value into a string or use the empty string
|
||||||
// Convert the item value into a string or use the empty string
|
// constant if the ToString() overload returns null
|
||||||
// constant if the ToString() overload returns null
|
string itemString = this.Item.ToString();
|
||||||
string itemString = this.Item.ToString();
|
if(itemString != null)
|
||||||
if(itemString != null)
|
length += itemString.Length;
|
||||||
length += itemString.Length;
|
else
|
||||||
else
|
itemString = string.Empty;
|
||||||
itemString = string.Empty;
|
|
||||||
|
// Concatenate priority and item into a single string
|
||||||
// Concatenate priority and item into a single string
|
StringBuilder builder = new StringBuilder(length);
|
||||||
StringBuilder builder = new StringBuilder(length);
|
builder.Append('[');
|
||||||
builder.Append('[');
|
builder.Append(priorityString);
|
||||||
builder.Append(priorityString);
|
builder.Append(", ");
|
||||||
builder.Append(", ");
|
builder.Append(itemString);
|
||||||
builder.Append(itemString);
|
builder.Append(']');
|
||||||
builder.Append(']');
|
return builder.ToString();
|
||||||
return builder.ToString();
|
}
|
||||||
}
|
|
||||||
|
}
|
||||||
}
|
|
||||||
|
} // namespace Nuclex.Support.Collections
|
||||||
} // namespace Nuclex.Support.Collections
|
|
||||||
|
|
|
@ -1,158 +1,157 @@
|
||||||
#region CPL License
|
#region Apache License 2.0
|
||||||
/*
|
/*
|
||||||
Nuclex Framework
|
Nuclex .NET Framework
|
||||||
Copyright (C) 2002-2017 Nuclex Development Labs
|
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
|
||||||
|
|
||||||
This library is free software; you can redistribute it and/or
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
modify it under the terms of the IBM Common Public License as
|
you may not use this file except in compliance with the License.
|
||||||
published by the IBM Corporation; either version 1.0 of the
|
You may obtain a copy of the License at
|
||||||
License, or (at your option) any later version.
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
This library is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
Unless required by applicable law or agreed to in writing, software
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
IBM Common Public License for more details.
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
You should have received a copy of the IBM Common Public
|
limitations under the License.
|
||||||
License along with this library
|
*/
|
||||||
*/
|
#endregion // Apache License 2.0
|
||||||
#endregion
|
|
||||||
|
using System;
|
||||||
using System;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Generic;
|
|
||||||
|
#if UNITTEST
|
||||||
#if UNITTEST
|
|
||||||
|
using NUnit.Framework;
|
||||||
using NUnit.Framework;
|
|
||||||
|
namespace Nuclex.Support.Collections {
|
||||||
namespace Nuclex.Support.Collections {
|
|
||||||
|
/// <summary>Unit Test for the priority queue class</summary>
|
||||||
/// <summary>Unit Test for the priority queue class</summary>
|
[TestFixture]
|
||||||
[TestFixture]
|
internal class PriorityQueueTest {
|
||||||
internal class PriorityQueueTest {
|
|
||||||
|
#region class FloatComparer
|
||||||
#region class FloatComparer
|
|
||||||
|
/// <summary>Comparer for two floating point values</summary>
|
||||||
/// <summary>Comparer for two floating point values</summary>
|
private class FloatComparer : IComparer<float> {
|
||||||
private class FloatComparer : IComparer<float> {
|
|
||||||
|
/// <summary>The default instance of this comparer</summary>
|
||||||
/// <summary>The default instance of this comparer</summary>
|
public static readonly FloatComparer Default = new FloatComparer();
|
||||||
public static readonly FloatComparer Default = new FloatComparer();
|
|
||||||
|
/// <summary>Compares two floating points against each other</summary>
|
||||||
/// <summary>Compares two floating points against each other</summary>
|
/// <param name="left">First float to compare</param>
|
||||||
/// <param name="left">First float to compare</param>
|
/// <param name="right">Second float to compare</param>
|
||||||
/// <param name="right">Second float to compare</param>
|
/// <returns>The relationship of the two floats to each other</returns>
|
||||||
/// <returns>The relationship of the two floats to each other</returns>
|
public int Compare(float left, float right) {
|
||||||
public int Compare(float left, float right) {
|
return Math.Sign(left - right);
|
||||||
return Math.Sign(left - right);
|
}
|
||||||
}
|
|
||||||
|
}
|
||||||
}
|
|
||||||
|
#endregion // class FloatComparer
|
||||||
#endregion // class FloatComparer
|
|
||||||
|
/// <summary>Tests to ensure the count property is properly updated</summary>
|
||||||
/// <summary>Tests to ensure the count property is properly updated</summary>
|
[Test]
|
||||||
[Test]
|
public void TestCount() {
|
||||||
public void TestCount() {
|
PriorityQueue<float> testQueue = new PriorityQueue<float>(FloatComparer.Default);
|
||||||
PriorityQueue<float> testQueue = new PriorityQueue<float>(FloatComparer.Default);
|
|
||||||
|
Assert.AreEqual(0, testQueue.Count);
|
||||||
Assert.AreEqual(0, testQueue.Count);
|
testQueue.Enqueue(12.34f);
|
||||||
testQueue.Enqueue(12.34f);
|
Assert.AreEqual(1, testQueue.Count);
|
||||||
Assert.AreEqual(1, testQueue.Count);
|
testQueue.Enqueue(56.78f);
|
||||||
testQueue.Enqueue(56.78f);
|
Assert.AreEqual(2, testQueue.Count);
|
||||||
Assert.AreEqual(2, testQueue.Count);
|
testQueue.Dequeue();
|
||||||
testQueue.Dequeue();
|
Assert.AreEqual(1, testQueue.Count);
|
||||||
Assert.AreEqual(1, testQueue.Count);
|
testQueue.Enqueue(9.0f);
|
||||||
testQueue.Enqueue(9.0f);
|
Assert.AreEqual(2, testQueue.Count);
|
||||||
Assert.AreEqual(2, testQueue.Count);
|
testQueue.Clear();
|
||||||
testQueue.Clear();
|
Assert.AreEqual(0, testQueue.Count);
|
||||||
Assert.AreEqual(0, testQueue.Count);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Tests to ensure that the priority collection actually sorts items</summary>
|
||||||
/// <summary>Tests to ensure that the priority collection actually sorts items</summary>
|
[Test]
|
||||||
[Test]
|
public void TestOrdering() {
|
||||||
public void TestOrdering() {
|
PriorityQueue<float> testQueue = new PriorityQueue<float>(FloatComparer.Default);
|
||||||
PriorityQueue<float> testQueue = new PriorityQueue<float>(FloatComparer.Default);
|
|
||||||
|
testQueue.Enqueue(1.0f);
|
||||||
testQueue.Enqueue(1.0f);
|
testQueue.Enqueue(9.0f);
|
||||||
testQueue.Enqueue(9.0f);
|
testQueue.Enqueue(2.0f);
|
||||||
testQueue.Enqueue(2.0f);
|
testQueue.Enqueue(8.0f);
|
||||||
testQueue.Enqueue(8.0f);
|
testQueue.Enqueue(3.0f);
|
||||||
testQueue.Enqueue(3.0f);
|
testQueue.Enqueue(7.0f);
|
||||||
testQueue.Enqueue(7.0f);
|
testQueue.Enqueue(4.0f);
|
||||||
testQueue.Enqueue(4.0f);
|
testQueue.Enqueue(6.0f);
|
||||||
testQueue.Enqueue(6.0f);
|
testQueue.Enqueue(5.0f);
|
||||||
testQueue.Enqueue(5.0f);
|
|
||||||
|
Assert.AreEqual(9.0f, testQueue.Dequeue());
|
||||||
Assert.AreEqual(9.0f, testQueue.Dequeue());
|
Assert.AreEqual(8.0f, testQueue.Dequeue());
|
||||||
Assert.AreEqual(8.0f, testQueue.Dequeue());
|
Assert.AreEqual(7.0f, testQueue.Dequeue());
|
||||||
Assert.AreEqual(7.0f, testQueue.Dequeue());
|
Assert.AreEqual(6.0f, testQueue.Dequeue());
|
||||||
Assert.AreEqual(6.0f, testQueue.Dequeue());
|
Assert.AreEqual(5.0f, testQueue.Dequeue());
|
||||||
Assert.AreEqual(5.0f, testQueue.Dequeue());
|
Assert.AreEqual(4.0f, testQueue.Dequeue());
|
||||||
Assert.AreEqual(4.0f, testQueue.Dequeue());
|
Assert.AreEqual(3.0f, testQueue.Dequeue());
|
||||||
Assert.AreEqual(3.0f, testQueue.Dequeue());
|
Assert.AreEqual(2.0f, testQueue.Dequeue());
|
||||||
Assert.AreEqual(2.0f, testQueue.Dequeue());
|
Assert.AreEqual(1.0f, testQueue.Dequeue());
|
||||||
Assert.AreEqual(1.0f, testQueue.Dequeue());
|
}
|
||||||
}
|
|
||||||
|
#if DEBUG
|
||||||
#if DEBUG
|
/// <summary>
|
||||||
/// <summary>
|
/// Tests whether the priority queue's enumerators are invalidated when the queue's
|
||||||
/// Tests whether the priority queue's enumerators are invalidated when the queue's
|
/// contents are modified
|
||||||
/// contents are modified
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void TestEnumeratorInvalidationOnModify() {
|
||||||
public void TestEnumeratorInvalidationOnModify() {
|
PriorityQueue<int> testQueue = new PriorityQueue<int>();
|
||||||
PriorityQueue<int> testQueue = new PriorityQueue<int>();
|
IEnumerator<int> testQueueEnumerator = testQueue.GetEnumerator();
|
||||||
IEnumerator<int> testQueueEnumerator = testQueue.GetEnumerator();
|
|
||||||
|
testQueue.Enqueue(123);
|
||||||
testQueue.Enqueue(123);
|
|
||||||
|
Assert.Throws<InvalidOperationException>(
|
||||||
Assert.Throws<InvalidOperationException>(
|
delegate() { testQueueEnumerator.MoveNext(); }
|
||||||
delegate() { testQueueEnumerator.MoveNext(); }
|
);
|
||||||
);
|
}
|
||||||
}
|
#endif
|
||||||
#endif
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Verifies that an exception is thrown when Peek() is called on an empty queue
|
||||||
/// Verifies that an exception is thrown when Peek() is called on an empty queue
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void TestPeekEmptyQueue() {
|
||||||
public void TestPeekEmptyQueue() {
|
PriorityQueue<int> testQueue = new PriorityQueue<int>();
|
||||||
PriorityQueue<int> testQueue = new PriorityQueue<int>();
|
Assert.Throws<InvalidOperationException>(
|
||||||
Assert.Throws<InvalidOperationException>(
|
delegate() { testQueue.Peek(); }
|
||||||
delegate() { testQueue.Peek(); }
|
);
|
||||||
);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Verifies that an exception is thrown when Dequeue() is called on an empty queue
|
||||||
/// Verifies that an exception is thrown when Dequeue() is called on an empty queue
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void TestDequeueEmptyQueue() {
|
||||||
public void TestDequeueEmptyQueue() {
|
PriorityQueue<int> testQueue = new PriorityQueue<int>();
|
||||||
PriorityQueue<int> testQueue = new PriorityQueue<int>();
|
Assert.Throws<InvalidOperationException>(
|
||||||
Assert.Throws<InvalidOperationException>(
|
delegate() { testQueue.Dequeue(); }
|
||||||
delegate() { testQueue.Dequeue(); }
|
);
|
||||||
);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Verifies that the priority queue can handle large amounts of data
|
||||||
/// Verifies that the priority queue can handle large amounts of data
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void TestLargeQueue() {
|
||||||
public void TestLargeQueue() {
|
PriorityQueue<int> testQueue = new PriorityQueue<int>();
|
||||||
PriorityQueue<int> testQueue = new PriorityQueue<int>();
|
List<int> testList = new List<int>();
|
||||||
List<int> testList = new List<int>();
|
|
||||||
|
for(int index = 0; index < 1000; ++index) {
|
||||||
for(int index = 0; index < 1000; ++index) {
|
testQueue.Enqueue(index * 2);
|
||||||
testQueue.Enqueue(index * 2);
|
testList.Add(index * 2);
|
||||||
testList.Add(index * 2);
|
}
|
||||||
}
|
|
||||||
|
CollectionAssert.AreEquivalent(testList, testQueue);
|
||||||
CollectionAssert.AreEquivalent(testList, testQueue);
|
}
|
||||||
}
|
|
||||||
|
}
|
||||||
}
|
|
||||||
|
} // namespace Nuclex.Support.Collections
|
||||||
} // namespace Nuclex.Support.Collections
|
|
||||||
|
#endif // UNITTEST
|
||||||
#endif // UNITTEST
|
|
||||||
|
|
|
@ -1,285 +1,284 @@
|
||||||
#region CPL License
|
#region Apache License 2.0
|
||||||
/*
|
/*
|
||||||
Nuclex Framework
|
Nuclex .NET Framework
|
||||||
Copyright (C) 2002-2017 Nuclex Development Labs
|
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
|
||||||
|
|
||||||
This library is free software; you can redistribute it and/or
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
modify it under the terms of the IBM Common Public License as
|
you may not use this file except in compliance with the License.
|
||||||
published by the IBM Corporation; either version 1.0 of the
|
You may obtain a copy of the License at
|
||||||
License, or (at your option) any later version.
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
This library is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
Unless required by applicable law or agreed to in writing, software
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
IBM Common Public License for more details.
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
You should have received a copy of the IBM Common Public
|
limitations under the License.
|
||||||
License along with this library
|
*/
|
||||||
*/
|
#endregion // Apache License 2.0
|
||||||
#endregion
|
|
||||||
|
using System;
|
||||||
using System;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Generic;
|
using System.Collections;
|
||||||
using System.Collections;
|
|
||||||
|
namespace Nuclex.Support.Collections {
|
||||||
namespace Nuclex.Support.Collections {
|
|
||||||
|
/// <summary>Queue that dequeues items in order of their priority</summary>
|
||||||
/// <summary>Queue that dequeues items in order of their priority</summary>
|
public class PriorityQueue<TItem> : ICollection, IEnumerable<TItem> {
|
||||||
public class PriorityQueue<TItem> : ICollection, IEnumerable<TItem> {
|
|
||||||
|
#region class Enumerator
|
||||||
#region class Enumerator
|
|
||||||
|
/// <summary>Enumerates all items contained in a priority queue</summary>
|
||||||
/// <summary>Enumerates all items contained in a priority queue</summary>
|
private class Enumerator : IEnumerator<TItem> {
|
||||||
private class Enumerator : IEnumerator<TItem> {
|
|
||||||
|
/// <summary>Initializes a new priority queue enumerator</summary>
|
||||||
/// <summary>Initializes a new priority queue enumerator</summary>
|
/// <param name="priorityQueue">Priority queue to be enumerated</param>
|
||||||
/// <param name="priorityQueue">Priority queue to be enumerated</param>
|
public Enumerator(PriorityQueue<TItem> priorityQueue) {
|
||||||
public Enumerator(PriorityQueue<TItem> priorityQueue) {
|
this.priorityQueue = priorityQueue;
|
||||||
this.priorityQueue = priorityQueue;
|
Reset();
|
||||||
Reset();
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Resets the enumerator to its initial state</summary>
|
||||||
/// <summary>Resets the enumerator to its initial state</summary>
|
public void Reset() {
|
||||||
public void Reset() {
|
this.index = -1;
|
||||||
this.index = -1;
|
#if DEBUG
|
||||||
#if DEBUG
|
this.expectedVersion = this.priorityQueue.version;
|
||||||
this.expectedVersion = this.priorityQueue.version;
|
#endif
|
||||||
#endif
|
}
|
||||||
}
|
|
||||||
|
/// <summary>The current item being enumerated</summary>
|
||||||
/// <summary>The current item being enumerated</summary>
|
TItem IEnumerator<TItem>.Current {
|
||||||
TItem IEnumerator<TItem>.Current {
|
get {
|
||||||
get {
|
#if DEBUG
|
||||||
#if DEBUG
|
checkVersion();
|
||||||
checkVersion();
|
#endif
|
||||||
#endif
|
return this.priorityQueue.heap[index];
|
||||||
return this.priorityQueue.heap[index];
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Moves to the next item in the priority queue</summary>
|
||||||
/// <summary>Moves to the next item in the priority queue</summary>
|
/// <returns>True if a next item was found, false if the end has been reached</returns>
|
||||||
/// <returns>True if a next item was found, false if the end has been reached</returns>
|
public bool MoveNext() {
|
||||||
public bool MoveNext() {
|
#if DEBUG
|
||||||
#if DEBUG
|
checkVersion();
|
||||||
checkVersion();
|
#endif
|
||||||
#endif
|
if(this.index + 1 == this.priorityQueue.count)
|
||||||
if(this.index + 1 == this.priorityQueue.count)
|
return false;
|
||||||
return false;
|
|
||||||
|
++this.index;
|
||||||
++this.index;
|
|
||||||
|
return true;
|
||||||
return true;
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Releases all resources used by the enumerator</summary>
|
||||||
/// <summary>Releases all resources used by the enumerator</summary>
|
public void Dispose() { }
|
||||||
public void Dispose() { }
|
|
||||||
|
#if DEBUG
|
||||||
#if DEBUG
|
/// <summary>Ensures that the priority queue has not changed</summary>
|
||||||
/// <summary>Ensures that the priority queue has not changed</summary>
|
private void checkVersion() {
|
||||||
private void checkVersion() {
|
if(this.expectedVersion != this.priorityQueue.version)
|
||||||
if(this.expectedVersion != this.priorityQueue.version)
|
throw new InvalidOperationException("Priority queue has been modified");
|
||||||
throw new InvalidOperationException("Priority queue has been modified");
|
}
|
||||||
}
|
#endif
|
||||||
#endif
|
|
||||||
|
/// <summary>The current item being enumerated</summary>
|
||||||
/// <summary>The current item being enumerated</summary>
|
object IEnumerator.Current {
|
||||||
object IEnumerator.Current {
|
get {
|
||||||
get {
|
#if DEBUG
|
||||||
#if DEBUG
|
checkVersion();
|
||||||
checkVersion();
|
#endif
|
||||||
#endif
|
return this.priorityQueue.heap[index];
|
||||||
return this.priorityQueue.heap[index];
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Index of the current item in the priority queue</summary>
|
||||||
/// <summary>Index of the current item in the priority queue</summary>
|
private int index;
|
||||||
private int index;
|
/// <summary>The priority queue whose items this instance enumerates</summary>
|
||||||
/// <summary>The priority queue whose items this instance enumerates</summary>
|
private PriorityQueue<TItem> priorityQueue;
|
||||||
private PriorityQueue<TItem> priorityQueue;
|
#if DEBUG
|
||||||
#if DEBUG
|
/// <summary>Expected version of the priority queue</summary>
|
||||||
/// <summary>Expected version of the priority queue</summary>
|
private int expectedVersion;
|
||||||
private int expectedVersion;
|
#endif
|
||||||
#endif
|
|
||||||
|
}
|
||||||
}
|
|
||||||
|
#endregion // class Enumerator
|
||||||
#endregion // class Enumerator
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Initializes a new priority queue using IComparable for comparing items
|
||||||
/// Initializes a new priority queue using IComparable for comparing items
|
/// </summary>
|
||||||
/// </summary>
|
public PriorityQueue() : this(Comparer<TItem>.Default) { }
|
||||||
public PriorityQueue() : this(Comparer<TItem>.Default) { }
|
|
||||||
|
/// <summary>Initializes a new priority queue</summary>
|
||||||
/// <summary>Initializes a new priority queue</summary>
|
/// <param name="comparer">Comparer to use for ordering the items</param>
|
||||||
/// <param name="comparer">Comparer to use for ordering the items</param>
|
public PriorityQueue(IComparer<TItem> comparer) {
|
||||||
public PriorityQueue(IComparer<TItem> comparer) {
|
this.comparer = comparer;
|
||||||
this.comparer = comparer;
|
this.capacity = 15; // 15 is equal to 4 complete levels
|
||||||
this.capacity = 15; // 15 is equal to 4 complete levels
|
this.heap = new TItem[this.capacity];
|
||||||
this.heap = new TItem[this.capacity];
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Returns the topmost item in the queue without dequeueing it</summary>
|
||||||
/// <summary>Returns the topmost item in the queue without dequeueing it</summary>
|
/// <returns>The topmost item in the queue</returns>
|
||||||
/// <returns>The topmost item in the queue</returns>
|
public TItem Peek() {
|
||||||
public TItem Peek() {
|
if(this.count == 0) {
|
||||||
if(this.count == 0) {
|
throw new InvalidOperationException("No items queued");
|
||||||
throw new InvalidOperationException("No items queued");
|
}
|
||||||
}
|
|
||||||
|
return this.heap[0];
|
||||||
return this.heap[0];
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Takes the item with the highest priority off from the queue</summary>
|
||||||
/// <summary>Takes the item with the highest priority off from the queue</summary>
|
/// <returns>The item with the highest priority in the list</returns>
|
||||||
/// <returns>The item with the highest priority in the list</returns>
|
/// <exception cref="InvalidOperationException">When the queue is empty</exception>
|
||||||
/// <exception cref="InvalidOperationException">When the queue is empty</exception>
|
public TItem Dequeue() {
|
||||||
public TItem Dequeue() {
|
if(this.count == 0) {
|
||||||
if(this.count == 0) {
|
throw new InvalidOperationException("No items available to dequeue");
|
||||||
throw new InvalidOperationException("No items available to dequeue");
|
}
|
||||||
}
|
|
||||||
|
TItem result = this.heap[0];
|
||||||
TItem result = this.heap[0];
|
--this.count;
|
||||||
--this.count;
|
trickleDown(0, this.heap[this.count]);
|
||||||
trickleDown(0, this.heap[this.count]);
|
#if DEBUG
|
||||||
#if DEBUG
|
++this.version;
|
||||||
++this.version;
|
#endif
|
||||||
#endif
|
return result;
|
||||||
return result;
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Puts an item into the priority queue</summary>
|
||||||
/// <summary>Puts an item into the priority queue</summary>
|
/// <param name="item">Item to be queued</param>
|
||||||
/// <param name="item">Item to be queued</param>
|
public void Enqueue(TItem item) {
|
||||||
public void Enqueue(TItem item) {
|
if(this.count == capacity)
|
||||||
if(this.count == capacity)
|
growHeap();
|
||||||
growHeap();
|
|
||||||
|
++this.count;
|
||||||
++this.count;
|
bubbleUp(this.count - 1, item);
|
||||||
bubbleUp(this.count - 1, item);
|
#if DEBUG
|
||||||
#if DEBUG
|
++this.version;
|
||||||
++this.version;
|
#endif
|
||||||
#endif
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Removes all items from the priority queue</summary>
|
||||||
/// <summary>Removes all items from the priority queue</summary>
|
public void Clear() {
|
||||||
public void Clear() {
|
this.count = 0;
|
||||||
this.count = 0;
|
#if DEBUG
|
||||||
#if DEBUG
|
++this.version;
|
||||||
++this.version;
|
#endif
|
||||||
#endif
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
/// <summary>Total number of items in the priority queue</summary>
|
||||||
/// <summary>Total number of items in the priority queue</summary>
|
public int Count {
|
||||||
public int Count {
|
get { return this.count; }
|
||||||
get { return this.count; }
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Copies the contents of the priority queue into an array</summary>
|
||||||
/// <summary>Copies the contents of the priority queue into an array</summary>
|
/// <param name="array">Array to copy the priority queue into</param>
|
||||||
/// <param name="array">Array to copy the priority queue into</param>
|
/// <param name="index">Starting index for the destination array</param>
|
||||||
/// <param name="index">Starting index for the destination array</param>
|
public void CopyTo(Array array, int index) {
|
||||||
public void CopyTo(Array array, int index) {
|
Array.Copy(this.heap, 0, array, index, this.count);
|
||||||
Array.Copy(this.heap, 0, array, index, this.count);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Obtains an object that can be used to synchronize accesses to the priority queue
|
||||||
/// Obtains an object that can be used to synchronize accesses to the priority queue
|
/// from different threads
|
||||||
/// from different threads
|
/// </summary>
|
||||||
/// </summary>
|
public object SyncRoot {
|
||||||
public object SyncRoot {
|
get { return this; }
|
||||||
get { return this; }
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Whether operations performed on this priority queue are thread safe</summary>
|
||||||
/// <summary>Whether operations performed on this priority queue are thread safe</summary>
|
public bool IsSynchronized {
|
||||||
public bool IsSynchronized {
|
get { return false; }
|
||||||
get { return false; }
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Returns a typesafe enumerator for the priority queue</summary>
|
||||||
/// <summary>Returns a typesafe enumerator for the priority queue</summary>
|
/// <returns>A new enumerator for the priority queue</returns>
|
||||||
/// <returns>A new enumerator for the priority queue</returns>
|
public IEnumerator<TItem> GetEnumerator() {
|
||||||
public IEnumerator<TItem> GetEnumerator() {
|
return new Enumerator(this);
|
||||||
return new Enumerator(this);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Moves an item upwards in the heap tree</summary>
|
||||||
/// <summary>Moves an item upwards in the heap tree</summary>
|
/// <param name="index">Index of the item to be moved</param>
|
||||||
/// <param name="index">Index of the item to be moved</param>
|
/// <param name="item">Item to be moved</param>
|
||||||
/// <param name="item">Item to be moved</param>
|
private void bubbleUp(int index, TItem item) {
|
||||||
private void bubbleUp(int index, TItem item) {
|
int parent = getParent(index);
|
||||||
int parent = getParent(index);
|
|
||||||
|
// Note: (index > 0) means there is a parent
|
||||||
// Note: (index > 0) means there is a parent
|
while((index > 0) && (this.comparer.Compare(this.heap[parent], item) < 0)) {
|
||||||
while((index > 0) && (this.comparer.Compare(this.heap[parent], item) < 0)) {
|
this.heap[index] = this.heap[parent];
|
||||||
this.heap[index] = this.heap[parent];
|
index = parent;
|
||||||
index = parent;
|
parent = getParent(index);
|
||||||
parent = getParent(index);
|
}
|
||||||
}
|
|
||||||
|
this.heap[index] = item;
|
||||||
this.heap[index] = item;
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Move the item downwards in the heap tree</summary>
|
||||||
/// <summary>Move the item downwards in the heap tree</summary>
|
/// <param name="index">Index of the item to be moved</param>
|
||||||
/// <param name="index">Index of the item to be moved</param>
|
/// <param name="item">Item to be moved</param>
|
||||||
/// <param name="item">Item to be moved</param>
|
private void trickleDown(int index, TItem item) {
|
||||||
private void trickleDown(int index, TItem item) {
|
int child = getLeftChild(index);
|
||||||
int child = getLeftChild(index);
|
|
||||||
|
while(child < this.count) {
|
||||||
while(child < this.count) {
|
|
||||||
|
bool needsToBeMoved =
|
||||||
bool needsToBeMoved =
|
((child + 1) < this.count) &&
|
||||||
((child + 1) < this.count) &&
|
(this.comparer.Compare(heap[child], this.heap[child + 1]) < 0);
|
||||||
(this.comparer.Compare(heap[child], this.heap[child + 1]) < 0);
|
|
||||||
|
if(needsToBeMoved)
|
||||||
if(needsToBeMoved)
|
++child;
|
||||||
++child;
|
|
||||||
|
this.heap[index] = this.heap[child];
|
||||||
this.heap[index] = this.heap[child];
|
index = child;
|
||||||
index = child;
|
child = getLeftChild(index);
|
||||||
child = getLeftChild(index);
|
|
||||||
|
}
|
||||||
}
|
|
||||||
|
bubbleUp(index, item);
|
||||||
bubbleUp(index, item);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Obtains the left child item in the heap tree</summary>
|
||||||
/// <summary>Obtains the left child item in the heap tree</summary>
|
/// <param name="index">Index of the item whose left child to return</param>
|
||||||
/// <param name="index">Index of the item whose left child to return</param>
|
/// <returns>The left child item of the provided parent item</returns>
|
||||||
/// <returns>The left child item of the provided parent item</returns>
|
private int getLeftChild(int index) {
|
||||||
private int getLeftChild(int index) {
|
return (index * 2) + 1;
|
||||||
return (index * 2) + 1;
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Calculates the parent entry of the item on the heap</summary>
|
||||||
/// <summary>Calculates the parent entry of the item on the heap</summary>
|
/// <param name="index">Index of the item whose parent to calculate</param>
|
||||||
/// <param name="index">Index of the item whose parent to calculate</param>
|
/// <returns>The index of the parent to the specified item</returns>
|
||||||
/// <returns>The index of the parent to the specified item</returns>
|
private int getParent(int index) {
|
||||||
private int getParent(int index) {
|
return (index - 1) / 2;
|
||||||
return (index - 1) / 2;
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Increases the size of the priority collection's heap</summary>
|
||||||
/// <summary>Increases the size of the priority collection's heap</summary>
|
private void growHeap() {
|
||||||
private void growHeap() {
|
this.capacity = (capacity * 2) + 1;
|
||||||
this.capacity = (capacity * 2) + 1;
|
|
||||||
|
TItem[] newHeap = new TItem[this.capacity];
|
||||||
TItem[] newHeap = new TItem[this.capacity];
|
Array.Copy(this.heap, 0, newHeap, 0, this.count);
|
||||||
Array.Copy(this.heap, 0, newHeap, 0, this.count);
|
this.heap = newHeap;
|
||||||
this.heap = newHeap;
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Returns an enumerator for the priority queue</summary>
|
||||||
/// <summary>Returns an enumerator for the priority queue</summary>
|
/// <returns>A new enumerator for the priority queue</returns>
|
||||||
/// <returns>A new enumerator for the priority queue</returns>
|
IEnumerator IEnumerable.GetEnumerator() {
|
||||||
IEnumerator IEnumerable.GetEnumerator() {
|
return new Enumerator(this);
|
||||||
return new Enumerator(this);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Comparer used to order the items in the priority queue</summary>
|
||||||
/// <summary>Comparer used to order the items in the priority queue</summary>
|
private IComparer<TItem> comparer;
|
||||||
private IComparer<TItem> comparer;
|
/// <summary>Total number of items in the priority queue</summary>
|
||||||
/// <summary>Total number of items in the priority queue</summary>
|
private int count;
|
||||||
private int count;
|
/// <summary>Available space in the priority queue</summary>
|
||||||
/// <summary>Available space in the priority queue</summary>
|
private int capacity;
|
||||||
private int capacity;
|
/// <summary>Tree containing the items in the priority queue</summary>
|
||||||
/// <summary>Tree containing the items in the priority queue</summary>
|
private TItem[] heap;
|
||||||
private TItem[] heap;
|
#if DEBUG
|
||||||
#if DEBUG
|
/// <summary>Incremented whenever the priority queue is modified</summary>
|
||||||
/// <summary>Incremented whenever the priority queue is modified</summary>
|
private int version;
|
||||||
private int version;
|
#endif
|
||||||
#endif
|
|
||||||
|
}
|
||||||
}
|
|
||||||
|
} // namespace Nuclex.Support.Collections
|
||||||
} // namespace Nuclex.Support.Collections
|
|
||||||
|
|
|
@ -1,163 +1,162 @@
|
||||||
#region CPL License
|
#region Apache License 2.0
|
||||||
/*
|
/*
|
||||||
Nuclex Framework
|
Nuclex .NET Framework
|
||||||
Copyright (C) 2002-2017 Nuclex Development Labs
|
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
|
||||||
|
|
||||||
This library is free software; you can redistribute it and/or
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
modify it under the terms of the IBM Common Public License as
|
you may not use this file except in compliance with the License.
|
||||||
published by the IBM Corporation; either version 1.0 of the
|
You may obtain a copy of the License at
|
||||||
License, or (at your option) any later version.
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
This library is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
Unless required by applicable law or agreed to in writing, software
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
IBM Common Public License for more details.
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
You should have received a copy of the IBM Common Public
|
limitations under the License.
|
||||||
License along with this library
|
*/
|
||||||
*/
|
#endregion // Apache License 2.0
|
||||||
#endregion
|
|
||||||
|
using System;
|
||||||
using System;
|
using System.Collections;
|
||||||
using System.Collections;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Generic;
|
|
||||||
|
#if UNITTEST
|
||||||
#if UNITTEST
|
|
||||||
|
using NUnit.Framework;
|
||||||
using NUnit.Framework;
|
|
||||||
|
namespace Nuclex.Support.Collections {
|
||||||
namespace Nuclex.Support.Collections {
|
|
||||||
|
/// <summary>Unit Test for the read only collection wrapper</summary>
|
||||||
/// <summary>Unit Test for the read only collection wrapper</summary>
|
[TestFixture]
|
||||||
[TestFixture]
|
internal class ReadOnlyCollectionTest {
|
||||||
internal class ReadOnlyCollectionTest {
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Verifies that the copy constructor of the read only collection works
|
||||||
/// Verifies that the copy constructor of the read only collection works
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void TestCopyConstructor() {
|
||||||
public void TestCopyConstructor() {
|
int[] integers = new int[] { 12, 34, 56, 78 };
|
||||||
int[] integers = new int[] { 12, 34, 56, 78 };
|
ReadOnlyCollection<int> testCollection = new ReadOnlyCollection<int>(integers);
|
||||||
ReadOnlyCollection<int> testCollection = new ReadOnlyCollection<int>(integers);
|
|
||||||
|
CollectionAssert.AreEqual(integers, testCollection);
|
||||||
CollectionAssert.AreEqual(integers, testCollection);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Verifies that the IsReadOnly property returns true</summary>
|
||||||
/// <summary>Verifies that the IsReadOnly property returns true</summary>
|
[Test]
|
||||||
[Test]
|
public void TestIsReadOnly() {
|
||||||
public void TestIsReadOnly() {
|
ReadOnlyCollection<int> testCollection = new ReadOnlyCollection<int>(new int[0]);
|
||||||
ReadOnlyCollection<int> testCollection = new ReadOnlyCollection<int>(new int[0]);
|
|
||||||
|
Assert.IsTrue(testCollection.IsReadOnly);
|
||||||
Assert.IsTrue(testCollection.IsReadOnly);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Verifies that the CopyTo() of the read only collection works
|
||||||
/// Verifies that the CopyTo() of the read only collection works
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void TestCopyToArray() {
|
||||||
public void TestCopyToArray() {
|
int[] inputIntegers = new int[] { 12, 34, 56, 78 };
|
||||||
int[] inputIntegers = new int[] { 12, 34, 56, 78 };
|
ReadOnlyCollection<int> testCollection = new ReadOnlyCollection<int>(inputIntegers);
|
||||||
ReadOnlyCollection<int> testCollection = new ReadOnlyCollection<int>(inputIntegers);
|
|
||||||
|
int[] outputIntegers = new int[testCollection.Count];
|
||||||
int[] outputIntegers = new int[testCollection.Count];
|
testCollection.CopyTo(outputIntegers, 0);
|
||||||
testCollection.CopyTo(outputIntegers, 0);
|
|
||||||
|
CollectionAssert.AreEqual(inputIntegers, outputIntegers);
|
||||||
CollectionAssert.AreEqual(inputIntegers, outputIntegers);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Checks whether the Contains() method of the read only collection is able to
|
||||||
/// Checks whether the Contains() method of the read only collection is able to
|
/// determine if the collection contains an item
|
||||||
/// determine if the collection contains an item
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void TestContains() {
|
||||||
public void TestContains() {
|
int[] integers = new int[] { 1234, 6789 };
|
||||||
int[] integers = new int[] { 1234, 6789 };
|
ReadOnlyCollection<int> testCollection = new ReadOnlyCollection<int>(integers);
|
||||||
ReadOnlyCollection<int> testCollection = new ReadOnlyCollection<int>(integers);
|
|
||||||
|
Assert.IsTrue(testCollection.Contains(1234));
|
||||||
Assert.IsTrue(testCollection.Contains(1234));
|
Assert.IsFalse(testCollection.Contains(4321));
|
||||||
Assert.IsFalse(testCollection.Contains(4321));
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Ensures that the Add() method of the read only collection throws an exception
|
||||||
/// Ensures that the Add() method of the read only collection throws an exception
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void TestThrowOnAdd() {
|
||||||
public void TestThrowOnAdd() {
|
ReadOnlyCollection<int> testCollection = new ReadOnlyCollection<int>(new int[0]);
|
||||||
ReadOnlyCollection<int> testCollection = new ReadOnlyCollection<int>(new int[0]);
|
Assert.Throws<NotSupportedException>(
|
||||||
Assert.Throws<NotSupportedException>(
|
delegate() { (testCollection as ICollection<int>).Add(123); }
|
||||||
delegate() { (testCollection as ICollection<int>).Add(123); }
|
);
|
||||||
);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Ensures that the Remove() method of the read only collection throws an exception
|
||||||
/// Ensures that the Remove() method of the read only collection throws an exception
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void TestThrowOnRemove() {
|
||||||
public void TestThrowOnRemove() {
|
ReadOnlyCollection<int> testCollection = new ReadOnlyCollection<int>(new int[0]);
|
||||||
ReadOnlyCollection<int> testCollection = new ReadOnlyCollection<int>(new int[0]);
|
Assert.Throws<NotSupportedException>(
|
||||||
Assert.Throws<NotSupportedException>(
|
delegate() { (testCollection as ICollection<int>).Remove(123); }
|
||||||
delegate() { (testCollection as ICollection<int>).Remove(123); }
|
);
|
||||||
);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Ensures that the Clear() method of the read only collection throws an exception
|
||||||
/// Ensures that the Clear() method of the read only collection throws an exception
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void TestThrowOnClear() {
|
||||||
public void TestThrowOnClear() {
|
ReadOnlyCollection<int> testCollection = new ReadOnlyCollection<int>(new int[0]);
|
||||||
ReadOnlyCollection<int> testCollection = new ReadOnlyCollection<int>(new int[0]);
|
Assert.Throws<NotSupportedException>(
|
||||||
Assert.Throws<NotSupportedException>(
|
delegate() { (testCollection as ICollection<int>).Clear(); }
|
||||||
delegate() { (testCollection as ICollection<int>).Clear(); }
|
);
|
||||||
);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Tests whether the typesafe enumerator of the read only collection is working
|
||||||
/// Tests whether the typesafe enumerator of the read only collection is working
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void TestTypesafeEnumerator() {
|
||||||
public void TestTypesafeEnumerator() {
|
int[] inputIntegers = new int[] { 12, 34, 56, 78 };
|
||||||
int[] inputIntegers = new int[] { 12, 34, 56, 78 };
|
ReadOnlyCollection<int> testCollection = new ReadOnlyCollection<int>(inputIntegers);
|
||||||
ReadOnlyCollection<int> testCollection = new ReadOnlyCollection<int>(inputIntegers);
|
|
||||||
|
List<int> outputIntegers = new List<int>();
|
||||||
List<int> outputIntegers = new List<int>();
|
foreach(int value in testCollection) {
|
||||||
foreach(int value in testCollection) {
|
outputIntegers.Add(value);
|
||||||
outputIntegers.Add(value);
|
}
|
||||||
}
|
|
||||||
|
CollectionAssert.AreEqual(inputIntegers, outputIntegers);
|
||||||
CollectionAssert.AreEqual(inputIntegers, outputIntegers);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Verifies that the CopyTo() of the read only collection works if invoked via
|
||||||
/// Verifies that the CopyTo() of the read only collection works if invoked via
|
/// the ICollection interface
|
||||||
/// the ICollection interface
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void TestCopyToArrayViaICollection() {
|
||||||
public void TestCopyToArrayViaICollection() {
|
int[] inputIntegers = new int[] { 12, 34, 56, 78 };
|
||||||
int[] inputIntegers = new int[] { 12, 34, 56, 78 };
|
ReadOnlyCollection<int> testCollection = new ReadOnlyCollection<int>(inputIntegers);
|
||||||
ReadOnlyCollection<int> testCollection = new ReadOnlyCollection<int>(inputIntegers);
|
|
||||||
|
int[] outputIntegers = new int[testCollection.Count];
|
||||||
int[] outputIntegers = new int[testCollection.Count];
|
(testCollection as ICollection).CopyTo(outputIntegers, 0);
|
||||||
(testCollection as ICollection).CopyTo(outputIntegers, 0);
|
|
||||||
|
CollectionAssert.AreEqual(inputIntegers, outputIntegers);
|
||||||
CollectionAssert.AreEqual(inputIntegers, outputIntegers);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Verifies that the IsSynchronized property and the SyncRoot property are working
|
||||||
/// Verifies that the IsSynchronized property and the SyncRoot property are working
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void TestSynchronization() {
|
||||||
public void TestSynchronization() {
|
ReadOnlyCollection<int> testCollection = new ReadOnlyCollection<int>(new int[0]);
|
||||||
ReadOnlyCollection<int> testCollection = new ReadOnlyCollection<int>(new int[0]);
|
|
||||||
|
if(!(testCollection as ICollection).IsSynchronized) {
|
||||||
if(!(testCollection as ICollection).IsSynchronized) {
|
lock((testCollection as ICollection).SyncRoot) {
|
||||||
lock((testCollection as ICollection).SyncRoot) {
|
Assert.AreEqual(0, testCollection.Count);
|
||||||
Assert.AreEqual(0, testCollection.Count);
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
}
|
||||||
}
|
|
||||||
|
} // namespace Nuclex.Support.Collections
|
||||||
} // namespace Nuclex.Support.Collections
|
|
||||||
|
#endif // UNITTEST
|
||||||
#endif // UNITTEST
|
|
||||||
|
|
|
@ -1,140 +1,139 @@
|
||||||
#region CPL License
|
#region Apache License 2.0
|
||||||
/*
|
/*
|
||||||
Nuclex Framework
|
Nuclex .NET Framework
|
||||||
Copyright (C) 2002-2017 Nuclex Development Labs
|
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
|
||||||
|
|
||||||
This library is free software; you can redistribute it and/or
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
modify it under the terms of the IBM Common Public License as
|
you may not use this file except in compliance with the License.
|
||||||
published by the IBM Corporation; either version 1.0 of the
|
You may obtain a copy of the License at
|
||||||
License, or (at your option) any later version.
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
This library is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
Unless required by applicable law or agreed to in writing, software
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
IBM Common Public License for more details.
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
You should have received a copy of the IBM Common Public
|
limitations under the License.
|
||||||
License along with this library
|
*/
|
||||||
*/
|
#endregion // Apache License 2.0
|
||||||
#endregion
|
|
||||||
|
using System;
|
||||||
using System;
|
using System.Collections;
|
||||||
using System.Collections;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Generic;
|
|
||||||
|
namespace Nuclex.Support.Collections {
|
||||||
namespace Nuclex.Support.Collections {
|
|
||||||
|
/// <summary>Wraps a Collection and prevents users from modifying it</summary>
|
||||||
/// <summary>Wraps a Collection and prevents users from modifying it</summary>
|
/// <typeparam name="TItem">Type of items to manage in the Collection</typeparam>
|
||||||
/// <typeparam name="TItem">Type of items to manage in the Collection</typeparam>
|
public class ReadOnlyCollection<TItem> :
|
||||||
public class ReadOnlyCollection<TItem> :
|
ICollection<TItem>,
|
||||||
ICollection<TItem>,
|
ICollection {
|
||||||
ICollection {
|
|
||||||
|
/// <summary>Initializes a new read-only Collection wrapper</summary>
|
||||||
/// <summary>Initializes a new read-only Collection wrapper</summary>
|
/// <param name="collection">Collection that will be wrapped</param>
|
||||||
/// <param name="collection">Collection that will be wrapped</param>
|
public ReadOnlyCollection(ICollection<TItem> collection) {
|
||||||
public ReadOnlyCollection(ICollection<TItem> collection) {
|
this.typedCollection = collection;
|
||||||
this.typedCollection = collection;
|
this.objectCollection = (collection as ICollection);
|
||||||
this.objectCollection = (collection as ICollection);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Determines whether the List contains the specified item</summary>
|
||||||
/// <summary>Determines whether the List contains the specified item</summary>
|
/// <param name="item">Item that will be checked for</param>
|
||||||
/// <param name="item">Item that will be checked for</param>
|
/// <returns>True if the specified item is contained in the List</returns>
|
||||||
/// <returns>True if the specified item is contained in the List</returns>
|
public bool Contains(TItem item) {
|
||||||
public bool Contains(TItem item) {
|
return this.typedCollection.Contains(item);
|
||||||
return this.typedCollection.Contains(item);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Copies the contents of the List into an array</summary>
|
||||||
/// <summary>Copies the contents of the List into an array</summary>
|
/// <param name="array">Array the List will be copied into</param>
|
||||||
/// <param name="array">Array the List will be copied into</param>
|
/// <param name="arrayIndex">
|
||||||
/// <param name="arrayIndex">
|
/// Starting index at which to begin filling the destination array
|
||||||
/// Starting index at which to begin filling the destination array
|
/// </param>
|
||||||
/// </param>
|
public void CopyTo(TItem[] array, int arrayIndex) {
|
||||||
public void CopyTo(TItem[] array, int arrayIndex) {
|
this.typedCollection.CopyTo(array, arrayIndex);
|
||||||
this.typedCollection.CopyTo(array, arrayIndex);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>The number of items current contained in the List</summary>
|
||||||
/// <summary>The number of items current contained in the List</summary>
|
public int Count {
|
||||||
public int Count {
|
get { return this.typedCollection.Count; }
|
||||||
get { return this.typedCollection.Count; }
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Whether the List is write-protected</summary>
|
||||||
/// <summary>Whether the List is write-protected</summary>
|
public bool IsReadOnly {
|
||||||
public bool IsReadOnly {
|
get { return true; }
|
||||||
get { return true; }
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Returns a new enumerator over the contents of the List</summary>
|
||||||
/// <summary>Returns a new enumerator over the contents of the List</summary>
|
/// <returns>The new List contents enumerator</returns>
|
||||||
/// <returns>The new List contents enumerator</returns>
|
public IEnumerator<TItem> GetEnumerator() {
|
||||||
public IEnumerator<TItem> GetEnumerator() {
|
return this.typedCollection.GetEnumerator();
|
||||||
return this.typedCollection.GetEnumerator();
|
}
|
||||||
}
|
|
||||||
|
#region ICollection<> implementation
|
||||||
#region ICollection<> implementation
|
|
||||||
|
/// <summary>Adds an item to the end of the List</summary>
|
||||||
/// <summary>Adds an item to the end of the List</summary>
|
/// <param name="item">Item that will be added to the List</param>
|
||||||
/// <param name="item">Item that will be added to the List</param>
|
void ICollection<TItem>.Add(TItem item) {
|
||||||
void ICollection<TItem>.Add(TItem item) {
|
throw new NotSupportedException(
|
||||||
throw new NotSupportedException(
|
"Adding items is not supported by the read-only List"
|
||||||
"Adding items is not supported by the read-only List"
|
);
|
||||||
);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Removes all items from the List</summary>
|
||||||
/// <summary>Removes all items from the List</summary>
|
void ICollection<TItem>.Clear() {
|
||||||
void ICollection<TItem>.Clear() {
|
throw new NotSupportedException(
|
||||||
throw new NotSupportedException(
|
"Clearing is not supported by the read-only List"
|
||||||
"Clearing is not supported by the read-only List"
|
);
|
||||||
);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Removes the specified item from the List</summary>
|
||||||
/// <summary>Removes the specified item from the List</summary>
|
/// <param name="item">Item that will be removed from the List</param>
|
||||||
/// <param name="item">Item that will be removed from the List</param>
|
/// <returns>True of the specified item was found in the List and removed</returns>
|
||||||
/// <returns>True of the specified item was found in the List and removed</returns>
|
bool ICollection<TItem>.Remove(TItem item) {
|
||||||
bool ICollection<TItem>.Remove(TItem item) {
|
throw new NotSupportedException(
|
||||||
throw new NotSupportedException(
|
"Removing items is not supported by the read-only List"
|
||||||
"Removing items is not supported by the read-only List"
|
);
|
||||||
);
|
}
|
||||||
}
|
|
||||||
|
#endregion
|
||||||
#endregion
|
|
||||||
|
#region IEnumerable implementation
|
||||||
#region IEnumerable implementation
|
|
||||||
|
/// <summary>Returns a new enumerator over the contents of the List</summary>
|
||||||
/// <summary>Returns a new enumerator over the contents of the List</summary>
|
/// <returns>The new List contents enumerator</returns>
|
||||||
/// <returns>The new List contents enumerator</returns>
|
IEnumerator IEnumerable.GetEnumerator() {
|
||||||
IEnumerator IEnumerable.GetEnumerator() {
|
return this.objectCollection.GetEnumerator();
|
||||||
return this.objectCollection.GetEnumerator();
|
}
|
||||||
}
|
|
||||||
|
#endregion
|
||||||
#endregion
|
|
||||||
|
#region ICollection implementation
|
||||||
#region ICollection implementation
|
|
||||||
|
/// <summary>Copies the contents of the List into an array</summary>
|
||||||
/// <summary>Copies the contents of the List into an array</summary>
|
/// <param name="array">Array the List will be copied into</param>
|
||||||
/// <param name="array">Array the List will be copied into</param>
|
/// <param name="index">
|
||||||
/// <param name="index">
|
/// Starting index at which to begin filling the destination array
|
||||||
/// Starting index at which to begin filling the destination array
|
/// </param>
|
||||||
/// </param>
|
void ICollection.CopyTo(Array array, int index) {
|
||||||
void ICollection.CopyTo(Array array, int index) {
|
this.objectCollection.CopyTo(array, index);
|
||||||
this.objectCollection.CopyTo(array, index);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Whether the List is synchronized for multi-threaded usage</summary>
|
||||||
/// <summary>Whether the List is synchronized for multi-threaded usage</summary>
|
bool ICollection.IsSynchronized {
|
||||||
bool ICollection.IsSynchronized {
|
get { return this.objectCollection.IsSynchronized; }
|
||||||
get { return this.objectCollection.IsSynchronized; }
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Synchronization root on which the List locks</summary>
|
||||||
/// <summary>Synchronization root on which the List locks</summary>
|
object ICollection.SyncRoot {
|
||||||
object ICollection.SyncRoot {
|
get { return this.objectCollection.SyncRoot; }
|
||||||
get { return this.objectCollection.SyncRoot; }
|
}
|
||||||
}
|
|
||||||
|
#endregion
|
||||||
#endregion
|
|
||||||
|
/// <summary>The wrapped Collection under its type-safe interface</summary>
|
||||||
/// <summary>The wrapped Collection under its type-safe interface</summary>
|
private ICollection<TItem> typedCollection;
|
||||||
private ICollection<TItem> typedCollection;
|
/// <summary>The wrapped Collection under its object interface</summary>
|
||||||
/// <summary>The wrapped Collection under its object interface</summary>
|
private ICollection objectCollection;
|
||||||
private ICollection objectCollection;
|
|
||||||
|
}
|
||||||
}
|
|
||||||
|
} // namespace Nuclex.Support.Collections
|
||||||
} // namespace Nuclex.Support.Collections
|
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,407 +1,406 @@
|
||||||
#region CPL License
|
#region Apache License 2.0
|
||||||
/*
|
/*
|
||||||
Nuclex Framework
|
Nuclex .NET Framework
|
||||||
Copyright (C) 2002-2017 Nuclex Development Labs
|
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
|
||||||
|
|
||||||
This library is free software; you can redistribute it and/or
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
modify it under the terms of the IBM Common Public License as
|
you may not use this file except in compliance with the License.
|
||||||
published by the IBM Corporation; either version 1.0 of the
|
You may obtain a copy of the License at
|
||||||
License, or (at your option) any later version.
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
This library is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
Unless required by applicable law or agreed to in writing, software
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
IBM Common Public License for more details.
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
You should have received a copy of the IBM Common Public
|
limitations under the License.
|
||||||
License along with this library
|
*/
|
||||||
*/
|
#endregion // Apache License 2.0
|
||||||
#endregion
|
|
||||||
|
using System;
|
||||||
using System;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Generic;
|
using System.Collections;
|
||||||
using System.Collections;
|
using System.Runtime.Serialization;
|
||||||
using System.Runtime.Serialization;
|
|
||||||
|
namespace Nuclex.Support.Collections {
|
||||||
namespace Nuclex.Support.Collections {
|
|
||||||
|
/// <summary>Wraps a dictionary and prevents users from modifying it</summary>
|
||||||
/// <summary>Wraps a dictionary and prevents users from modifying it</summary>
|
/// <typeparam name="KeyType">Type of the keys used in the dictionary</typeparam>
|
||||||
/// <typeparam name="KeyType">Type of the keys used in the dictionary</typeparam>
|
/// <typeparam name="ValueType">Type of the values used in the dictionary</typeparam>
|
||||||
/// <typeparam name="ValueType">Type of the values used in the dictionary</typeparam>
|
#if !NO_SERIALIZATION
|
||||||
#if !NO_SERIALIZATION
|
[Serializable]
|
||||||
[Serializable]
|
#endif
|
||||||
#endif
|
public class ReadOnlyDictionary<KeyType, ValueType> :
|
||||||
public class ReadOnlyDictionary<KeyType, ValueType> :
|
#if !NO_SERIALIZATION
|
||||||
#if !NO_SERIALIZATION
|
ISerializable,
|
||||||
ISerializable,
|
IDeserializationCallback,
|
||||||
IDeserializationCallback,
|
#endif
|
||||||
#endif
|
IDictionary,
|
||||||
IDictionary,
|
IDictionary<KeyType, ValueType> {
|
||||||
IDictionary<KeyType, ValueType> {
|
|
||||||
|
#if !NO_SERIALIZATION
|
||||||
#if !NO_SERIALIZATION
|
|
||||||
|
#region class SerializedDictionary
|
||||||
#region class SerializedDictionary
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Dictionary wrapped used to reconstruct a serialized read only dictionary
|
||||||
/// Dictionary wrapped used to reconstruct a serialized read only dictionary
|
/// </summary>
|
||||||
/// </summary>
|
private class SerializedDictionary : Dictionary<KeyType, ValueType> {
|
||||||
private class SerializedDictionary : Dictionary<KeyType, ValueType> {
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Initializes a new instance of the System.WeakReference class, using deserialized
|
||||||
/// Initializes a new instance of the System.WeakReference class, using deserialized
|
/// data from the specified serialization and stream objects.
|
||||||
/// data from the specified serialization and stream objects.
|
/// </summary>
|
||||||
/// </summary>
|
/// <param name="info">
|
||||||
/// <param name="info">
|
/// An object that holds all the data needed to serialize or deserialize the
|
||||||
/// An object that holds all the data needed to serialize or deserialize the
|
/// current System.WeakReference object.
|
||||||
/// current System.WeakReference object.
|
/// </param>
|
||||||
/// </param>
|
/// <param name="context">
|
||||||
/// <param name="context">
|
/// (Reserved) Describes the source and destination of the serialized stream
|
||||||
/// (Reserved) Describes the source and destination of the serialized stream
|
/// specified by info.
|
||||||
/// specified by info.
|
/// </param>
|
||||||
/// </param>
|
/// <exception cref="System.ArgumentNullException">
|
||||||
/// <exception cref="System.ArgumentNullException">
|
/// The info parameter is null.
|
||||||
/// The info parameter is null.
|
/// </exception>
|
||||||
/// </exception>
|
public SerializedDictionary(SerializationInfo info, StreamingContext context) :
|
||||||
public SerializedDictionary(SerializationInfo info, StreamingContext context) :
|
base(info, context) { }
|
||||||
base(info, context) { }
|
|
||||||
|
}
|
||||||
}
|
|
||||||
|
#endregion // class SerializedDictionary
|
||||||
#endregion // class SerializedDictionary
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Initializes a new instance of the System.WeakReference class, using deserialized
|
||||||
/// Initializes a new instance of the System.WeakReference class, using deserialized
|
/// data from the specified serialization and stream objects.
|
||||||
/// data from the specified serialization and stream objects.
|
/// </summary>
|
||||||
/// </summary>
|
/// <param name="info">
|
||||||
/// <param name="info">
|
/// An object that holds all the data needed to serialize or deserialize the
|
||||||
/// An object that holds all the data needed to serialize or deserialize the
|
/// current System.WeakReference object.
|
||||||
/// current System.WeakReference object.
|
/// </param>
|
||||||
/// </param>
|
/// <param name="context">
|
||||||
/// <param name="context">
|
/// (Reserved) Describes the source and destination of the serialized stream
|
||||||
/// (Reserved) Describes the source and destination of the serialized stream
|
/// specified by info.
|
||||||
/// specified by info.
|
/// </param>
|
||||||
/// </param>
|
/// <exception cref="System.ArgumentNullException">
|
||||||
/// <exception cref="System.ArgumentNullException">
|
/// The info parameter is null.
|
||||||
/// The info parameter is null.
|
/// </exception>
|
||||||
/// </exception>
|
protected ReadOnlyDictionary(SerializationInfo info, StreamingContext context) :
|
||||||
protected ReadOnlyDictionary(SerializationInfo info, StreamingContext context) :
|
this(new SerializedDictionary(info, context)) { }
|
||||||
this(new SerializedDictionary(info, context)) { }
|
|
||||||
|
#endif // !NO_SERIALIZATION
|
||||||
#endif // !NO_SERIALIZATION
|
|
||||||
|
/// <summary>Initializes a new read-only dictionary wrapper</summary>
|
||||||
/// <summary>Initializes a new read-only dictionary wrapper</summary>
|
/// <param name="dictionary">Dictionary that will be wrapped</param>
|
||||||
/// <param name="dictionary">Dictionary that will be wrapped</param>
|
public ReadOnlyDictionary(IDictionary<KeyType, ValueType> dictionary) {
|
||||||
public ReadOnlyDictionary(IDictionary<KeyType, ValueType> dictionary) {
|
this.typedDictionary = dictionary;
|
||||||
this.typedDictionary = dictionary;
|
this.objectDictionary = (this.typedDictionary as IDictionary);
|
||||||
this.objectDictionary = (this.typedDictionary as IDictionary);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Whether the directory is write-protected</summary>
|
||||||
/// <summary>Whether the directory is write-protected</summary>
|
public bool IsReadOnly {
|
||||||
public bool IsReadOnly {
|
get { return true; }
|
||||||
get { return true; }
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Determines whether the specified KeyValuePair is contained in the Dictionary
|
||||||
/// Determines whether the specified KeyValuePair is contained in the Dictionary
|
/// </summary>
|
||||||
/// </summary>
|
/// <param name="item">KeyValuePair that will be checked for</param>
|
||||||
/// <param name="item">KeyValuePair that will be checked for</param>
|
/// <returns>True if the provided KeyValuePair was contained in the Dictionary</returns>
|
||||||
/// <returns>True if the provided KeyValuePair was contained in the Dictionary</returns>
|
public bool Contains(KeyValuePair<KeyType, ValueType> item) {
|
||||||
public bool Contains(KeyValuePair<KeyType, ValueType> item) {
|
return this.typedDictionary.Contains(item);
|
||||||
return this.typedDictionary.Contains(item);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Determines whether the Dictionary contains the specified key</summary>
|
||||||
/// <summary>Determines whether the Dictionary contains the specified key</summary>
|
/// <param name="key">Key that will be checked for</param>
|
||||||
/// <param name="key">Key that will be checked for</param>
|
/// <returns>
|
||||||
/// <returns>
|
/// True if an entry with the specified key was contained in the Dictionary
|
||||||
/// True if an entry with the specified key was contained in the Dictionary
|
/// </returns>
|
||||||
/// </returns>
|
public bool ContainsKey(KeyType key) {
|
||||||
public bool ContainsKey(KeyType key) {
|
return this.typedDictionary.ContainsKey(key);
|
||||||
return this.typedDictionary.ContainsKey(key);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Copies the contents of the Dictionary into an array</summary>
|
||||||
/// <summary>Copies the contents of the Dictionary into an array</summary>
|
/// <param name="array">Array the Dictionary will be copied into</param>
|
||||||
/// <param name="array">Array the Dictionary will be copied into</param>
|
/// <param name="arrayIndex">
|
||||||
/// <param name="arrayIndex">
|
/// Starting index at which to begin filling the destination array
|
||||||
/// Starting index at which to begin filling the destination array
|
/// </param>
|
||||||
/// </param>
|
public void CopyTo(KeyValuePair<KeyType, ValueType>[] array, int arrayIndex) {
|
||||||
public void CopyTo(KeyValuePair<KeyType, ValueType>[] array, int arrayIndex) {
|
this.typedDictionary.CopyTo(array, arrayIndex);
|
||||||
this.typedDictionary.CopyTo(array, arrayIndex);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Number of elements contained in the Dictionary</summary>
|
||||||
/// <summary>Number of elements contained in the Dictionary</summary>
|
public int Count {
|
||||||
public int Count {
|
get { return this.typedDictionary.Count; }
|
||||||
get { return this.typedDictionary.Count; }
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Creates a new enumerator for the Dictionary</summary>
|
||||||
/// <summary>Creates a new enumerator for the Dictionary</summary>
|
/// <returns>The new Dictionary enumerator</returns>
|
||||||
/// <returns>The new Dictionary enumerator</returns>
|
public IEnumerator<KeyValuePair<KeyType, ValueType>> GetEnumerator() {
|
||||||
public IEnumerator<KeyValuePair<KeyType, ValueType>> GetEnumerator() {
|
return this.typedDictionary.GetEnumerator();
|
||||||
return this.typedDictionary.GetEnumerator();
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Collection of all keys contained in the Dictionary</summary>
|
||||||
/// <summary>Collection of all keys contained in the Dictionary</summary>
|
public ICollection<KeyType> Keys {
|
||||||
public ICollection<KeyType> Keys {
|
get {
|
||||||
get {
|
if(this.readonlyKeyCollection == null) {
|
||||||
if(this.readonlyKeyCollection == null) {
|
this.readonlyKeyCollection = new ReadOnlyCollection<KeyType>(
|
||||||
this.readonlyKeyCollection = new ReadOnlyCollection<KeyType>(
|
this.typedDictionary.Keys
|
||||||
this.typedDictionary.Keys
|
);
|
||||||
);
|
}
|
||||||
}
|
|
||||||
|
return this.readonlyKeyCollection;
|
||||||
return this.readonlyKeyCollection;
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Collection of all values contained in the Dictionary</summary>
|
||||||
/// <summary>Collection of all values contained in the Dictionary</summary>
|
public ICollection<ValueType> Values {
|
||||||
public ICollection<ValueType> Values {
|
get {
|
||||||
get {
|
if(this.readonlyValueCollection == null) {
|
||||||
if(this.readonlyValueCollection == null) {
|
this.readonlyValueCollection = new ReadOnlyCollection<ValueType>(
|
||||||
this.readonlyValueCollection = new ReadOnlyCollection<ValueType>(
|
this.typedDictionary.Values
|
||||||
this.typedDictionary.Values
|
);
|
||||||
);
|
}
|
||||||
}
|
|
||||||
|
return this.readonlyValueCollection;
|
||||||
return this.readonlyValueCollection;
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Attempts to retrieve the item with the specified key from the Dictionary
|
||||||
/// Attempts to retrieve the item with the specified key from the Dictionary
|
/// </summary>
|
||||||
/// </summary>
|
/// <param name="key">Key of the item to attempt to retrieve</param>
|
||||||
/// <param name="key">Key of the item to attempt to retrieve</param>
|
/// <param name="value">
|
||||||
/// <param name="value">
|
/// Output parameter that will receive the key upon successful completion
|
||||||
/// Output parameter that will receive the key upon successful completion
|
/// </param>
|
||||||
/// </param>
|
/// <returns>
|
||||||
/// <returns>
|
/// True if the item was found and has been placed in the output parameter
|
||||||
/// True if the item was found and has been placed in the output parameter
|
/// </returns>
|
||||||
/// </returns>
|
public bool TryGetValue(KeyType key, out ValueType value) {
|
||||||
public bool TryGetValue(KeyType key, out ValueType value) {
|
return this.typedDictionary.TryGetValue(key, out value);
|
||||||
return this.typedDictionary.TryGetValue(key, out value);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Accesses an item in the Dictionary by its key</summary>
|
||||||
/// <summary>Accesses an item in the Dictionary by its key</summary>
|
/// <param name="key">Key of the item that will be accessed</param>
|
||||||
/// <param name="key">Key of the item that will be accessed</param>
|
public ValueType this[KeyType key] {
|
||||||
public ValueType this[KeyType key] {
|
get { return this.typedDictionary[key]; }
|
||||||
get { return this.typedDictionary[key]; }
|
}
|
||||||
}
|
|
||||||
|
#region IDictionary<,> implementation
|
||||||
#region IDictionary<,> implementation
|
|
||||||
|
/// <summary>Inserts an item into the Dictionary</summary>
|
||||||
/// <summary>Inserts an item into the Dictionary</summary>
|
/// <param name="key">Key under which to add the new item</param>
|
||||||
/// <param name="key">Key under which to add the new item</param>
|
/// <param name="value">Item that will be added to the Dictionary</param>
|
||||||
/// <param name="value">Item that will be added to the Dictionary</param>
|
void IDictionary<KeyType, ValueType>.Add(KeyType key, ValueType value) {
|
||||||
void IDictionary<KeyType, ValueType>.Add(KeyType key, ValueType value) {
|
throw new NotSupportedException(
|
||||||
throw new NotSupportedException(
|
"Adding items is not supported by the read-only Dictionary"
|
||||||
"Adding items is not supported by the read-only Dictionary"
|
);
|
||||||
);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Removes the item with the specified key from the Dictionary</summary>
|
||||||
/// <summary>Removes the item with the specified key from the Dictionary</summary>
|
/// <param name="key">Key of the elementes that will be removed</param>
|
||||||
/// <param name="key">Key of the elementes that will be removed</param>
|
/// <returns>True if an item with the specified key was found and removed</returns>
|
||||||
/// <returns>True if an item with the specified key was found and removed</returns>
|
bool IDictionary<KeyType, ValueType>.Remove(KeyType key) {
|
||||||
bool IDictionary<KeyType, ValueType>.Remove(KeyType key) {
|
throw new NotSupportedException(
|
||||||
throw new NotSupportedException(
|
"Removing items is not supported by the read-only Dictionary"
|
||||||
"Removing items is not supported by the read-only Dictionary"
|
);
|
||||||
);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Accesses an item in the Dictionary by its key</summary>
|
||||||
/// <summary>Accesses an item in the Dictionary by its key</summary>
|
/// <param name="key">Key of the item that will be accessed</param>
|
||||||
/// <param name="key">Key of the item that will be accessed</param>
|
ValueType IDictionary<KeyType, ValueType>.this[KeyType key] {
|
||||||
ValueType IDictionary<KeyType, ValueType>.this[KeyType key] {
|
get { return this.typedDictionary[key]; }
|
||||||
get { return this.typedDictionary[key]; }
|
set {
|
||||||
set {
|
throw new NotSupportedException(
|
||||||
throw new NotSupportedException(
|
"Assigning items is not supported in a read-only Dictionary"
|
||||||
"Assigning items is not supported in a read-only Dictionary"
|
);
|
||||||
);
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
#endregion
|
||||||
#endregion
|
|
||||||
|
#region IEnumerable implementation
|
||||||
#region IEnumerable implementation
|
|
||||||
|
/// <summary>Returns a new object enumerator for the Dictionary</summary>
|
||||||
/// <summary>Returns a new object enumerator for the Dictionary</summary>
|
/// <returns>The new object enumerator</returns>
|
||||||
/// <returns>The new object enumerator</returns>
|
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() {
|
||||||
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() {
|
return (this.typedDictionary as IEnumerable).GetEnumerator();
|
||||||
return (this.typedDictionary as IEnumerable).GetEnumerator();
|
}
|
||||||
}
|
|
||||||
|
#endregion
|
||||||
#endregion
|
|
||||||
|
#region IDictionary implementation
|
||||||
#region IDictionary implementation
|
|
||||||
|
/// <summary>Removes all items from the Dictionary</summary>
|
||||||
/// <summary>Removes all items from the Dictionary</summary>
|
void IDictionary.Clear() {
|
||||||
void IDictionary.Clear() {
|
throw new NotSupportedException(
|
||||||
throw new NotSupportedException(
|
"Clearing is not supported in a read-only Dictionary"
|
||||||
"Clearing is not supported in a read-only Dictionary"
|
);
|
||||||
);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Adds an item into the Dictionary</summary>
|
||||||
/// <summary>Adds an item into the Dictionary</summary>
|
/// <param name="key">Key under which the item will be added</param>
|
||||||
/// <param name="key">Key under which the item will be added</param>
|
/// <param name="value">Item that will be added</param>
|
||||||
/// <param name="value">Item that will be added</param>
|
void IDictionary.Add(object key, object value) {
|
||||||
void IDictionary.Add(object key, object value) {
|
throw new NotSupportedException(
|
||||||
throw new NotSupportedException(
|
"Adding items is not supported in a read-only Dictionary"
|
||||||
"Adding items is not supported in a read-only Dictionary"
|
);
|
||||||
);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Determines whether the specified key exists in the Dictionary</summary>
|
||||||
/// <summary>Determines whether the specified key exists in the Dictionary</summary>
|
/// <param name="key">Key that will be checked for</param>
|
||||||
/// <param name="key">Key that will be checked for</param>
|
/// <returns>True if an item with the specified key exists in the Dictionary</returns>
|
||||||
/// <returns>True if an item with the specified key exists in the Dictionary</returns>
|
bool IDictionary.Contains(object key) {
|
||||||
bool IDictionary.Contains(object key) {
|
return this.objectDictionary.Contains(key);
|
||||||
return this.objectDictionary.Contains(key);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Whether the size of the Dictionary is fixed</summary>
|
||||||
/// <summary>Whether the size of the Dictionary is fixed</summary>
|
bool IDictionary.IsFixedSize {
|
||||||
bool IDictionary.IsFixedSize {
|
get { return this.objectDictionary.IsFixedSize; }
|
||||||
get { return this.objectDictionary.IsFixedSize; }
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Returns a collection of all keys in the Dictionary</summary>
|
||||||
/// <summary>Returns a collection of all keys in the Dictionary</summary>
|
ICollection IDictionary.Keys {
|
||||||
ICollection IDictionary.Keys {
|
get {
|
||||||
get {
|
if(this.readonlyKeyCollection == null) {
|
||||||
if(this.readonlyKeyCollection == null) {
|
this.readonlyKeyCollection = new ReadOnlyCollection<KeyType>(
|
||||||
this.readonlyKeyCollection = new ReadOnlyCollection<KeyType>(
|
this.typedDictionary.Keys
|
||||||
this.typedDictionary.Keys
|
);
|
||||||
);
|
}
|
||||||
}
|
|
||||||
|
return this.readonlyKeyCollection;
|
||||||
return this.readonlyKeyCollection;
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Returns a collection of all values stored in the Dictionary</summary>
|
||||||
/// <summary>Returns a collection of all values stored in the Dictionary</summary>
|
ICollection IDictionary.Values {
|
||||||
ICollection IDictionary.Values {
|
get {
|
||||||
get {
|
if(this.readonlyValueCollection == null) {
|
||||||
if(this.readonlyValueCollection == null) {
|
this.readonlyValueCollection = new ReadOnlyCollection<ValueType>(
|
||||||
this.readonlyValueCollection = new ReadOnlyCollection<ValueType>(
|
this.typedDictionary.Values
|
||||||
this.typedDictionary.Values
|
);
|
||||||
);
|
}
|
||||||
}
|
|
||||||
|
return this.readonlyValueCollection;
|
||||||
return this.readonlyValueCollection;
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Removes an item from the Dictionary</summary>
|
||||||
/// <summary>Removes an item from the Dictionary</summary>
|
/// <param name="key">Key of the item that will be removed</param>
|
||||||
/// <param name="key">Key of the item that will be removed</param>
|
void IDictionary.Remove(object key) {
|
||||||
void IDictionary.Remove(object key) {
|
throw new NotSupportedException(
|
||||||
throw new NotSupportedException(
|
"Removing is not supported by the read-only Dictionary"
|
||||||
"Removing is not supported by the read-only Dictionary"
|
);
|
||||||
);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Accesses an item in the Dictionary by its key</summary>
|
||||||
/// <summary>Accesses an item in the Dictionary by its key</summary>
|
/// <param name="key">Key of the item that will be accessed</param>
|
||||||
/// <param name="key">Key of the item that will be accessed</param>
|
/// <returns>The item with the specified key</returns>
|
||||||
/// <returns>The item with the specified key</returns>
|
object IDictionary.this[object key] {
|
||||||
object IDictionary.this[object key] {
|
get { return this.objectDictionary[key]; }
|
||||||
get { return this.objectDictionary[key]; }
|
set {
|
||||||
set {
|
throw new NotSupportedException(
|
||||||
throw new NotSupportedException(
|
"Assigning items is not supported by the read-only Dictionary"
|
||||||
"Assigning items is not supported by the read-only Dictionary"
|
);
|
||||||
);
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
#endregion // IDictionary implementation
|
||||||
#endregion // IDictionary implementation
|
|
||||||
|
#region IDictionaryEnumerator implementation
|
||||||
#region IDictionaryEnumerator implementation
|
|
||||||
|
/// <summary>Returns a new entry enumerator for the dictionary</summary>
|
||||||
/// <summary>Returns a new entry enumerator for the dictionary</summary>
|
/// <returns>The new entry enumerator</returns>
|
||||||
/// <returns>The new entry enumerator</returns>
|
IDictionaryEnumerator IDictionary.GetEnumerator() {
|
||||||
IDictionaryEnumerator IDictionary.GetEnumerator() {
|
return this.objectDictionary.GetEnumerator();
|
||||||
return this.objectDictionary.GetEnumerator();
|
}
|
||||||
}
|
|
||||||
|
#endregion // IDictionaryEnumerator implementation
|
||||||
#endregion // IDictionaryEnumerator implementation
|
|
||||||
|
#region ICollection<> implementation
|
||||||
#region ICollection<> implementation
|
|
||||||
|
/// <summary>Inserts an already prepared element into the Dictionary</summary>
|
||||||
/// <summary>Inserts an already prepared element into the Dictionary</summary>
|
/// <param name="item">Prepared element that will be added to the Dictionary</param>
|
||||||
/// <param name="item">Prepared element that will be added to the Dictionary</param>
|
void ICollection<KeyValuePair<KeyType, ValueType>>.Add(
|
||||||
void ICollection<KeyValuePair<KeyType, ValueType>>.Add(
|
KeyValuePair<KeyType, ValueType> item
|
||||||
KeyValuePair<KeyType, ValueType> item
|
) {
|
||||||
) {
|
throw new NotSupportedException(
|
||||||
throw new NotSupportedException(
|
"Adding items is not supported by the read-only Dictionary"
|
||||||
"Adding items is not supported by the read-only Dictionary"
|
);
|
||||||
);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Removes all items from the Dictionary</summary>
|
||||||
/// <summary>Removes all items from the Dictionary</summary>
|
void ICollection<KeyValuePair<KeyType, ValueType>>.Clear() {
|
||||||
void ICollection<KeyValuePair<KeyType, ValueType>>.Clear() {
|
throw new NotSupportedException(
|
||||||
throw new NotSupportedException(
|
"Clearing is not supported in a read-only Dictionary"
|
||||||
"Clearing is not supported in a read-only Dictionary"
|
);
|
||||||
);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Removes all items from the Dictionary</summary>
|
||||||
/// <summary>Removes all items from the Dictionary</summary>
|
/// <param name="itemToRemove">Item that will be removed from the Dictionary</param>
|
||||||
/// <param name="itemToRemove">Item that will be removed from the Dictionary</param>
|
bool ICollection<KeyValuePair<KeyType, ValueType>>.Remove(
|
||||||
bool ICollection<KeyValuePair<KeyType, ValueType>>.Remove(
|
KeyValuePair<KeyType, ValueType> itemToRemove
|
||||||
KeyValuePair<KeyType, ValueType> itemToRemove
|
) {
|
||||||
) {
|
throw new NotSupportedException(
|
||||||
throw new NotSupportedException(
|
"Removing items is not supported in a read-only Dictionary"
|
||||||
"Removing items is not supported in a read-only Dictionary"
|
);
|
||||||
);
|
}
|
||||||
}
|
|
||||||
|
#endregion
|
||||||
#endregion
|
|
||||||
|
#region ICollection implementation
|
||||||
#region ICollection implementation
|
|
||||||
|
/// <summary>Copies the contents of the Dictionary into an array</summary>
|
||||||
/// <summary>Copies the contents of the Dictionary into an array</summary>
|
/// <param name="array">Array the Dictionary contents will be copied into</param>
|
||||||
/// <param name="array">Array the Dictionary contents will be copied into</param>
|
/// <param name="index">
|
||||||
/// <param name="index">
|
/// Starting index at which to begin filling the destination array
|
||||||
/// Starting index at which to begin filling the destination array
|
/// </param>
|
||||||
/// </param>
|
void ICollection.CopyTo(Array array, int index) {
|
||||||
void ICollection.CopyTo(Array array, int index) {
|
this.objectDictionary.CopyTo(array, index);
|
||||||
this.objectDictionary.CopyTo(array, index);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Whether the Dictionary is synchronized for multi-threaded usage</summary>
|
||||||
/// <summary>Whether the Dictionary is synchronized for multi-threaded usage</summary>
|
bool ICollection.IsSynchronized {
|
||||||
bool ICollection.IsSynchronized {
|
get { return this.objectDictionary.IsSynchronized; }
|
||||||
get { return this.objectDictionary.IsSynchronized; }
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Synchronization root on which the Dictionary locks</summary>
|
||||||
/// <summary>Synchronization root on which the Dictionary locks</summary>
|
object ICollection.SyncRoot {
|
||||||
object ICollection.SyncRoot {
|
get { return this.objectDictionary.SyncRoot; }
|
||||||
get { return this.objectDictionary.SyncRoot; }
|
}
|
||||||
}
|
|
||||||
|
#endregion
|
||||||
#endregion
|
|
||||||
|
#if !NO_SERIALIZATION
|
||||||
#if !NO_SERIALIZATION
|
#region ISerializable implementation
|
||||||
#region ISerializable implementation
|
|
||||||
|
/// <summary>Serializes the Dictionary</summary>
|
||||||
/// <summary>Serializes the Dictionary</summary>
|
/// <param name="info">
|
||||||
/// <param name="info">
|
/// Provides the container into which the Dictionary will serialize itself
|
||||||
/// Provides the container into which the Dictionary will serialize itself
|
/// </param>
|
||||||
/// </param>
|
/// <param name="context">
|
||||||
/// <param name="context">
|
/// Contextual informations about the serialization environment
|
||||||
/// Contextual informations about the serialization environment
|
/// </param>
|
||||||
/// </param>
|
void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context) {
|
||||||
void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context) {
|
(this.typedDictionary as ISerializable).GetObjectData(info, context);
|
||||||
(this.typedDictionary as ISerializable).GetObjectData(info, context);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Called after all objects have been successfully deserialized</summary>
|
||||||
/// <summary>Called after all objects have been successfully deserialized</summary>
|
/// <param name="sender">Nicht unterstützt</param>
|
||||||
/// <param name="sender">Nicht unterstützt</param>
|
void IDeserializationCallback.OnDeserialization(object sender) {
|
||||||
void IDeserializationCallback.OnDeserialization(object sender) {
|
(this.typedDictionary as IDeserializationCallback).OnDeserialization(sender);
|
||||||
(this.typedDictionary as IDeserializationCallback).OnDeserialization(sender);
|
}
|
||||||
}
|
|
||||||
|
#endregion
|
||||||
#endregion
|
#endif //!NO_SERIALIZATION
|
||||||
#endif //!NO_SERIALIZATION
|
|
||||||
|
/// <summary>The wrapped Dictionary under its type-safe interface</summary>
|
||||||
/// <summary>The wrapped Dictionary under its type-safe interface</summary>
|
private IDictionary<KeyType, ValueType> typedDictionary;
|
||||||
private IDictionary<KeyType, ValueType> typedDictionary;
|
/// <summary>The wrapped Dictionary under its object interface</summary>
|
||||||
/// <summary>The wrapped Dictionary under its object interface</summary>
|
private IDictionary objectDictionary;
|
||||||
private IDictionary objectDictionary;
|
/// <summary>ReadOnly wrapper for the keys collection of the Dictionary</summary>
|
||||||
/// <summary>ReadOnly wrapper for the keys collection of the Dictionary</summary>
|
private ReadOnlyCollection<KeyType> readonlyKeyCollection;
|
||||||
private ReadOnlyCollection<KeyType> readonlyKeyCollection;
|
/// <summary>ReadOnly wrapper for the values collection of the Dictionary</summary>
|
||||||
/// <summary>ReadOnly wrapper for the values collection of the Dictionary</summary>
|
private ReadOnlyCollection<ValueType> readonlyValueCollection;
|
||||||
private ReadOnlyCollection<ValueType> readonlyValueCollection;
|
}
|
||||||
}
|
|
||||||
|
} // namespace Nuclex.Support.Collections
|
||||||
} // namespace Nuclex.Support.Collections
|
|
||||||
|
|
|
@ -1,381 +1,380 @@
|
||||||
#region CPL License
|
#region Apache License 2.0
|
||||||
/*
|
/*
|
||||||
Nuclex Framework
|
Nuclex .NET Framework
|
||||||
Copyright (C) 2002-2017 Nuclex Development Labs
|
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
|
||||||
|
|
||||||
This library is free software; you can redistribute it and/or
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
modify it under the terms of the IBM Common Public License as
|
you may not use this file except in compliance with the License.
|
||||||
published by the IBM Corporation; either version 1.0 of the
|
You may obtain a copy of the License at
|
||||||
License, or (at your option) any later version.
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
This library is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
Unless required by applicable law or agreed to in writing, software
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
IBM Common Public License for more details.
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
You should have received a copy of the IBM Common Public
|
limitations under the License.
|
||||||
License along with this library
|
*/
|
||||||
*/
|
#endregion // Apache License 2.0
|
||||||
#endregion
|
|
||||||
|
using System;
|
||||||
using System;
|
using System.Collections;
|
||||||
using System.Collections;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Generic;
|
|
||||||
|
#if UNITTEST
|
||||||
#if UNITTEST
|
|
||||||
|
using NUnit.Framework;
|
||||||
using NUnit.Framework;
|
|
||||||
|
namespace Nuclex.Support.Collections {
|
||||||
namespace Nuclex.Support.Collections {
|
|
||||||
|
/// <summary>Unit Test for the read only list wrapper</summary>
|
||||||
/// <summary>Unit Test for the read only list wrapper</summary>
|
[TestFixture]
|
||||||
[TestFixture]
|
internal class ReadOnlyListTest {
|
||||||
internal class ReadOnlyListTest {
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Verifies that the copy constructor of the read only list works
|
||||||
/// Verifies that the copy constructor of the read only list works
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void TestCopyConstructor() {
|
||||||
public void TestCopyConstructor() {
|
int[] integers = new int[] { 12, 34, 56, 78 };
|
||||||
int[] integers = new int[] { 12, 34, 56, 78 };
|
ReadOnlyList<int> testList = new ReadOnlyList<int>(integers);
|
||||||
ReadOnlyList<int> testList = new ReadOnlyList<int>(integers);
|
|
||||||
|
CollectionAssert.AreEqual(integers, testList);
|
||||||
CollectionAssert.AreEqual(integers, testList);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Verifies that the IsReadOnly property returns true</summary>
|
||||||
/// <summary>Verifies that the IsReadOnly property returns true</summary>
|
[Test]
|
||||||
[Test]
|
public void TestIsReadOnly() {
|
||||||
public void TestIsReadOnly() {
|
ReadOnlyList<int> testList = new ReadOnlyList<int>(new int[0]);
|
||||||
ReadOnlyList<int> testList = new ReadOnlyList<int>(new int[0]);
|
|
||||||
|
Assert.IsTrue(testList.IsReadOnly);
|
||||||
Assert.IsTrue(testList.IsReadOnly);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Verifies that the CopyTo() of the read only list works
|
||||||
/// Verifies that the CopyTo() of the read only list works
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void TestCopyToArray() {
|
||||||
public void TestCopyToArray() {
|
int[] inputIntegers = new int[] { 12, 34, 56, 78 };
|
||||||
int[] inputIntegers = new int[] { 12, 34, 56, 78 };
|
ReadOnlyList<int> testList = new ReadOnlyList<int>(inputIntegers);
|
||||||
ReadOnlyList<int> testList = new ReadOnlyList<int>(inputIntegers);
|
|
||||||
|
int[] outputIntegers = new int[testList.Count];
|
||||||
int[] outputIntegers = new int[testList.Count];
|
testList.CopyTo(outputIntegers, 0);
|
||||||
testList.CopyTo(outputIntegers, 0);
|
|
||||||
|
CollectionAssert.AreEqual(inputIntegers, outputIntegers);
|
||||||
CollectionAssert.AreEqual(inputIntegers, outputIntegers);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Checks whether the Contains() method of the read only list is able to
|
||||||
/// Checks whether the Contains() method of the read only list is able to
|
/// determine if the list contains an item
|
||||||
/// determine if the list contains an item
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void TestContains() {
|
||||||
public void TestContains() {
|
int[] integers = new int[] { 1234, 6789 };
|
||||||
int[] integers = new int[] { 1234, 6789 };
|
ReadOnlyList<int> testList = new ReadOnlyList<int>(integers);
|
||||||
ReadOnlyList<int> testList = new ReadOnlyList<int>(integers);
|
|
||||||
|
Assert.IsTrue(testList.Contains(1234));
|
||||||
Assert.IsTrue(testList.Contains(1234));
|
Assert.IsFalse(testList.Contains(4321));
|
||||||
Assert.IsFalse(testList.Contains(4321));
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Checks whether the IndexOf() method of the read only list is able to
|
||||||
/// Checks whether the IndexOf() method of the read only list is able to
|
/// determine if the index of an item in the list
|
||||||
/// determine if the index of an item in the list
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void TestIndexOf() {
|
||||||
public void TestIndexOf() {
|
int[] integers = new int[] { 12, 34, 67, 89 };
|
||||||
int[] integers = new int[] { 12, 34, 67, 89 };
|
ReadOnlyList<int> testList = new ReadOnlyList<int>(integers);
|
||||||
ReadOnlyList<int> testList = new ReadOnlyList<int>(integers);
|
|
||||||
|
Assert.AreEqual(0, testList.IndexOf(12));
|
||||||
Assert.AreEqual(0, testList.IndexOf(12));
|
Assert.AreEqual(1, testList.IndexOf(34));
|
||||||
Assert.AreEqual(1, testList.IndexOf(34));
|
Assert.AreEqual(2, testList.IndexOf(67));
|
||||||
Assert.AreEqual(2, testList.IndexOf(67));
|
Assert.AreEqual(3, testList.IndexOf(89));
|
||||||
Assert.AreEqual(3, testList.IndexOf(89));
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Checks whether the indexer method of the read only list is able to
|
||||||
/// Checks whether the indexer method of the read only list is able to
|
/// retrieve items from the list
|
||||||
/// retrieve items from the list
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void TestRetrieveByIndexer() {
|
||||||
public void TestRetrieveByIndexer() {
|
int[] integers = new int[] { 12, 34, 67, 89 };
|
||||||
int[] integers = new int[] { 12, 34, 67, 89 };
|
ReadOnlyList<int> testList = new ReadOnlyList<int>(integers);
|
||||||
ReadOnlyList<int> testList = new ReadOnlyList<int>(integers);
|
|
||||||
|
Assert.AreEqual(12, testList[0]);
|
||||||
Assert.AreEqual(12, testList[0]);
|
Assert.AreEqual(34, testList[1]);
|
||||||
Assert.AreEqual(34, testList[1]);
|
Assert.AreEqual(67, testList[2]);
|
||||||
Assert.AreEqual(67, testList[2]);
|
Assert.AreEqual(89, testList[3]);
|
||||||
Assert.AreEqual(89, testList[3]);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Checks whether the read only list will throw an exception if its Insert() method
|
||||||
/// Checks whether the read only list will throw an exception if its Insert() method
|
/// is called via the generic IList<> interface
|
||||||
/// is called via the generic IList<> interface
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void TestThrowOnInsertViaGenericIList() {
|
||||||
public void TestThrowOnInsertViaGenericIList() {
|
ReadOnlyList<int> testList = new ReadOnlyList<int>(new int[0]);
|
||||||
ReadOnlyList<int> testList = new ReadOnlyList<int>(new int[0]);
|
Assert.Throws<NotSupportedException>(
|
||||||
Assert.Throws<NotSupportedException>(
|
delegate() { (testList as IList<int>).Insert(0, 12345); }
|
||||||
delegate() { (testList as IList<int>).Insert(0, 12345); }
|
);
|
||||||
);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Checks whether the read only list will throw an exception if its RemoveAt() method
|
||||||
/// Checks whether the read only list will throw an exception if its RemoveAt() method
|
/// is called via the generic IList<> interface
|
||||||
/// is called via the generic IList<> interface
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void TestThrowOnRemoveViaGenericIList() {
|
||||||
public void TestThrowOnRemoveViaGenericIList() {
|
ReadOnlyList<int> testList = new ReadOnlyList<int>(new int[1]);
|
||||||
ReadOnlyList<int> testList = new ReadOnlyList<int>(new int[1]);
|
Assert.Throws<NotSupportedException>(
|
||||||
Assert.Throws<NotSupportedException>(
|
delegate() { (testList as IList<int>).RemoveAt(0); }
|
||||||
delegate() { (testList as IList<int>).RemoveAt(0); }
|
);
|
||||||
);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Checks whether the indexer method of the read only list will throw an exception
|
||||||
/// Checks whether the indexer method of the read only list will throw an exception
|
/// if it is attempted to be used for replacing an item
|
||||||
/// if it is attempted to be used for replacing an item
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void TestRetrieveByIndexerViaGenericIList() {
|
||||||
public void TestRetrieveByIndexerViaGenericIList() {
|
int[] integers = new int[] { 12, 34, 67, 89 };
|
||||||
int[] integers = new int[] { 12, 34, 67, 89 };
|
ReadOnlyList<int> testList = new ReadOnlyList<int>(integers);
|
||||||
ReadOnlyList<int> testList = new ReadOnlyList<int>(integers);
|
|
||||||
|
Assert.AreEqual(12, (testList as IList<int>)[0]);
|
||||||
Assert.AreEqual(12, (testList as IList<int>)[0]);
|
Assert.AreEqual(34, (testList as IList<int>)[1]);
|
||||||
Assert.AreEqual(34, (testList as IList<int>)[1]);
|
Assert.AreEqual(67, (testList as IList<int>)[2]);
|
||||||
Assert.AreEqual(67, (testList as IList<int>)[2]);
|
Assert.AreEqual(89, (testList as IList<int>)[3]);
|
||||||
Assert.AreEqual(89, (testList as IList<int>)[3]);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Checks whether the indexer method of the read only list will throw an exception
|
||||||
/// Checks whether the indexer method of the read only list will throw an exception
|
/// if it is attempted to be used for replacing an item
|
||||||
/// if it is attempted to be used for replacing an item
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void TestThrowOnReplaceByIndexerViaGenericIList() {
|
||||||
public void TestThrowOnReplaceByIndexerViaGenericIList() {
|
ReadOnlyList<int> testList = new ReadOnlyList<int>(new int[1]);
|
||||||
ReadOnlyList<int> testList = new ReadOnlyList<int>(new int[1]);
|
|
||||||
|
Assert.Throws<NotSupportedException>(
|
||||||
Assert.Throws<NotSupportedException>(
|
delegate() { (testList as IList<int>)[0] = 12345; }
|
||||||
delegate() { (testList as IList<int>)[0] = 12345; }
|
);
|
||||||
);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Checks whether the read only list will throw an exception if its Add() method
|
||||||
/// Checks whether the read only list will throw an exception if its Add() method
|
/// is called via the generic ICollection<> interface
|
||||||
/// is called via the generic ICollection<> interface
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void TestThrowOnAddViaGenericICollection() {
|
||||||
public void TestThrowOnAddViaGenericICollection() {
|
ReadOnlyList<int> testList = new ReadOnlyList<int>(new int[0]);
|
||||||
ReadOnlyList<int> testList = new ReadOnlyList<int>(new int[0]);
|
Assert.Throws<NotSupportedException>(
|
||||||
Assert.Throws<NotSupportedException>(
|
delegate() { (testList as ICollection<int>).Add(12345); }
|
||||||
delegate() { (testList as ICollection<int>).Add(12345); }
|
);
|
||||||
);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Checks whether the read only list will throw an exception if its Clear() method
|
||||||
/// Checks whether the read only list will throw an exception if its Clear() method
|
/// is called via the generic ICollection<> interface
|
||||||
/// is called via the generic ICollection<> interface
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void TestThrowOnClearViaGenericICollection() {
|
||||||
public void TestThrowOnClearViaGenericICollection() {
|
ReadOnlyList<int> testList = new ReadOnlyList<int>(new int[1]);
|
||||||
ReadOnlyList<int> testList = new ReadOnlyList<int>(new int[1]);
|
Assert.Throws<NotSupportedException>(
|
||||||
Assert.Throws<NotSupportedException>(
|
delegate() { (testList as ICollection<int>).Clear(); }
|
||||||
delegate() { (testList as ICollection<int>).Clear(); }
|
);
|
||||||
);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Checks whether the read only list will throw an exception if its Remove() method
|
||||||
/// Checks whether the read only list will throw an exception if its Remove() method
|
/// is called via the generic ICollection<> interface
|
||||||
/// is called via the generic ICollection<> interface
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void TestThrowOnRemoveViaGenericICollection() {
|
||||||
public void TestThrowOnRemoveViaGenericICollection() {
|
int[] integers = new int[] { 12, 34, 67, 89 };
|
||||||
int[] integers = new int[] { 12, 34, 67, 89 };
|
ReadOnlyList<int> testList = new ReadOnlyList<int>(integers);
|
||||||
ReadOnlyList<int> testList = new ReadOnlyList<int>(integers);
|
|
||||||
|
Assert.Throws<NotSupportedException>(
|
||||||
Assert.Throws<NotSupportedException>(
|
delegate() { (testList as ICollection<int>).Remove(89); }
|
||||||
delegate() { (testList as ICollection<int>).Remove(89); }
|
);
|
||||||
);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Tests whether the typesafe enumerator of the read only list is working
|
||||||
/// Tests whether the typesafe enumerator of the read only list is working
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void TestTypesafeEnumerator() {
|
||||||
public void TestTypesafeEnumerator() {
|
int[] inputIntegers = new int[] { 12, 34, 56, 78 };
|
||||||
int[] inputIntegers = new int[] { 12, 34, 56, 78 };
|
ReadOnlyList<int> testList = new ReadOnlyList<int>(inputIntegers);
|
||||||
ReadOnlyList<int> testList = new ReadOnlyList<int>(inputIntegers);
|
|
||||||
|
List<int> outputIntegers = new List<int>();
|
||||||
List<int> outputIntegers = new List<int>();
|
foreach(int value in testList) {
|
||||||
foreach(int value in testList) {
|
outputIntegers.Add(value);
|
||||||
outputIntegers.Add(value);
|
}
|
||||||
}
|
|
||||||
|
CollectionAssert.AreEqual(inputIntegers, outputIntegers);
|
||||||
CollectionAssert.AreEqual(inputIntegers, outputIntegers);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Checks whether the read only list will throw an exception if its Clear() method
|
||||||
/// Checks whether the read only list will throw an exception if its Clear() method
|
/// is called via the IList interface
|
||||||
/// is called via the IList interface
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void TestThrowOnClearViaIList() {
|
||||||
public void TestThrowOnClearViaIList() {
|
ReadOnlyList<int> testList = new ReadOnlyList<int>(new int[1]);
|
||||||
ReadOnlyList<int> testList = new ReadOnlyList<int>(new int[1]);
|
Assert.Throws<NotSupportedException>(
|
||||||
Assert.Throws<NotSupportedException>(
|
delegate() { (testList as IList).Clear(); }
|
||||||
delegate() { (testList as IList).Clear(); }
|
);
|
||||||
);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Checks whether the read only list will throw an exception if its Add() method
|
||||||
/// Checks whether the read only list will throw an exception if its Add() method
|
/// is called via the IList interface
|
||||||
/// is called via the IList interface
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void TestThrowOnAddViaIList() {
|
||||||
public void TestThrowOnAddViaIList() {
|
ReadOnlyList<int> testList = new ReadOnlyList<int>(new int[0]);
|
||||||
ReadOnlyList<int> testList = new ReadOnlyList<int>(new int[0]);
|
Assert.Throws<NotSupportedException>(
|
||||||
Assert.Throws<NotSupportedException>(
|
delegate() { (testList as IList).Add(12345); }
|
||||||
delegate() { (testList as IList).Add(12345); }
|
);
|
||||||
);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Checks whether the Contains() method of the read only list is able to
|
||||||
/// Checks whether the Contains() method of the read only list is able to
|
/// determine if the list contains an item
|
||||||
/// determine if the list contains an item
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void TestContainsViaIList() {
|
||||||
public void TestContainsViaIList() {
|
int[] integers = new int[] { 1234, 6789 };
|
||||||
int[] integers = new int[] { 1234, 6789 };
|
ReadOnlyList<int> testList = new ReadOnlyList<int>(integers);
|
||||||
ReadOnlyList<int> testList = new ReadOnlyList<int>(integers);
|
|
||||||
|
Assert.IsTrue((testList as IList).Contains(1234));
|
||||||
Assert.IsTrue((testList as IList).Contains(1234));
|
Assert.IsFalse((testList as IList).Contains(4321));
|
||||||
Assert.IsFalse((testList as IList).Contains(4321));
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Checks whether the IndexOf() method of the read only list is able to
|
||||||
/// Checks whether the IndexOf() method of the read only list is able to
|
/// determine if the index of an item in the list
|
||||||
/// determine if the index of an item in the list
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void TestIndexOfViaIList() {
|
||||||
public void TestIndexOfViaIList() {
|
int[] integers = new int[] { 12, 34, 67, 89 };
|
||||||
int[] integers = new int[] { 12, 34, 67, 89 };
|
ReadOnlyList<int> testList = new ReadOnlyList<int>(integers);
|
||||||
ReadOnlyList<int> testList = new ReadOnlyList<int>(integers);
|
|
||||||
|
Assert.AreEqual(0, (testList as IList).IndexOf(12));
|
||||||
Assert.AreEqual(0, (testList as IList).IndexOf(12));
|
Assert.AreEqual(1, (testList as IList).IndexOf(34));
|
||||||
Assert.AreEqual(1, (testList as IList).IndexOf(34));
|
Assert.AreEqual(2, (testList as IList).IndexOf(67));
|
||||||
Assert.AreEqual(2, (testList as IList).IndexOf(67));
|
Assert.AreEqual(3, (testList as IList).IndexOf(89));
|
||||||
Assert.AreEqual(3, (testList as IList).IndexOf(89));
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Checks whether the read only list will throw an exception if its Insert() method
|
||||||
/// Checks whether the read only list will throw an exception if its Insert() method
|
/// is called via the IList interface
|
||||||
/// is called via the IList interface
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void TestThrowOnInsertViaIList() {
|
||||||
public void TestThrowOnInsertViaIList() {
|
ReadOnlyList<int> testList = new ReadOnlyList<int>(new int[0]);
|
||||||
ReadOnlyList<int> testList = new ReadOnlyList<int>(new int[0]);
|
Assert.Throws<NotSupportedException>(
|
||||||
Assert.Throws<NotSupportedException>(
|
delegate() { (testList as IList).Insert(0, 12345); }
|
||||||
delegate() { (testList as IList).Insert(0, 12345); }
|
);
|
||||||
);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Checks whether the IsFixedSize property of the read only list returns the
|
||||||
/// Checks whether the IsFixedSize property of the read only list returns the
|
/// expected result for a read only list based on a fixed array
|
||||||
/// expected result for a read only list based on a fixed array
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void TestIsFixedSizeViaIList() {
|
||||||
public void TestIsFixedSizeViaIList() {
|
int[] integers = new int[] { 12, 34, 67, 89 };
|
||||||
int[] integers = new int[] { 12, 34, 67, 89 };
|
ReadOnlyList<int> testList = new ReadOnlyList<int>(integers);
|
||||||
ReadOnlyList<int> testList = new ReadOnlyList<int>(integers);
|
|
||||||
|
Assert.IsTrue((testList as IList).IsFixedSize);
|
||||||
Assert.IsTrue((testList as IList).IsFixedSize);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Checks whether the read only list will throw an exception if its Remove() method
|
||||||
/// Checks whether the read only list will throw an exception if its Remove() method
|
/// is called via the IList interface
|
||||||
/// is called via the IList interface
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void TestThrowOnRemoveViaIList() {
|
||||||
public void TestThrowOnRemoveViaIList() {
|
int[] integers = new int[] { 1234, 6789 };
|
||||||
int[] integers = new int[] { 1234, 6789 };
|
ReadOnlyList<int> testList = new ReadOnlyList<int>(integers);
|
||||||
ReadOnlyList<int> testList = new ReadOnlyList<int>(integers);
|
|
||||||
|
Assert.Throws<NotSupportedException>(
|
||||||
Assert.Throws<NotSupportedException>(
|
delegate() { (testList as IList).Remove(6789); }
|
||||||
delegate() { (testList as IList).Remove(6789); }
|
);
|
||||||
);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Checks whether the read only list will throw an exception if its Remove() method
|
||||||
/// Checks whether the read only list will throw an exception if its Remove() method
|
/// is called via the IList interface
|
||||||
/// is called via the IList interface
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void TestThrowOnRemoveAtViaIList() {
|
||||||
public void TestThrowOnRemoveAtViaIList() {
|
ReadOnlyList<int> testList = new ReadOnlyList<int>(new int[1]);
|
||||||
ReadOnlyList<int> testList = new ReadOnlyList<int>(new int[1]);
|
|
||||||
|
Assert.Throws<NotSupportedException>(
|
||||||
Assert.Throws<NotSupportedException>(
|
delegate() { (testList as IList).RemoveAt(0); }
|
||||||
delegate() { (testList as IList).RemoveAt(0); }
|
);
|
||||||
);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Checks whether the indexer method of the read only list will throw an exception
|
||||||
/// Checks whether the indexer method of the read only list will throw an exception
|
/// if it is attempted to be used for replacing an item
|
||||||
/// if it is attempted to be used for replacing an item
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void TestRetrieveByIndexerViaIList() {
|
||||||
public void TestRetrieveByIndexerViaIList() {
|
int[] integers = new int[] { 12, 34, 67, 89 };
|
||||||
int[] integers = new int[] { 12, 34, 67, 89 };
|
ReadOnlyList<int> testList = new ReadOnlyList<int>(integers);
|
||||||
ReadOnlyList<int> testList = new ReadOnlyList<int>(integers);
|
|
||||||
|
Assert.AreEqual(12, (testList as IList)[0]);
|
||||||
Assert.AreEqual(12, (testList as IList)[0]);
|
Assert.AreEqual(34, (testList as IList)[1]);
|
||||||
Assert.AreEqual(34, (testList as IList)[1]);
|
Assert.AreEqual(67, (testList as IList)[2]);
|
||||||
Assert.AreEqual(67, (testList as IList)[2]);
|
Assert.AreEqual(89, (testList as IList)[3]);
|
||||||
Assert.AreEqual(89, (testList as IList)[3]);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Checks whether the indexer method of the read only list will throw an exception
|
||||||
/// Checks whether the indexer method of the read only list will throw an exception
|
/// if it is attempted to be used for replacing an item
|
||||||
/// if it is attempted to be used for replacing an item
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void TestThrowOnReplaceByIndexerViaIList() {
|
||||||
public void TestThrowOnReplaceByIndexerViaIList() {
|
ReadOnlyList<int> testList = new ReadOnlyList<int>(new int[1]);
|
||||||
ReadOnlyList<int> testList = new ReadOnlyList<int>(new int[1]);
|
|
||||||
|
Assert.Throws<NotSupportedException>(
|
||||||
Assert.Throws<NotSupportedException>(
|
delegate() { (testList as IList)[0] = 12345; }
|
||||||
delegate() { (testList as IList)[0] = 12345; }
|
);
|
||||||
);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Verifies that the CopyTo() of the read only list works if invoked via
|
||||||
/// Verifies that the CopyTo() of the read only list works if invoked via
|
/// the ICollection interface
|
||||||
/// the ICollection interface
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void TestCopyToArrayViaICollection() {
|
||||||
public void TestCopyToArrayViaICollection() {
|
int[] inputIntegers = new int[] { 12, 34, 56, 78 };
|
||||||
int[] inputIntegers = new int[] { 12, 34, 56, 78 };
|
ReadOnlyList<int> testList = new ReadOnlyList<int>(inputIntegers);
|
||||||
ReadOnlyList<int> testList = new ReadOnlyList<int>(inputIntegers);
|
|
||||||
|
int[] outputIntegers = new int[testList.Count];
|
||||||
int[] outputIntegers = new int[testList.Count];
|
(testList as ICollection).CopyTo(outputIntegers, 0);
|
||||||
(testList as ICollection).CopyTo(outputIntegers, 0);
|
|
||||||
|
CollectionAssert.AreEqual(inputIntegers, outputIntegers);
|
||||||
CollectionAssert.AreEqual(inputIntegers, outputIntegers);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Verifies that the IsSynchronized property and the SyncRoot property are working
|
||||||
/// Verifies that the IsSynchronized property and the SyncRoot property are working
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void TestSynchronization() {
|
||||||
public void TestSynchronization() {
|
ReadOnlyList<int> testList = new ReadOnlyList<int>(new int[0]);
|
||||||
ReadOnlyList<int> testList = new ReadOnlyList<int>(new int[0]);
|
|
||||||
|
if(!(testList as ICollection).IsSynchronized) {
|
||||||
if(!(testList as ICollection).IsSynchronized) {
|
lock((testList as ICollection).SyncRoot) {
|
||||||
lock((testList as ICollection).SyncRoot) {
|
Assert.AreEqual(0, testList.Count);
|
||||||
Assert.AreEqual(0, testList.Count);
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
}
|
||||||
}
|
|
||||||
|
} // namespace Nuclex.Support.Collections
|
||||||
} // namespace Nuclex.Support.Collections
|
|
||||||
|
#endif // UNITTEST
|
||||||
#endif // UNITTEST
|
|
||||||
|
|
|
@ -1,258 +1,257 @@
|
||||||
#region CPL License
|
#region Apache License 2.0
|
||||||
/*
|
/*
|
||||||
Nuclex Framework
|
Nuclex .NET Framework
|
||||||
Copyright (C) 2002-2017 Nuclex Development Labs
|
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
|
||||||
|
|
||||||
This library is free software; you can redistribute it and/or
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
modify it under the terms of the IBM Common Public License as
|
you may not use this file except in compliance with the License.
|
||||||
published by the IBM Corporation; either version 1.0 of the
|
You may obtain a copy of the License at
|
||||||
License, or (at your option) any later version.
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
This library is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
Unless required by applicable law or agreed to in writing, software
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
IBM Common Public License for more details.
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
You should have received a copy of the IBM Common Public
|
limitations under the License.
|
||||||
License along with this library
|
*/
|
||||||
*/
|
#endregion // Apache License 2.0
|
||||||
#endregion
|
|
||||||
|
using System;
|
||||||
using System;
|
using System.Collections;
|
||||||
using System.Collections;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Generic;
|
|
||||||
|
namespace Nuclex.Support.Collections {
|
||||||
namespace Nuclex.Support.Collections {
|
|
||||||
|
/// <summary>Wraps a list and prevents users from modifying it</summary>
|
||||||
/// <summary>Wraps a list and prevents users from modifying it</summary>
|
/// <typeparam name="TItem">Type of items to manage in the set</typeparam>
|
||||||
/// <typeparam name="TItem">Type of items to manage in the set</typeparam>
|
public class ReadOnlyList<TItem> : IList<TItem>, IList {
|
||||||
public class ReadOnlyList<TItem> : IList<TItem>, IList {
|
|
||||||
|
/// <summary>Initializes a new read-only List wrapper</summary>
|
||||||
/// <summary>Initializes a new read-only List wrapper</summary>
|
/// <param name="list">List that will be wrapped</param>
|
||||||
/// <param name="list">List that will be wrapped</param>
|
public ReadOnlyList(IList<TItem> list) {
|
||||||
public ReadOnlyList(IList<TItem> list) {
|
this.typedList = list;
|
||||||
this.typedList = list;
|
this.objectList = (list as IList);
|
||||||
this.objectList = (list as IList);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Retrieves the index of an item within the List</summary>
|
||||||
/// <summary>Retrieves the index of an item within the List</summary>
|
/// <param name="item">Item whose index will be returned</param>
|
||||||
/// <param name="item">Item whose index will be returned</param>
|
/// <returns>The zero-based index of the specified item in the List</returns>
|
||||||
/// <returns>The zero-based index of the specified item in the List</returns>
|
public int IndexOf(TItem item) {
|
||||||
public int IndexOf(TItem item) {
|
return this.typedList.IndexOf(item);
|
||||||
return this.typedList.IndexOf(item);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Accesses the List item with the specified index</summary>
|
||||||
/// <summary>Accesses the List item with the specified index</summary>
|
/// <param name="index">Zero-based index of the List item that will be accessed</param>
|
||||||
/// <param name="index">Zero-based index of the List item that will be accessed</param>
|
public TItem this[int index] {
|
||||||
public TItem this[int index] {
|
get { return this.typedList[index]; }
|
||||||
get { return this.typedList[index]; }
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Determines whether the List contains the specified item</summary>
|
||||||
/// <summary>Determines whether the List contains the specified item</summary>
|
/// <param name="item">Item that will be checked for</param>
|
||||||
/// <param name="item">Item that will be checked for</param>
|
/// <returns>True if the specified item is contained in the List</returns>
|
||||||
/// <returns>True if the specified item is contained in the List</returns>
|
public bool Contains(TItem item) {
|
||||||
public bool Contains(TItem item) {
|
return this.typedList.Contains(item);
|
||||||
return this.typedList.Contains(item);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Copies the contents of the List into an array</summary>
|
||||||
/// <summary>Copies the contents of the List into an array</summary>
|
/// <param name="array">Array the List will be copied into</param>
|
||||||
/// <param name="array">Array the List will be copied into</param>
|
/// <param name="arrayIndex">
|
||||||
/// <param name="arrayIndex">
|
/// Starting index at which to begin filling the destination array
|
||||||
/// Starting index at which to begin filling the destination array
|
/// </param>
|
||||||
/// </param>
|
public void CopyTo(TItem[] array, int arrayIndex) {
|
||||||
public void CopyTo(TItem[] array, int arrayIndex) {
|
this.typedList.CopyTo(array, arrayIndex);
|
||||||
this.typedList.CopyTo(array, arrayIndex);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>The number of items current contained in the list</summary>
|
||||||
/// <summary>The number of items current contained in the list</summary>
|
public int Count {
|
||||||
public int Count {
|
get { return this.typedList.Count; }
|
||||||
get { return this.typedList.Count; }
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Whether the list is write-protected</summary>
|
||||||
/// <summary>Whether the list is write-protected</summary>
|
public bool IsReadOnly {
|
||||||
public bool IsReadOnly {
|
get { return true; }
|
||||||
get { return true; }
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Returns a new enumerator over the contents of the list</summary>
|
||||||
/// <summary>Returns a new enumerator over the contents of the list</summary>
|
/// <returns>The new list content enumerator</returns>
|
||||||
/// <returns>The new list content enumerator</returns>
|
public IEnumerator<TItem> GetEnumerator() {
|
||||||
public IEnumerator<TItem> GetEnumerator() {
|
return this.typedList.GetEnumerator();
|
||||||
return this.typedList.GetEnumerator();
|
}
|
||||||
}
|
|
||||||
|
#region IList<> implementation
|
||||||
#region IList<> implementation
|
|
||||||
|
/// <summary>Inserts an item into the list</summary>
|
||||||
/// <summary>Inserts an item into the list</summary>
|
/// <param name="index">Zero-based index before which the item will be inserted</param>
|
||||||
/// <param name="index">Zero-based index before which the item will be inserted</param>
|
/// <param name="item">Item that will be inserted into the list</param>
|
||||||
/// <param name="item">Item that will be inserted into the list</param>
|
void IList<TItem>.Insert(int index, TItem item) {
|
||||||
void IList<TItem>.Insert(int index, TItem item) {
|
throw new NotSupportedException(
|
||||||
throw new NotSupportedException(
|
"Inserting items is not supported by the read-only list"
|
||||||
"Inserting items is not supported by the read-only list"
|
);
|
||||||
);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Removes an item from the list</summary>
|
||||||
/// <summary>Removes an item from the list</summary>
|
/// <param name="index">Zero-based index of the item that will be removed</param>
|
||||||
/// <param name="index">Zero-based index of the item that will be removed</param>
|
void IList<TItem>.RemoveAt(int index) {
|
||||||
void IList<TItem>.RemoveAt(int index) {
|
throw new NotSupportedException(
|
||||||
throw new NotSupportedException(
|
"Removing items is not supported by the read-only list"
|
||||||
"Removing items is not supported by the read-only list"
|
);
|
||||||
);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Accesses the list item with the specified index</summary>
|
||||||
/// <summary>Accesses the list item with the specified index</summary>
|
/// <param name="index">Zero-based index of the list item that will be accessed</param>
|
||||||
/// <param name="index">Zero-based index of the list item that will be accessed</param>
|
TItem IList<TItem>.this[int index] {
|
||||||
TItem IList<TItem>.this[int index] {
|
get { return this.typedList[index]; }
|
||||||
get { return this.typedList[index]; }
|
set {
|
||||||
set {
|
throw new NotSupportedException(
|
||||||
throw new NotSupportedException(
|
"Assigning items is not supported by the read-only list"
|
||||||
"Assigning items is not supported by the read-only list"
|
);
|
||||||
);
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
#endregion
|
||||||
#endregion
|
|
||||||
|
#region ICollection<> implementation
|
||||||
#region ICollection<> implementation
|
|
||||||
|
/// <summary>Adds an item to the end of the list</summary>
|
||||||
/// <summary>Adds an item to the end of the list</summary>
|
/// <param name="item">Item that will be added to the list</param>
|
||||||
/// <param name="item">Item that will be added to the list</param>
|
void ICollection<TItem>.Add(TItem item) {
|
||||||
void ICollection<TItem>.Add(TItem item) {
|
throw new NotSupportedException(
|
||||||
throw new NotSupportedException(
|
"Adding items is not supported by the read-only list"
|
||||||
"Adding items is not supported by the read-only list"
|
);
|
||||||
);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Removes all items from the List</summary>
|
||||||
/// <summary>Removes all items from the List</summary>
|
void ICollection<TItem>.Clear() {
|
||||||
void ICollection<TItem>.Clear() {
|
throw new NotSupportedException(
|
||||||
throw new NotSupportedException(
|
"Clearing is not supported by the read-only list"
|
||||||
"Clearing is not supported by the read-only list"
|
);
|
||||||
);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Removes the specified item from the list</summary>
|
||||||
/// <summary>Removes the specified item from the list</summary>
|
/// <param name="item">Item that will be removed from the list</param>
|
||||||
/// <param name="item">Item that will be removed from the list</param>
|
/// <returns>True of the specified item was found in the list and removed</returns>
|
||||||
/// <returns>True of the specified item was found in the list and removed</returns>
|
bool ICollection<TItem>.Remove(TItem item) {
|
||||||
bool ICollection<TItem>.Remove(TItem item) {
|
throw new NotSupportedException(
|
||||||
throw new NotSupportedException(
|
"Removing items is not supported by the read-only list"
|
||||||
"Removing items is not supported by the read-only list"
|
);
|
||||||
);
|
}
|
||||||
}
|
|
||||||
|
#endregion
|
||||||
#endregion
|
|
||||||
|
#region IEnumerable implementation
|
||||||
#region IEnumerable implementation
|
|
||||||
|
/// <summary>Returns a new enumerator over the contents of the list</summary>
|
||||||
/// <summary>Returns a new enumerator over the contents of the list</summary>
|
/// <returns>The new list content enumerator</returns>
|
||||||
/// <returns>The new list content enumerator</returns>
|
IEnumerator IEnumerable.GetEnumerator() {
|
||||||
IEnumerator IEnumerable.GetEnumerator() {
|
return this.objectList.GetEnumerator();
|
||||||
return this.objectList.GetEnumerator();
|
}
|
||||||
}
|
|
||||||
|
#endregion
|
||||||
#endregion
|
|
||||||
|
#region IList implementation
|
||||||
#region IList implementation
|
|
||||||
|
/// <summary>Removes all items from the list</summary>
|
||||||
/// <summary>Removes all items from the list</summary>
|
void IList.Clear() {
|
||||||
void IList.Clear() {
|
throw new NotSupportedException(
|
||||||
throw new NotSupportedException(
|
"Clearing is not supported by the read-only list"
|
||||||
"Clearing is not supported by the read-only list"
|
);
|
||||||
);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Adds an item to the end of the list</summary>
|
||||||
/// <summary>Adds an item to the end of the list</summary>
|
/// <param name="value">Item that will be added to the list</param>
|
||||||
/// <param name="value">Item that will be added to the list</param>
|
int IList.Add(object value) {
|
||||||
int IList.Add(object value) {
|
throw new NotSupportedException(
|
||||||
throw new NotSupportedException(
|
"Adding items is not supported by the read-only list"
|
||||||
"Adding items is not supported by the read-only list"
|
);
|
||||||
);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Determines whether the List contains the specified item</summary>
|
||||||
/// <summary>Determines whether the List contains the specified item</summary>
|
/// <param name="value">Item that will be checked for</param>
|
||||||
/// <param name="value">Item that will be checked for</param>
|
/// <returns>True if the specified item is contained in the list</returns>
|
||||||
/// <returns>True if the specified item is contained in the list</returns>
|
bool IList.Contains(object value) {
|
||||||
bool IList.Contains(object value) {
|
return this.objectList.Contains(value);
|
||||||
return this.objectList.Contains(value);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Retrieves the index of an item within the list</summary>
|
||||||
/// <summary>Retrieves the index of an item within the list</summary>
|
/// <param name="value">Item whose index will be returned</param>
|
||||||
/// <param name="value">Item whose index will be returned</param>
|
/// <returns>The zero-based index of the specified item in the list</returns>
|
||||||
/// <returns>The zero-based index of the specified item in the list</returns>
|
int IList.IndexOf(object value) {
|
||||||
int IList.IndexOf(object value) {
|
return this.objectList.IndexOf(value);
|
||||||
return this.objectList.IndexOf(value);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Inserts an item into the list</summary>
|
||||||
/// <summary>Inserts an item into the list</summary>
|
/// <param name="index">Zero-based index before which the item will be inserted</param>
|
||||||
/// <param name="index">Zero-based index before which the item will be inserted</param>
|
/// <param name="value">Item that will be inserted into the list</param>
|
||||||
/// <param name="value">Item that will be inserted into the list</param>
|
void IList.Insert(int index, object value) {
|
||||||
void IList.Insert(int index, object value) {
|
throw new NotSupportedException(
|
||||||
throw new NotSupportedException(
|
"Inserting items is not supported by the read-only list"
|
||||||
"Inserting items is not supported by the read-only list"
|
);
|
||||||
);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Whether the size of the list is fixed</summary>
|
||||||
/// <summary>Whether the size of the list is fixed</summary>
|
bool IList.IsFixedSize {
|
||||||
bool IList.IsFixedSize {
|
get { return this.objectList.IsFixedSize; }
|
||||||
get { return this.objectList.IsFixedSize; }
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Removes the specified item from the list</summary>
|
||||||
/// <summary>Removes the specified item from the list</summary>
|
/// <param name="value">Item that will be removed from the list</param>
|
||||||
/// <param name="value">Item that will be removed from the list</param>
|
/// <returns>True of the specified item was found in the list and removed</returns>
|
||||||
/// <returns>True of the specified item was found in the list and removed</returns>
|
void IList.Remove(object value) {
|
||||||
void IList.Remove(object value) {
|
throw new NotSupportedException(
|
||||||
throw new NotSupportedException(
|
"Removing items is not supported by the read-only list"
|
||||||
"Removing items is not supported by the read-only list"
|
);
|
||||||
);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Removes an item from the list</summary>
|
||||||
/// <summary>Removes an item from the list</summary>
|
/// <param name="index">Zero-based index of the item that will be removed</param>
|
||||||
/// <param name="index">Zero-based index of the item that will be removed</param>
|
void IList.RemoveAt(int index) {
|
||||||
void IList.RemoveAt(int index) {
|
throw new NotSupportedException(
|
||||||
throw new NotSupportedException(
|
"Removing items is not supported by the read-only list"
|
||||||
"Removing items is not supported by the read-only list"
|
);
|
||||||
);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Accesses the list item with the specified index</summary>
|
||||||
/// <summary>Accesses the list item with the specified index</summary>
|
/// <param name="index">Zero-based index of the list item that will be accessed</param>
|
||||||
/// <param name="index">Zero-based index of the list item that will be accessed</param>
|
object IList.this[int index] {
|
||||||
object IList.this[int index] {
|
get { return this.objectList[index]; }
|
||||||
get { return this.objectList[index]; }
|
set {
|
||||||
set {
|
throw new NotSupportedException(
|
||||||
throw new NotSupportedException(
|
"Assigning items is not supported by the read-only list"
|
||||||
"Assigning items is not supported by the read-only list"
|
);
|
||||||
);
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
#endregion
|
||||||
#endregion
|
|
||||||
|
#region ICollection implementation
|
||||||
#region ICollection implementation
|
|
||||||
|
/// <summary>Copies the contents of the list into an array</summary>
|
||||||
/// <summary>Copies the contents of the list into an array</summary>
|
/// <param name="array">Array the list will be copied into</param>
|
||||||
/// <param name="array">Array the list will be copied into</param>
|
/// <param name="index">
|
||||||
/// <param name="index">
|
/// Starting index at which to begin filling the destination array
|
||||||
/// Starting index at which to begin filling the destination array
|
/// </param>
|
||||||
/// </param>
|
void ICollection.CopyTo(Array array, int index) {
|
||||||
void ICollection.CopyTo(Array array, int index) {
|
this.objectList.CopyTo(array, index);
|
||||||
this.objectList.CopyTo(array, index);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Whether the list is synchronized for multi-threaded usage</summary>
|
||||||
/// <summary>Whether the list is synchronized for multi-threaded usage</summary>
|
bool ICollection.IsSynchronized {
|
||||||
bool ICollection.IsSynchronized {
|
get { return this.objectList.IsSynchronized; }
|
||||||
get { return this.objectList.IsSynchronized; }
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Synchronization root on which the list locks</summary>
|
||||||
/// <summary>Synchronization root on which the list locks</summary>
|
object ICollection.SyncRoot {
|
||||||
object ICollection.SyncRoot {
|
get { return this.objectList.SyncRoot; }
|
||||||
get { return this.objectList.SyncRoot; }
|
}
|
||||||
}
|
|
||||||
|
#endregion
|
||||||
#endregion
|
|
||||||
|
/// <summary>The wrapped list under its type-safe interface</summary>
|
||||||
/// <summary>The wrapped list under its type-safe interface</summary>
|
private IList<TItem> typedList;
|
||||||
private IList<TItem> typedList;
|
/// <summary>The wrapped list under its object interface</summary>
|
||||||
/// <summary>The wrapped list under its object interface</summary>
|
private IList objectList;
|
||||||
private IList objectList;
|
|
||||||
|
}
|
||||||
}
|
|
||||||
|
} // namespace Nuclex.Support.Collections
|
||||||
} // namespace Nuclex.Support.Collections
|
|
||||||
|
|
|
@ -1,208 +1,207 @@
|
||||||
#region CPL License
|
#region Apache License 2.0
|
||||||
/*
|
/*
|
||||||
Nuclex Framework
|
Nuclex .NET Framework
|
||||||
Copyright (C) 2002-2017 Nuclex Development Labs
|
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
|
||||||
|
|
||||||
This library is free software; you can redistribute it and/or
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
modify it under the terms of the IBM Common Public License as
|
you may not use this file except in compliance with the License.
|
||||||
published by the IBM Corporation; either version 1.0 of the
|
You may obtain a copy of the License at
|
||||||
License, or (at your option) any later version.
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
This library is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
Unless required by applicable law or agreed to in writing, software
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
IBM Common Public License for more details.
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
You should have received a copy of the IBM Common Public
|
limitations under the License.
|
||||||
License along with this library
|
*/
|
||||||
*/
|
#endregion // Apache License 2.0
|
||||||
#endregion
|
|
||||||
|
#if !NO_SETS
|
||||||
#if !NO_SETS
|
|
||||||
|
#if UNITTEST
|
||||||
#if UNITTEST
|
|
||||||
|
using System;
|
||||||
using System;
|
using System.Collections;
|
||||||
using System.Collections;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Generic;
|
using System.IO;
|
||||||
using System.IO;
|
using System.Runtime.Serialization.Formatters.Binary;
|
||||||
using System.Runtime.Serialization.Formatters.Binary;
|
|
||||||
|
using NUnit.Framework;
|
||||||
using NUnit.Framework;
|
using NMock;
|
||||||
using NMock;
|
|
||||||
|
namespace Nuclex.Support.Collections {
|
||||||
namespace Nuclex.Support.Collections {
|
|
||||||
|
/// <summary>Unit Test for the observable set wrapper</summary>
|
||||||
/// <summary>Unit Test for the observable set wrapper</summary>
|
[TestFixture]
|
||||||
[TestFixture]
|
internal class ReadOnlySetTest {
|
||||||
internal class ReadOnlySetTest {
|
|
||||||
|
/// <summary>Called before each test is run</summary>
|
||||||
/// <summary>Called before each test is run</summary>
|
[SetUp]
|
||||||
[SetUp]
|
public void Setup() {
|
||||||
public void Setup() {
|
this.set = new HashSet<int>();
|
||||||
this.set = new HashSet<int>();
|
this.readOnlySet = new ReadOnlySet<int>(this.set);
|
||||||
this.readOnlySet = new ReadOnlySet<int>(this.set);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Verifies that the observable set has a default constructor
|
||||||
/// Verifies that the observable set has a default constructor
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void HasDefaultConstructor() {
|
||||||
public void HasDefaultConstructor() {
|
Assert.IsNotNull(new ReadOnlySet<int>(new HashSet<int>()));
|
||||||
Assert.IsNotNull(new ReadOnlySet<int>(new HashSet<int>()));
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Verifies that an exception is thrown upon any attempt to add items
|
||||||
/// Verifies that an exception is thrown upon any attempt to add items
|
/// to a read-only set
|
||||||
/// to a read-only set
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void AddingThrowsException() {
|
||||||
public void AddingThrowsException() {
|
Assert.Throws<NotSupportedException>(
|
||||||
Assert.Throws<NotSupportedException>(
|
delegate() { ((ISet<int>)this.readOnlySet).Add(123); }
|
||||||
delegate() { ((ISet<int>)this.readOnlySet).Add(123); }
|
);
|
||||||
);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Verifies that an exception is thrown upon any attempt to remove items
|
||||||
/// Verifies that an exception is thrown upon any attempt to remove items
|
/// from a read-only set
|
||||||
/// from a read-only set
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void RemovingThrowsException() {
|
||||||
public void RemovingThrowsException() {
|
Assert.Throws<NotSupportedException>(
|
||||||
Assert.Throws<NotSupportedException>(
|
delegate() { ((ISet<int>)this.readOnlySet).Remove(123); }
|
||||||
delegate() { ((ISet<int>)this.readOnlySet).Remove(123); }
|
);
|
||||||
);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Verifies that an exception is thrown upon any attempt to except
|
||||||
/// Verifies that an exception is thrown upon any attempt to except
|
/// the set with another set
|
||||||
/// the set with another set
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void ExceptingThrowsException() {
|
||||||
public void ExceptingThrowsException() {
|
Assert.Throws<NotSupportedException>(
|
||||||
Assert.Throws<NotSupportedException>(
|
delegate() { ((ISet<int>)this.readOnlySet).ExceptWith(null); }
|
||||||
delegate() { ((ISet<int>)this.readOnlySet).ExceptWith(null); }
|
);
|
||||||
);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Verifies that an exception is thrown upon any attempt to intersect
|
||||||
/// Verifies that an exception is thrown upon any attempt to intersect
|
/// the set with another set
|
||||||
/// the set with another set
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void InsersectThrowsException() {
|
||||||
public void InsersectThrowsException() {
|
Assert.Throws<NotSupportedException>(
|
||||||
Assert.Throws<NotSupportedException>(
|
delegate() { ((ISet<int>)this.readOnlySet).IntersectWith(null); }
|
||||||
delegate() { ((ISet<int>)this.readOnlySet).IntersectWith(null); }
|
);
|
||||||
);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Verifies that it's possible to determine whether a set is a proper subset
|
||||||
/// Verifies that it's possible to determine whether a set is a proper subset
|
/// or superset of another set
|
||||||
/// or superset of another set
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void CanDetermineProperSubsetAndSuperset() {
|
||||||
public void CanDetermineProperSubsetAndSuperset() {
|
this.set.Add(1);
|
||||||
this.set.Add(1);
|
this.set.Add(2);
|
||||||
this.set.Add(2);
|
this.set.Add(3);
|
||||||
this.set.Add(3);
|
|
||||||
|
var set2 = new HashSet<int>() { 1, 3 };
|
||||||
var set2 = new HashSet<int>() { 1, 3 };
|
|
||||||
|
Assert.IsTrue(this.readOnlySet.IsProperSupersetOf(set2));
|
||||||
Assert.IsTrue(this.readOnlySet.IsProperSupersetOf(set2));
|
Assert.IsTrue(set2.IsProperSubsetOf(this.readOnlySet));
|
||||||
Assert.IsTrue(set2.IsProperSubsetOf(this.readOnlySet));
|
|
||||||
|
set2.Add(2);
|
||||||
set2.Add(2);
|
|
||||||
|
Assert.IsFalse(this.readOnlySet.IsProperSupersetOf(set2));
|
||||||
Assert.IsFalse(this.readOnlySet.IsProperSupersetOf(set2));
|
Assert.IsFalse(set2.IsProperSubsetOf(this.readOnlySet));
|
||||||
Assert.IsFalse(set2.IsProperSubsetOf(this.readOnlySet));
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Verifies that it's possible to determine whether a set is a subset
|
||||||
/// Verifies that it's possible to determine whether a set is a subset
|
/// or a superset of another set
|
||||||
/// or a superset of another set
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void CanDetermineSubsetAndSuperset() {
|
||||||
public void CanDetermineSubsetAndSuperset() {
|
this.set.Add(1);
|
||||||
this.set.Add(1);
|
this.set.Add(2);
|
||||||
this.set.Add(2);
|
this.set.Add(3);
|
||||||
this.set.Add(3);
|
|
||||||
|
var set2 = new HashSet<int>() { 1, 2, 3 };
|
||||||
var set2 = new HashSet<int>() { 1, 2, 3 };
|
|
||||||
|
Assert.IsTrue(this.readOnlySet.IsSupersetOf(set2));
|
||||||
Assert.IsTrue(this.readOnlySet.IsSupersetOf(set2));
|
Assert.IsTrue(set2.IsSubsetOf(this.readOnlySet));
|
||||||
Assert.IsTrue(set2.IsSubsetOf(this.readOnlySet));
|
|
||||||
|
set2.Add(4);
|
||||||
set2.Add(4);
|
|
||||||
|
Assert.IsFalse(this.readOnlySet.IsSupersetOf(set2));
|
||||||
Assert.IsFalse(this.readOnlySet.IsSupersetOf(set2));
|
Assert.IsFalse(set2.IsSubsetOf(this.readOnlySet));
|
||||||
Assert.IsFalse(set2.IsSubsetOf(this.readOnlySet));
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Verifies that a set can determine if another set overlaps with it
|
||||||
/// Verifies that a set can determine if another set overlaps with it
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void CanDetermineOverlap() {
|
||||||
public void CanDetermineOverlap() {
|
this.set.Add(1);
|
||||||
this.set.Add(1);
|
this.set.Add(3);
|
||||||
this.set.Add(3);
|
this.set.Add(5);
|
||||||
this.set.Add(5);
|
|
||||||
|
var set2 = new HashSet<int>() { 3 };
|
||||||
var set2 = new HashSet<int>() { 3 };
|
|
||||||
|
Assert.IsTrue(this.readOnlySet.Overlaps(set2));
|
||||||
Assert.IsTrue(this.readOnlySet.Overlaps(set2));
|
Assert.IsTrue(set2.Overlaps(this.readOnlySet));
|
||||||
Assert.IsTrue(set2.Overlaps(this.readOnlySet));
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Verifies that a set can determine if another set contains the same elements
|
||||||
/// Verifies that a set can determine if another set contains the same elements
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void CanDetermineSetEquality() {
|
||||||
public void CanDetermineSetEquality() {
|
this.set.Add(1);
|
||||||
this.set.Add(1);
|
this.set.Add(3);
|
||||||
this.set.Add(3);
|
this.set.Add(5);
|
||||||
this.set.Add(5);
|
|
||||||
|
var set2 = new HashSet<int>() { 3, 1, 5 };
|
||||||
var set2 = new HashSet<int>() { 3, 1, 5 };
|
|
||||||
|
Assert.IsTrue(this.readOnlySet.SetEquals(set2));
|
||||||
Assert.IsTrue(this.readOnlySet.SetEquals(set2));
|
Assert.IsTrue(set2.SetEquals(this.readOnlySet));
|
||||||
Assert.IsTrue(set2.SetEquals(this.readOnlySet));
|
|
||||||
|
this.set.Add(7);
|
||||||
this.set.Add(7);
|
|
||||||
|
Assert.IsFalse(this.readOnlySet.SetEquals(set2));
|
||||||
Assert.IsFalse(this.readOnlySet.SetEquals(set2));
|
Assert.IsFalse(set2.SetEquals(this.readOnlySet));
|
||||||
Assert.IsFalse(set2.SetEquals(this.readOnlySet));
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Verifies that any attempt to symmetrically except a read-only set
|
||||||
/// Verifies that any attempt to symmetrically except a read-only set
|
/// causes an exception
|
||||||
/// causes an exception
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void SymmetricallyExceptingThrowsException() {
|
||||||
public void SymmetricallyExceptingThrowsException() {
|
Assert.Throws<NotSupportedException>(
|
||||||
Assert.Throws<NotSupportedException>(
|
delegate() { ((ISet<int>)this.readOnlySet).SymmetricExceptWith(null); }
|
||||||
delegate() { ((ISet<int>)this.readOnlySet).SymmetricExceptWith(null); }
|
);
|
||||||
);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Verifies that any attempt to union a read-only set causes an exception
|
||||||
/// Verifies that any attempt to union a read-only set causes an exception
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void UnioningThrowsException() {
|
||||||
public void UnioningThrowsException() {
|
Assert.Throws<NotSupportedException>(
|
||||||
Assert.Throws<NotSupportedException>(
|
delegate() { ((ISet<int>)this.readOnlySet).UnionWith(null); }
|
||||||
delegate() { ((ISet<int>)this.readOnlySet).UnionWith(null); }
|
);
|
||||||
);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Set being wrapped in a read-only set</summary>
|
||||||
/// <summary>Set being wrapped in a read-only set</summary>
|
private ISet<int> set;
|
||||||
private ISet<int> set;
|
/// <summary>Read-only wrapper around the set</summary>
|
||||||
/// <summary>Read-only wrapper around the set</summary>
|
private ReadOnlySet<int> readOnlySet;
|
||||||
private ReadOnlySet<int> readOnlySet;
|
|
||||||
|
}
|
||||||
}
|
|
||||||
|
} // namespace Nuclex.Support.Collections
|
||||||
} // namespace Nuclex.Support.Collections
|
|
||||||
|
#endif // UNITTEST
|
||||||
#endif // UNITTEST
|
|
||||||
|
#endif // !NO_SETS
|
||||||
#endif // !NO_SETS
|
|
||||||
|
|
|
@ -1,216 +1,215 @@
|
||||||
#region CPL License
|
#region Apache License 2.0
|
||||||
/*
|
/*
|
||||||
Nuclex Framework
|
Nuclex .NET Framework
|
||||||
Copyright (C) 2002-2017 Nuclex Development Labs
|
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
|
||||||
|
|
||||||
This library is free software; you can redistribute it and/or
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
modify it under the terms of the IBM Common Public License as
|
you may not use this file except in compliance with the License.
|
||||||
published by the IBM Corporation; either version 1.0 of the
|
You may obtain a copy of the License at
|
||||||
License, or (at your option) any later version.
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
This library is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
Unless required by applicable law or agreed to in writing, software
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
IBM Common Public License for more details.
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
You should have received a copy of the IBM Common Public
|
limitations under the License.
|
||||||
License along with this library
|
*/
|
||||||
*/
|
#endregion // Apache License 2.0
|
||||||
#endregion
|
|
||||||
|
using System;
|
||||||
using System;
|
using System.Collections;
|
||||||
using System.Collections;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Generic;
|
|
||||||
|
#if !NO_SETS
|
||||||
#if !NO_SETS
|
|
||||||
|
namespace Nuclex.Support.Collections {
|
||||||
namespace Nuclex.Support.Collections {
|
|
||||||
|
/// <summary>Wraps a set and prevents it from being modified</summary>
|
||||||
/// <summary>Wraps a set and prevents it from being modified</summary>
|
/// <typeparam name="TItem">Type of items to manage in the set</typeparam>
|
||||||
/// <typeparam name="TItem">Type of items to manage in the set</typeparam>
|
public class ReadOnlySet<TItem> : ISet<TItem>, ICollection<TItem> {
|
||||||
public class ReadOnlySet<TItem> : ISet<TItem>, ICollection<TItem> {
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Initializes a new observable set forwarding operations to the specified set
|
||||||
/// Initializes a new observable set forwarding operations to the specified set
|
/// </summary>
|
||||||
/// </summary>
|
/// <param name="set">Set operations will be forwarded to</param>
|
||||||
/// <param name="set">Set operations will be forwarded to</param>
|
public ReadOnlySet(ISet<TItem> set) {
|
||||||
public ReadOnlySet(ISet<TItem> set) {
|
this.set = set;
|
||||||
this.set = set;
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Determines whether the current set is a proper (strict) subset of a collection
|
||||||
/// Determines whether the current set is a proper (strict) subset of a collection
|
/// </summary>
|
||||||
/// </summary>
|
/// <param name="other">Collection against which the set will be tested</param>
|
||||||
/// <param name="other">Collection against which the set will be tested</param>
|
/// <returns>True if the set is a proper subset of the specified collection</returns>
|
||||||
/// <returns>True if the set is a proper subset of the specified collection</returns>
|
public bool IsProperSubsetOf(IEnumerable<TItem> other) {
|
||||||
public bool IsProperSubsetOf(IEnumerable<TItem> other) {
|
return this.set.IsProperSubsetOf(other);
|
||||||
return this.set.IsProperSubsetOf(other);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Determines whether the current set is a proper (strict) superset of a collection
|
||||||
/// Determines whether the current set is a proper (strict) superset of a collection
|
/// </summary>
|
||||||
/// </summary>
|
/// <param name="other">Collection against which the set will be tested</param>
|
||||||
/// <param name="other">Collection against which the set will be tested</param>
|
/// <returns>True if the set is a proper superset of the specified collection</returns>
|
||||||
/// <returns>True if the set is a proper superset of the specified collection</returns>
|
public bool IsProperSupersetOf(IEnumerable<TItem> other) {
|
||||||
public bool IsProperSupersetOf(IEnumerable<TItem> other) {
|
return this.set.IsProperSupersetOf(other);
|
||||||
return this.set.IsProperSupersetOf(other);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Determines whether the current set is a subset of a collection</summary>
|
||||||
/// <summary>Determines whether the current set is a subset of a collection</summary>
|
/// <param name="other">Collection against which the set will be tested</param>
|
||||||
/// <param name="other">Collection against which the set will be tested</param>
|
/// <returns>True if the set is a subset of the specified collection</returns>
|
||||||
/// <returns>True if the set is a subset of the specified collection</returns>
|
public bool IsSubsetOf(IEnumerable<TItem> other) {
|
||||||
public bool IsSubsetOf(IEnumerable<TItem> other) {
|
return this.set.IsSubsetOf(other);
|
||||||
return this.set.IsSubsetOf(other);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Determines whether the current set is a superset of a collection</summary>
|
||||||
/// <summary>Determines whether the current set is a superset of a collection</summary>
|
/// <param name="other">Collection against which the set will be tested</param>
|
||||||
/// <param name="other">Collection against which the set will be tested</param>
|
/// <returns>True if the set is a superset of the specified collection</returns>
|
||||||
/// <returns>True if the set is a superset of the specified collection</returns>
|
public bool IsSupersetOf(IEnumerable<TItem> other) {
|
||||||
public bool IsSupersetOf(IEnumerable<TItem> other) {
|
return this.set.IsSupersetOf(other);
|
||||||
return this.set.IsSupersetOf(other);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Determines if the set shares at least one common element with the collection
|
||||||
/// Determines if the set shares at least one common element with the collection
|
/// </summary>
|
||||||
/// </summary>
|
/// <param name="other">Collection the set will be tested against</param>
|
||||||
/// <param name="other">Collection the set will be tested against</param>
|
/// <returns>
|
||||||
/// <returns>
|
/// True if the set shares at least one common element with the collection
|
||||||
/// True if the set shares at least one common element with the collection
|
/// </returns>
|
||||||
/// </returns>
|
public bool Overlaps(IEnumerable<TItem> other) {
|
||||||
public bool Overlaps(IEnumerable<TItem> other) {
|
return this.set.Overlaps(other);
|
||||||
return this.set.Overlaps(other);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Determines whether the set contains the same elements as the specified collection
|
||||||
/// Determines whether the set contains the same elements as the specified collection
|
/// </summary>
|
||||||
/// </summary>
|
/// <param name="other">Collection the set will be tested against</param>
|
||||||
/// <param name="other">Collection the set will be tested against</param>
|
/// <returns>True if the set contains the same elements as the collection</returns>
|
||||||
/// <returns>True if the set contains the same elements as the collection</returns>
|
public bool SetEquals(IEnumerable<TItem> other) {
|
||||||
public bool SetEquals(IEnumerable<TItem> other) {
|
return this.set.SetEquals(other);
|
||||||
return this.set.SetEquals(other);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Determines whether the set contains the specified item</summary>
|
||||||
/// <summary>Determines whether the set contains the specified item</summary>
|
/// <param name="item">Item the set will be tested for</param>
|
||||||
/// <param name="item">Item the set will be tested for</param>
|
/// <returns>True if the set contains the specified item</returns>
|
||||||
/// <returns>True if the set contains the specified item</returns>
|
public bool Contains(TItem item) {
|
||||||
public bool Contains(TItem item) {
|
return this.set.Contains(item);
|
||||||
return this.set.Contains(item);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Copies the contents of the set into an array</summary>
|
||||||
/// <summary>Copies the contents of the set into an array</summary>
|
/// <param name="array">Array the set's contents will be copied to</param>
|
||||||
/// <param name="array">Array the set's contents will be copied to</param>
|
/// <param name="arrayIndex">
|
||||||
/// <param name="arrayIndex">
|
/// Index in the array the first copied element will be written to
|
||||||
/// Index in the array the first copied element will be written to
|
/// </param>
|
||||||
/// </param>
|
public void CopyTo(TItem[] array, int arrayIndex) {
|
||||||
public void CopyTo(TItem[] array, int arrayIndex) {
|
this.set.CopyTo(array, arrayIndex);
|
||||||
this.set.CopyTo(array, arrayIndex);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Counts the number of items contained in the set</summary>
|
||||||
/// <summary>Counts the number of items contained in the set</summary>
|
public int Count {
|
||||||
public int Count {
|
get { return this.set.Count; }
|
||||||
get { return this.set.Count; }
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Determines whether the set is readonly</summary>
|
||||||
/// <summary>Determines whether the set is readonly</summary>
|
public bool IsReadOnly {
|
||||||
public bool IsReadOnly {
|
get { return true; }
|
||||||
get { return true; }
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
/// <summary>Creates an enumerator for the set's contents</summary>
|
||||||
/// <summary>Creates an enumerator for the set's contents</summary>
|
/// <returns>A new enumerator for the sets contents</returns>
|
||||||
/// <returns>A new enumerator for the sets contents</returns>
|
public IEnumerator<TItem> GetEnumerator() {
|
||||||
public IEnumerator<TItem> GetEnumerator() {
|
return this.set.GetEnumerator();
|
||||||
return this.set.GetEnumerator();
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Creates an enumerator for the set's contents</summary>
|
||||||
/// <summary>Creates an enumerator for the set's contents</summary>
|
/// <returns>A new enumerator for the sets contents</returns>
|
||||||
/// <returns>A new enumerator for the sets contents</returns>
|
IEnumerator IEnumerable.GetEnumerator() {
|
||||||
IEnumerator IEnumerable.GetEnumerator() {
|
return this.set.GetEnumerator();
|
||||||
return this.set.GetEnumerator();
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Modifies the current set so that it contains only elements that are present either
|
||||||
/// Modifies the current set so that it contains only elements that are present either
|
/// in the current set or in the specified collection, but not both
|
||||||
/// in the current set or in the specified collection, but not both
|
/// </summary>
|
||||||
/// </summary>
|
/// <param name="other">Collection the set will be excepted with</param>
|
||||||
/// <param name="other">Collection the set will be excepted with</param>
|
void ISet<TItem>.SymmetricExceptWith(IEnumerable<TItem> other) {
|
||||||
void ISet<TItem>.SymmetricExceptWith(IEnumerable<TItem> other) {
|
throw new NotSupportedException(
|
||||||
throw new NotSupportedException(
|
"Excepting is not supported by the read-only set"
|
||||||
"Excepting is not supported by the read-only set"
|
);
|
||||||
);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Modifies the current set so that it contains all elements that are present in both
|
||||||
/// Modifies the current set so that it contains all elements that are present in both
|
/// the current set and in the specified collection
|
||||||
/// the current set and in the specified collection
|
/// </summary>
|
||||||
/// </summary>
|
/// <param name="other">Collection an union will be built with</param>
|
||||||
/// <param name="other">Collection an union will be built with</param>
|
void ISet<TItem>.UnionWith(IEnumerable<TItem> other) {
|
||||||
void ISet<TItem>.UnionWith(IEnumerable<TItem> other) {
|
throw new NotSupportedException(
|
||||||
throw new NotSupportedException(
|
"Unioning is not supported by the read-only set"
|
||||||
"Unioning is not supported by the read-only set"
|
);
|
||||||
);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Removes all items from the set</summary>
|
||||||
/// <summary>Removes all items from the set</summary>
|
public void Clear() {
|
||||||
public void Clear() {
|
throw new NotSupportedException(
|
||||||
throw new NotSupportedException(
|
"Clearing is not supported by the read-only set"
|
||||||
"Clearing is not supported by the read-only set"
|
);
|
||||||
);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Removes an item from the set</summary>
|
||||||
/// <summary>Removes an item from the set</summary>
|
/// <param name="item">Item that will be removed from the set</param>
|
||||||
/// <param name="item">Item that will be removed from the set</param>
|
/// <returns>
|
||||||
/// <returns>
|
/// True if the item was contained in the set and is now removed
|
||||||
/// True if the item was contained in the set and is now removed
|
/// </returns>
|
||||||
/// </returns>
|
bool ICollection<TItem>.Remove(TItem item) {
|
||||||
bool ICollection<TItem>.Remove(TItem item) {
|
throw new NotSupportedException(
|
||||||
throw new NotSupportedException(
|
"Removing items is not supported by the read-only set"
|
||||||
"Removing items is not supported by the read-only set"
|
);
|
||||||
);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Adds an item to the set</summary>
|
||||||
/// <summary>Adds an item to the set</summary>
|
/// <param name="item">Item that will be added to the set</param>
|
||||||
/// <param name="item">Item that will be added to the set</param>
|
/// <returns>
|
||||||
/// <returns>
|
/// True if the element was added, false if it was already contained in the set
|
||||||
/// True if the element was added, false if it was already contained in the set
|
/// </returns>
|
||||||
/// </returns>
|
bool ISet<TItem>.Add(TItem item) {
|
||||||
bool ISet<TItem>.Add(TItem item) {
|
throw new NotSupportedException(
|
||||||
throw new NotSupportedException(
|
"Adding items is not supported by the read-only set"
|
||||||
"Adding items is not supported by the read-only set"
|
);
|
||||||
);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Removes all elements that are contained in the collection</summary>
|
||||||
/// <summary>Removes all elements that are contained in the collection</summary>
|
/// <param name="other">Collection whose elements will be removed from this set</param>
|
||||||
/// <param name="other">Collection whose elements will be removed from this set</param>
|
void ISet<TItem>.ExceptWith(IEnumerable<TItem> other) {
|
||||||
void ISet<TItem>.ExceptWith(IEnumerable<TItem> other) {
|
throw new NotSupportedException(
|
||||||
throw new NotSupportedException(
|
"Excepting items is not supported by the read-only set"
|
||||||
"Excepting items is not supported by the read-only set"
|
);
|
||||||
);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Only keeps those elements in this set that are contained in the collection
|
||||||
/// Only keeps those elements in this set that are contained in the collection
|
/// </summary>
|
||||||
/// </summary>
|
/// <param name="other">Other set this set will be filtered by</param>
|
||||||
/// <param name="other">Other set this set will be filtered by</param>
|
void ISet<TItem>.IntersectWith(IEnumerable<TItem> other) {
|
||||||
void ISet<TItem>.IntersectWith(IEnumerable<TItem> other) {
|
throw new NotSupportedException(
|
||||||
throw new NotSupportedException(
|
"Intersecting items is not supported by the read-only set"
|
||||||
"Intersecting items is not supported by the read-only set"
|
);
|
||||||
);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Adds an item to the set</summary>
|
||||||
/// <summary>Adds an item to the set</summary>
|
/// <param name="item">Item that will be added to the set</param>
|
||||||
/// <param name="item">Item that will be added to the set</param>
|
void ICollection<TItem>.Add(TItem item) {
|
||||||
void ICollection<TItem>.Add(TItem item) {
|
throw new NotSupportedException(
|
||||||
throw new NotSupportedException(
|
"Adding is not supported by the read-only set"
|
||||||
"Adding is not supported by the read-only set"
|
);
|
||||||
);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>The set being wrapped</summary>
|
||||||
/// <summary>The set being wrapped</summary>
|
private ISet<TItem> set;
|
||||||
private ISet<TItem> set;
|
|
||||||
|
}
|
||||||
}
|
|
||||||
|
} // namespace Nuclex.Support.Collections
|
||||||
} // namespace Nuclex.Support.Collections
|
|
||||||
|
#endif // !NO_SETS
|
||||||
#endif // !NO_SETS
|
|
||||||
|
|
|
@ -1,117 +1,116 @@
|
||||||
#region CPL License
|
#region Apache License 2.0
|
||||||
/*
|
/*
|
||||||
Nuclex Framework
|
Nuclex .NET Framework
|
||||||
Copyright (C) 2002-2017 Nuclex Development Labs
|
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
|
||||||
|
|
||||||
This library is free software; you can redistribute it and/or
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
modify it under the terms of the IBM Common Public License as
|
you may not use this file except in compliance with the License.
|
||||||
published by the IBM Corporation; either version 1.0 of the
|
You may obtain a copy of the License at
|
||||||
License, or (at your option) any later version.
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
This library is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
Unless required by applicable law or agreed to in writing, software
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
IBM Common Public License for more details.
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
You should have received a copy of the IBM Common Public
|
limitations under the License.
|
||||||
License along with this library
|
*/
|
||||||
*/
|
#endregion // Apache License 2.0
|
||||||
#endregion
|
|
||||||
|
using System;
|
||||||
using System;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Generic;
|
|
||||||
|
#if UNITTEST
|
||||||
#if UNITTEST
|
|
||||||
|
using NUnit.Framework;
|
||||||
using NUnit.Framework;
|
|
||||||
|
namespace Nuclex.Support.Collections {
|
||||||
namespace Nuclex.Support.Collections {
|
|
||||||
|
/// <summary>Unit Test for the ReverseComparer helper class</summary>
|
||||||
/// <summary>Unit Test for the ReverseComparer helper class</summary>
|
[TestFixture]
|
||||||
[TestFixture]
|
internal class ReverseComparerTest {
|
||||||
internal class ReverseComparerTest {
|
|
||||||
|
#region class FortyTwoComparer
|
||||||
#region class FortyTwoComparer
|
|
||||||
|
/// <summary>Special comparer in which 42 is larger than everything</summary>
|
||||||
/// <summary>Special comparer in which 42 is larger than everything</summary>
|
private class FortyTwoComparer : IComparer<int> {
|
||||||
private class FortyTwoComparer : IComparer<int> {
|
|
||||||
|
/// <summary>Compares the left value to the right value</summary>
|
||||||
/// <summary>Compares the left value to the right value</summary>
|
/// <param name="left">Value on the left side</param>
|
||||||
/// <param name="left">Value on the left side</param>
|
/// <param name="right">Value on the right side</param>
|
||||||
/// <param name="right">Value on the right side</param>
|
/// <returns>The relationship of the two values</returns>
|
||||||
/// <returns>The relationship of the two values</returns>
|
public int Compare(int left, int right) {
|
||||||
public int Compare(int left, int right) {
|
|
||||||
|
// Is there a 42 in the arguments?
|
||||||
// Is there a 42 in the arguments?
|
if(left == 42) {
|
||||||
if(left == 42) {
|
if(right == 42) {
|
||||||
if(right == 42) {
|
return 0; // both are equal
|
||||||
return 0; // both are equal
|
} else {
|
||||||
} else {
|
return +1; // left is larger
|
||||||
return +1; // left is larger
|
}
|
||||||
}
|
} else if(right == 42) {
|
||||||
} else if(right == 42) {
|
return -1; // right is larger
|
||||||
return -1; // right is larger
|
}
|
||||||
}
|
|
||||||
|
// No 42 encountered, proceed as normal
|
||||||
// No 42 encountered, proceed as normal
|
return Math.Sign(left - right);
|
||||||
return Math.Sign(left - right);
|
|
||||||
|
}
|
||||||
}
|
|
||||||
|
}
|
||||||
}
|
|
||||||
|
#endregion // class FortyTwoComparer
|
||||||
#endregion // class FortyTwoComparer
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Tests whether the default constructor of the reverse comparer works
|
||||||
/// Tests whether the default constructor of the reverse comparer works
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void TestDefaultConstructor() {
|
||||||
public void TestDefaultConstructor() {
|
new ReverseComparer<int>();
|
||||||
new ReverseComparer<int>();
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Tests whether the full constructor of the reverse comparer works
|
||||||
/// Tests whether the full constructor of the reverse comparer works
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void TestFullConstructor() {
|
||||||
public void TestFullConstructor() {
|
new ReverseComparer<int>(new FortyTwoComparer());
|
||||||
new ReverseComparer<int>(new FortyTwoComparer());
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Tests whether the full constructor of the reverse comparer works
|
||||||
/// Tests whether the full constructor of the reverse comparer works
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void TestReversedDefaultComparer() {
|
||||||
public void TestReversedDefaultComparer() {
|
Comparer<int> comparer = Comparer<int>.Default;
|
||||||
Comparer<int> comparer = Comparer<int>.Default;
|
ReverseComparer<int> reverseComparer = new ReverseComparer<int>(comparer);
|
||||||
ReverseComparer<int> reverseComparer = new ReverseComparer<int>(comparer);
|
|
||||||
|
Assert.Greater(0, comparer.Compare(10, 20));
|
||||||
Assert.Greater(0, comparer.Compare(10, 20));
|
Assert.Less(0, comparer.Compare(20, 10));
|
||||||
Assert.Less(0, comparer.Compare(20, 10));
|
|
||||||
|
Assert.Less(0, reverseComparer.Compare(10, 20));
|
||||||
Assert.Less(0, reverseComparer.Compare(10, 20));
|
Assert.Greater(0, reverseComparer.Compare(20, 10));
|
||||||
Assert.Greater(0, reverseComparer.Compare(20, 10));
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Tests whether the full constructor of the reverse comparer works
|
||||||
/// Tests whether the full constructor of the reverse comparer works
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void TestReversedCustomComparer() {
|
||||||
public void TestReversedCustomComparer() {
|
FortyTwoComparer fortyTwoComparer = new FortyTwoComparer();
|
||||||
FortyTwoComparer fortyTwoComparer = new FortyTwoComparer();
|
ReverseComparer<int> reverseFortyTwoComparer = new ReverseComparer<int>(
|
||||||
ReverseComparer<int> reverseFortyTwoComparer = new ReverseComparer<int>(
|
fortyTwoComparer
|
||||||
fortyTwoComparer
|
);
|
||||||
);
|
|
||||||
|
Assert.Less(0, fortyTwoComparer.Compare(42, 84));
|
||||||
Assert.Less(0, fortyTwoComparer.Compare(42, 84));
|
Assert.Greater(0, fortyTwoComparer.Compare(84, 42));
|
||||||
Assert.Greater(0, fortyTwoComparer.Compare(84, 42));
|
|
||||||
|
Assert.Greater(0, reverseFortyTwoComparer.Compare(42, 84));
|
||||||
Assert.Greater(0, reverseFortyTwoComparer.Compare(42, 84));
|
Assert.Less(0, reverseFortyTwoComparer.Compare(84, 42));
|
||||||
Assert.Less(0, reverseFortyTwoComparer.Compare(84, 42));
|
}
|
||||||
}
|
|
||||||
|
}
|
||||||
}
|
|
||||||
|
} // namespace Nuclex.Support.Collections
|
||||||
} // namespace Nuclex.Support.Collections
|
|
||||||
|
#endif // UNITTEST
|
||||||
#endif // UNITTEST
|
|
||||||
|
|
|
@ -1,56 +1,55 @@
|
||||||
#region CPL License
|
#region Apache License 2.0
|
||||||
/*
|
/*
|
||||||
Nuclex Framework
|
Nuclex .NET Framework
|
||||||
Copyright (C) 2002-2017 Nuclex Development Labs
|
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
|
||||||
|
|
||||||
This library is free software; you can redistribute it and/or
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
modify it under the terms of the IBM Common Public License as
|
you may not use this file except in compliance with the License.
|
||||||
published by the IBM Corporation; either version 1.0 of the
|
You may obtain a copy of the License at
|
||||||
License, or (at your option) any later version.
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
This library is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
Unless required by applicable law or agreed to in writing, software
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
IBM Common Public License for more details.
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
You should have received a copy of the IBM Common Public
|
limitations under the License.
|
||||||
License along with this library
|
*/
|
||||||
*/
|
#endregion // Apache License 2.0
|
||||||
#endregion
|
|
||||||
|
using System;
|
||||||
using System;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Generic;
|
|
||||||
|
namespace Nuclex.Support.Collections {
|
||||||
namespace Nuclex.Support.Collections {
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Compares two values in reverse or reverses the output of another comparer
|
||||||
/// Compares two values in reverse or reverses the output of another comparer
|
/// </summary>
|
||||||
/// </summary>
|
/// <typeparam name="TCompared">Type of values to be compared</typeparam>
|
||||||
/// <typeparam name="TCompared">Type of values to be compared</typeparam>
|
public class ReverseComparer<TCompared> : IComparer<TCompared> {
|
||||||
public class ReverseComparer<TCompared> : IComparer<TCompared> {
|
|
||||||
|
/// <summary>Initializes a new reverse comparer</summary>
|
||||||
/// <summary>Initializes a new reverse comparer</summary>
|
public ReverseComparer() : this(Comparer<TCompared>.Default) { }
|
||||||
public ReverseComparer() : this(Comparer<TCompared>.Default) { }
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Initializes the comparer to provide the inverse results of another comparer
|
||||||
/// Initializes the comparer to provide the inverse results of another comparer
|
/// </summary>
|
||||||
/// </summary>
|
/// <param name="comparerToReverse">Comparer whose results will be inversed</param>
|
||||||
/// <param name="comparerToReverse">Comparer whose results will be inversed</param>
|
public ReverseComparer(IComparer<TCompared> comparerToReverse) {
|
||||||
public ReverseComparer(IComparer<TCompared> comparerToReverse) {
|
this.comparer = comparerToReverse;
|
||||||
this.comparer = comparerToReverse;
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Compares the left value to the right value</summary>
|
||||||
/// <summary>Compares the left value to the right value</summary>
|
/// <param name="left">Value on the left side</param>
|
||||||
/// <param name="left">Value on the left side</param>
|
/// <param name="right">Value on the right side</param>
|
||||||
/// <param name="right">Value on the right side</param>
|
/// <returns>The relationship of the two values</returns>
|
||||||
/// <returns>The relationship of the two values</returns>
|
public int Compare(TCompared left, TCompared right) {
|
||||||
public int Compare(TCompared left, TCompared right) {
|
return this.comparer.Compare(right, left); // intentionally reversed
|
||||||
return this.comparer.Compare(right, left); // intentionally reversed
|
}
|
||||||
}
|
|
||||||
|
/// <summary>The default comparer from the .NET framework</summary>
|
||||||
/// <summary>The default comparer from the .NET framework</summary>
|
private IComparer<TCompared> comparer;
|
||||||
private IComparer<TCompared> comparer;
|
|
||||||
|
}
|
||||||
}
|
|
||||||
|
} // namespace Nuclex.Support.Collections
|
||||||
} // namespace Nuclex.Support.Collections
|
|
||||||
|
|
|
@ -1,120 +1,119 @@
|
||||||
#region CPL License
|
#region Apache License 2.0
|
||||||
/*
|
/*
|
||||||
Nuclex Framework
|
Nuclex .NET Framework
|
||||||
Copyright (C) 2002-2017 Nuclex Development Labs
|
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
|
||||||
|
|
||||||
This library is free software; you can redistribute it and/or
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
modify it under the terms of the IBM Common Public License as
|
you may not use this file except in compliance with the License.
|
||||||
published by the IBM Corporation; either version 1.0 of the
|
You may obtain a copy of the License at
|
||||||
License, or (at your option) any later version.
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
This library is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
Unless required by applicable law or agreed to in writing, software
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
IBM Common Public License for more details.
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
You should have received a copy of the IBM Common Public
|
limitations under the License.
|
||||||
License along with this library
|
*/
|
||||||
*/
|
#endregion // Apache License 2.0
|
||||||
#endregion
|
|
||||||
|
using System;
|
||||||
using System;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Generic;
|
using System.ComponentModel;
|
||||||
using System.ComponentModel;
|
using System.Reflection;
|
||||||
using System.Reflection;
|
|
||||||
|
#if UNITTEST
|
||||||
#if UNITTEST
|
|
||||||
|
using NUnit.Framework;
|
||||||
using NUnit.Framework;
|
|
||||||
|
namespace Nuclex.Support.Collections {
|
||||||
namespace Nuclex.Support.Collections {
|
|
||||||
|
/// <summary>Unit Test for the SortableBindingList class</summary>
|
||||||
/// <summary>Unit Test for the SortableBindingList class</summary>
|
[TestFixture]
|
||||||
[TestFixture]
|
internal class SortableBindingListTest {
|
||||||
internal class SortableBindingListTest {
|
|
||||||
|
#region class TestRecord
|
||||||
#region class TestRecord
|
|
||||||
|
/// <summary>Dummy record used to test the sortable binding list</summary>
|
||||||
/// <summary>Dummy record used to test the sortable binding list</summary>
|
private class TestRecord {
|
||||||
private class TestRecord {
|
|
||||||
|
/// <summary>A property of type integer</summary>
|
||||||
/// <summary>A property of type integer</summary>
|
public int IntegerValue { get; set; }
|
||||||
public int IntegerValue { get; set; }
|
|
||||||
|
/// <summary>A property of type string</summary>
|
||||||
/// <summary>A property of type string</summary>
|
public string StringValue { get; set; }
|
||||||
public string StringValue { get; set; }
|
|
||||||
|
/// <summary>A property of type float</summary>
|
||||||
/// <summary>A property of type float</summary>
|
public float FloatValue { get; set; }
|
||||||
public float FloatValue { get; set; }
|
|
||||||
|
}
|
||||||
}
|
|
||||||
|
#endregion // class TestRecord
|
||||||
#endregion // class TestRecord
|
|
||||||
|
/// <summary>Verifies that the sortable binding list is default constructible</summary>
|
||||||
/// <summary>Verifies that the sortable binding list is default constructible</summary>
|
[Test]
|
||||||
[Test]
|
public void HasDefaultConstructor() {
|
||||||
public void HasDefaultConstructor() {
|
Assert.DoesNotThrow(
|
||||||
Assert.DoesNotThrow(
|
delegate () { new SortableBindingList<TestRecord>(); }
|
||||||
delegate () { new SortableBindingList<TestRecord>(); }
|
);
|
||||||
);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Tests whether the sortable binding list can copy an existing list
|
||||||
/// Tests whether the sortable binding list can copy an existing list
|
/// when being constructed
|
||||||
/// when being constructed
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void HasEnumerableConstructor() {
|
||||||
public void HasEnumerableConstructor() {
|
var items = new List<TestRecord>() {
|
||||||
var items = new List<TestRecord>() {
|
new TestRecord() { IntegerValue = 123 },
|
||||||
new TestRecord() { IntegerValue = 123 },
|
new TestRecord() { IntegerValue = 456 }
|
||||||
new TestRecord() { IntegerValue = 456 }
|
};
|
||||||
};
|
|
||||||
|
var testList = new SortableBindingList<TestRecord>(items);
|
||||||
var testList = new SortableBindingList<TestRecord>(items);
|
|
||||||
|
Assert.AreEqual(2, testList.Count);
|
||||||
Assert.AreEqual(2, testList.Count);
|
Assert.AreSame(items[0], testList[0]);
|
||||||
Assert.AreSame(items[0], testList[0]);
|
Assert.AreSame(items[1], testList[1]);
|
||||||
Assert.AreSame(items[1], testList[1]);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Verifies that the sortable binding list supports sorting</summary>
|
||||||
/// <summary>Verifies that the sortable binding list supports sorting</summary>
|
[Test]
|
||||||
[Test]
|
public void SupportsSorting() {
|
||||||
public void SupportsSorting() {
|
var testList = new SortableBindingList<TestRecord>();
|
||||||
var testList = new SortableBindingList<TestRecord>();
|
IBindingList testListAsBindingList = testList;
|
||||||
IBindingList testListAsBindingList = testList;
|
|
||||||
|
Assert.IsTrue(testListAsBindingList.SupportsSorting);
|
||||||
Assert.IsTrue(testListAsBindingList.SupportsSorting);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Tests whether the sortable binding list can sort its elements by different properties
|
||||||
/// Tests whether the sortable binding list can sort its elements by different properties
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void CanSortItems() {
|
||||||
public void CanSortItems() {
|
var items = new List<TestRecord>() {
|
||||||
var items = new List<TestRecord>() {
|
new TestRecord() { IntegerValue = 456 },
|
||||||
new TestRecord() { IntegerValue = 456 },
|
new TestRecord() { IntegerValue = 789 },
|
||||||
new TestRecord() { IntegerValue = 789 },
|
new TestRecord() { IntegerValue = 123 }
|
||||||
new TestRecord() { IntegerValue = 123 }
|
};
|
||||||
};
|
|
||||||
|
var testList = new SortableBindingList<TestRecord>(items);
|
||||||
var testList = new SortableBindingList<TestRecord>(items);
|
IBindingList testListAsBindingList = testList;
|
||||||
IBindingList testListAsBindingList = testList;
|
|
||||||
|
PropertyDescriptor integerValuePropertyDescriptor = (
|
||||||
PropertyDescriptor integerValuePropertyDescriptor = (
|
TypeDescriptor.GetProperties(typeof(TestRecord))[nameof(TestRecord.IntegerValue)]
|
||||||
TypeDescriptor.GetProperties(typeof(TestRecord))[nameof(TestRecord.IntegerValue)]
|
);
|
||||||
);
|
testListAsBindingList.ApplySort(
|
||||||
testListAsBindingList.ApplySort(
|
integerValuePropertyDescriptor, ListSortDirection.Ascending
|
||||||
integerValuePropertyDescriptor, ListSortDirection.Ascending
|
);
|
||||||
);
|
|
||||||
|
Assert.AreEqual(3, testList.Count);
|
||||||
Assert.AreEqual(3, testList.Count);
|
Assert.AreEqual(123, testList[0].IntegerValue);
|
||||||
Assert.AreEqual(123, testList[0].IntegerValue);
|
Assert.AreEqual(456, testList[1].IntegerValue);
|
||||||
Assert.AreEqual(456, testList[1].IntegerValue);
|
Assert.AreEqual(789, testList[2].IntegerValue);
|
||||||
Assert.AreEqual(789, testList[2].IntegerValue);
|
}
|
||||||
}
|
|
||||||
|
}
|
||||||
}
|
|
||||||
|
} // namespace Nuclex.Support.Collections
|
||||||
} // namespace Nuclex.Support.Collections
|
|
||||||
|
#endif // UNITTEST
|
||||||
#endif // UNITTEST
|
|
||||||
|
|
|
@ -1,223 +1,222 @@
|
||||||
#region CPL License
|
#region Apache License 2.0
|
||||||
/*
|
/*
|
||||||
Nuclex Framework
|
Nuclex .NET Framework
|
||||||
Copyright (C) 2002-2017 Nuclex Development Labs
|
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
|
||||||
|
|
||||||
This library is free software; you can redistribute it and/or
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
modify it under the terms of the IBM Common Public License as
|
you may not use this file except in compliance with the License.
|
||||||
published by the IBM Corporation; either version 1.0 of the
|
You may obtain a copy of the License at
|
||||||
License, or (at your option) any later version.
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
This library is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
Unless required by applicable law or agreed to in writing, software
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
IBM Common Public License for more details.
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
You should have received a copy of the IBM Common Public
|
limitations under the License.
|
||||||
License along with this library
|
*/
|
||||||
*/
|
#endregion // Apache License 2.0
|
||||||
#endregion
|
|
||||||
|
using System;
|
||||||
using System;
|
using System.Collections;
|
||||||
using System.Collections;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Generic;
|
using System.ComponentModel;
|
||||||
using System.ComponentModel;
|
using System.Reflection;
|
||||||
using System.Reflection;
|
|
||||||
|
namespace Nuclex.Support.Collections {
|
||||||
namespace Nuclex.Support.Collections {
|
|
||||||
|
/// <summary>Variant of BindingList that supports sorting</summary>
|
||||||
/// <summary>Variant of BindingList that supports sorting</summary>
|
/// <typeparam name="TElement">Type of items the binding list will contain</typeparam>
|
||||||
/// <typeparam name="TElement">Type of items the binding list will contain</typeparam>
|
public class SortableBindingList<TElement> : BindingList<TElement> {
|
||||||
public class SortableBindingList<TElement> : BindingList<TElement> {
|
|
||||||
|
#region class PropertyComparer
|
||||||
#region class PropertyComparer
|
|
||||||
|
/// <summary>Compares two elements based on a single preselected property</summary>
|
||||||
/// <summary>Compares two elements based on a single preselected property</summary>
|
private class PropertyComparer : IComparer<TElement> {
|
||||||
private class PropertyComparer : IComparer<TElement> {
|
|
||||||
|
/// <summary>Initializes a new property comparer for the specified property</summary>
|
||||||
/// <summary>Initializes a new property comparer for the specified property</summary>
|
/// <param name="property">Property based on which elements should be compared</param>
|
||||||
/// <param name="property">Property based on which elements should be compared</param>
|
/// <param name="direction">Direction in which elements should be sorted</param>
|
||||||
/// <param name="direction">Direction in which elements should be sorted</param>
|
public PropertyComparer(PropertyDescriptor property, ListSortDirection direction) {
|
||||||
public PropertyComparer(PropertyDescriptor property, ListSortDirection direction) {
|
this.propertyDescriptor = property;
|
||||||
this.propertyDescriptor = property;
|
|
||||||
|
Type comparerForPropertyType = typeof(Comparer<>).MakeGenericType(property.PropertyType);
|
||||||
Type comparerForPropertyType = typeof(Comparer<>).MakeGenericType(property.PropertyType);
|
this.comparer = (IComparer)comparerForPropertyType.InvokeMember(
|
||||||
this.comparer = (IComparer)comparerForPropertyType.InvokeMember(
|
"Default",
|
||||||
"Default",
|
BindingFlags.Static | BindingFlags.GetProperty | BindingFlags.Public,
|
||||||
BindingFlags.Static | BindingFlags.GetProperty | BindingFlags.Public,
|
null, // binder
|
||||||
null, // binder
|
null, // target (none since method is static)
|
||||||
null, // target (none since method is static)
|
null // argument array
|
||||||
null // argument array
|
);
|
||||||
);
|
|
||||||
|
SetListSortDirection(direction);
|
||||||
SetListSortDirection(direction);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Compares two elements based on the comparer's chosen property</summary>
|
||||||
/// <summary>Compares two elements based on the comparer's chosen property</summary>
|
/// <param name="first">First element for the comparison</param>
|
||||||
/// <param name="first">First element for the comparison</param>
|
/// <param name="second">Second element for the comparison</param>
|
||||||
/// <param name="second">Second element for the comparison</param>
|
/// <returns>The relationship of the two elements to each other</returns>
|
||||||
/// <returns>The relationship of the two elements to each other</returns>
|
public int Compare(TElement first, TElement second) {
|
||||||
public int Compare(TElement first, TElement second) {
|
return this.comparer.Compare(
|
||||||
return this.comparer.Compare(
|
this.propertyDescriptor.GetValue(first),
|
||||||
this.propertyDescriptor.GetValue(first),
|
this.propertyDescriptor.GetValue(second)
|
||||||
this.propertyDescriptor.GetValue(second)
|
) * this.reverse;
|
||||||
) * this.reverse;
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Selects the property based on which elements should be compared</summary>
|
||||||
/// <summary>Selects the property based on which elements should be compared</summary>
|
/// <param name="descriptor">Descriptor for the property to use for comparison</param>
|
||||||
/// <param name="descriptor">Descriptor for the property to use for comparison</param>
|
private void SetPropertyDescriptor(PropertyDescriptor descriptor) {
|
||||||
private void SetPropertyDescriptor(PropertyDescriptor descriptor) {
|
this.propertyDescriptor = descriptor;
|
||||||
this.propertyDescriptor = descriptor;
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Changes the sort direction</summary>
|
||||||
/// <summary>Changes the sort direction</summary>
|
/// <param name="direction">New sort direction</param>
|
||||||
/// <param name="direction">New sort direction</param>
|
private void SetListSortDirection(ListSortDirection direction) {
|
||||||
private void SetListSortDirection(ListSortDirection direction) {
|
this.reverse = direction == ListSortDirection.Ascending ? 1 : -1;
|
||||||
this.reverse = direction == ListSortDirection.Ascending ? 1 : -1;
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Updtes the sorted proeprty and the sort direction</summary>
|
||||||
/// <summary>Updtes the sorted proeprty and the sort direction</summary>
|
/// <param name="descriptor">Property based on which elements will be sorted</param>
|
||||||
/// <param name="descriptor">Property based on which elements will be sorted</param>
|
/// <param name="direction">Direction in which elements will be sorted</param>
|
||||||
/// <param name="direction">Direction in which elements will be sorted</param>
|
public void SetPropertyAndDirection(
|
||||||
public void SetPropertyAndDirection(
|
PropertyDescriptor descriptor, ListSortDirection direction
|
||||||
PropertyDescriptor descriptor, ListSortDirection direction
|
) {
|
||||||
) {
|
SetPropertyDescriptor(descriptor);
|
||||||
SetPropertyDescriptor(descriptor);
|
SetListSortDirection(direction);
|
||||||
SetListSortDirection(direction);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>The default comparer for the type of the chosen property</summary>
|
||||||
/// <summary>The default comparer for the type of the chosen property</summary>
|
private readonly IComparer comparer;
|
||||||
private readonly IComparer comparer;
|
/// <summary>Descriptor for the chosen property</summary>
|
||||||
/// <summary>Descriptor for the chosen property</summary>
|
private PropertyDescriptor propertyDescriptor;
|
||||||
private PropertyDescriptor propertyDescriptor;
|
/// <summary>
|
||||||
/// <summary>
|
/// Either positive or negative 1 to change the sign of the comparison result
|
||||||
/// Either positive or negative 1 to change the sign of the comparison result
|
/// </summary>
|
||||||
/// </summary>
|
private int reverse;
|
||||||
private int reverse;
|
|
||||||
|
}
|
||||||
}
|
|
||||||
|
#endregion // class PropertyComparer
|
||||||
#endregion // class PropertyComparer
|
|
||||||
|
/// <summary>Initializes a new BindingList with support for sorting</summary>
|
||||||
/// <summary>Initializes a new BindingList with support for sorting</summary>
|
public SortableBindingList() : base(new List<TElement>()) {
|
||||||
public SortableBindingList() : base(new List<TElement>()) {
|
this.comparers = new Dictionary<Type, PropertyComparer>();
|
||||||
this.comparers = new Dictionary<Type, PropertyComparer>();
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Initializes a sortable BindingList, copying the contents of an existing list
|
||||||
/// Initializes a sortable BindingList, copying the contents of an existing list
|
/// </summary>
|
||||||
/// </summary>
|
/// <param name="enumeration">Existing list whose contents will be shallo-wcopied</param>
|
||||||
/// <param name="enumeration">Existing list whose contents will be shallo-wcopied</param>
|
public SortableBindingList(IEnumerable<TElement> enumeration) :
|
||||||
public SortableBindingList(IEnumerable<TElement> enumeration) :
|
base(new List<TElement>(enumeration)) {
|
||||||
base(new List<TElement>(enumeration)) {
|
this.comparers = new Dictionary<Type, PropertyComparer>();
|
||||||
this.comparers = new Dictionary<Type, PropertyComparer>();
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Used by BindingList implementation to check whether sorting is supported
|
||||||
/// Used by BindingList implementation to check whether sorting is supported
|
/// </summary>
|
||||||
/// </summary>
|
protected override bool SupportsSortingCore {
|
||||||
protected override bool SupportsSortingCore {
|
get { return true; }
|
||||||
get { return true; }
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Used by BindingList implementation to check whether the list is currently sorted
|
||||||
/// Used by BindingList implementation to check whether the list is currently sorted
|
/// </summary>
|
||||||
/// </summary>
|
protected override bool IsSortedCore {
|
||||||
protected override bool IsSortedCore {
|
get { return this.isSorted; }
|
||||||
get { return this.isSorted; }
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Used by BindingList implementation to track the property the list is sorted by
|
||||||
/// Used by BindingList implementation to track the property the list is sorted by
|
/// </summary>
|
||||||
/// </summary>
|
protected override PropertyDescriptor SortPropertyCore {
|
||||||
protected override PropertyDescriptor SortPropertyCore {
|
get { return this.propertyDescriptor; }
|
||||||
get { return this.propertyDescriptor; }
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Used by BindingList implementation to track the direction in which the list is sortd
|
||||||
/// Used by BindingList implementation to track the direction in which the list is sortd
|
/// </summary>
|
||||||
/// </summary>
|
protected override ListSortDirection SortDirectionCore {
|
||||||
protected override ListSortDirection SortDirectionCore {
|
get { return this.listSortDirection; }
|
||||||
get { return this.listSortDirection; }
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Used by BindingList implementation to check whether the list supports searching
|
||||||
/// Used by BindingList implementation to check whether the list supports searching
|
/// </summary>
|
||||||
/// </summary>
|
protected override bool SupportsSearchingCore {
|
||||||
protected override bool SupportsSearchingCore {
|
get { return true; }
|
||||||
get { return true; }
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Used by BindingList implementation to sort the elements in the backing collection
|
||||||
/// Used by BindingList implementation to sort the elements in the backing collection
|
/// </summary>
|
||||||
/// </summary>
|
protected override void ApplySortCore(PropertyDescriptor property, ListSortDirection direction) {
|
||||||
protected override void ApplySortCore(PropertyDescriptor property, ListSortDirection direction) {
|
|
||||||
|
// Obtain a property comparer that sorts on the attributes the SortableBindingList
|
||||||
// Obtain a property comparer that sorts on the attributes the SortableBindingList
|
// has been configured for its sort order
|
||||||
// has been configured for its sort order
|
PropertyComparer comparer;
|
||||||
PropertyComparer comparer;
|
{
|
||||||
{
|
Type propertyType = property.PropertyType;
|
||||||
Type propertyType = property.PropertyType;
|
|
||||||
|
if(!this.comparers.TryGetValue(propertyType, out comparer)) {
|
||||||
if(!this.comparers.TryGetValue(propertyType, out comparer)) {
|
comparer = new PropertyComparer(property, direction);
|
||||||
comparer = new PropertyComparer(property, direction);
|
this.comparers.Add(propertyType, comparer);
|
||||||
this.comparers.Add(propertyType, comparer);
|
}
|
||||||
}
|
|
||||||
|
// Direction may need to be updated
|
||||||
// Direction may need to be updated
|
comparer.SetPropertyAndDirection(property, direction);
|
||||||
comparer.SetPropertyAndDirection(property, direction);
|
}
|
||||||
}
|
|
||||||
|
// Check to see if our base class is using a standard List<> in which case
|
||||||
// Check to see if our base class is using a standard List<> in which case
|
// we'll sneakily use the downcast to call the List<>.Sort() method, otherwise
|
||||||
// we'll sneakily use the downcast to call the List<>.Sort() method, otherwise
|
// there's still our own quicksort implementation for IList<>.
|
||||||
// there's still our own quicksort implementation for IList<>.
|
List<TElement> itemsAsList = this.Items as List<TElement>;
|
||||||
List<TElement> itemsAsList = this.Items as List<TElement>;
|
if(itemsAsList != null) {
|
||||||
if(itemsAsList != null) {
|
itemsAsList.Sort(comparer);
|
||||||
itemsAsList.Sort(comparer);
|
} else {
|
||||||
} else {
|
this.Items.QuickSort(0, this.Items.Count, comparer); // from IListExtensions
|
||||||
this.Items.QuickSort(0, this.Items.Count, comparer); // from IListExtensions
|
}
|
||||||
}
|
|
||||||
|
this.propertyDescriptor = property;
|
||||||
this.propertyDescriptor = property;
|
this.listSortDirection = direction;
|
||||||
this.listSortDirection = direction;
|
this.isSorted = true;
|
||||||
this.isSorted = true;
|
|
||||||
|
OnListChanged(new ListChangedEventArgs(ListChangedType.Reset, -1));
|
||||||
OnListChanged(new ListChangedEventArgs(ListChangedType.Reset, -1));
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Used by BindingList implementation to undo any sorting that took place</summary>
|
||||||
/// <summary>Used by BindingList implementation to undo any sorting that took place</summary>
|
protected override void RemoveSortCore() {
|
||||||
protected override void RemoveSortCore() {
|
this.isSorted = false;
|
||||||
this.isSorted = false;
|
this.propertyDescriptor = base.SortPropertyCore;
|
||||||
this.propertyDescriptor = base.SortPropertyCore;
|
this.listSortDirection = base.SortDirectionCore;
|
||||||
this.listSortDirection = base.SortDirectionCore;
|
|
||||||
|
OnListChanged(new ListChangedEventArgs(ListChangedType.Reset, -1));
|
||||||
OnListChanged(new ListChangedEventArgs(ListChangedType.Reset, -1));
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Used by BindingList implementation to run a search on any of the element's properties
|
||||||
/// Used by BindingList implementation to run a search on any of the element's properties
|
/// </summary>
|
||||||
/// </summary>
|
protected override int FindCore(PropertyDescriptor property, object key) {
|
||||||
protected override int FindCore(PropertyDescriptor property, object key) {
|
int count = this.Count;
|
||||||
int count = this.Count;
|
for(int index = 0; index < count; ++index) {
|
||||||
for(int index = 0; index < count; ++index) {
|
TElement element = this[index];
|
||||||
TElement element = this[index];
|
if(property.GetValue(element).Equals(key)) {
|
||||||
if(property.GetValue(element).Equals(key)) {
|
return index;
|
||||||
return index;
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
return -1;
|
||||||
return -1;
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Cached property comparers, created for each element property as needed</summary>
|
||||||
/// <summary>Cached property comparers, created for each element property as needed</summary>
|
private readonly Dictionary<Type, PropertyComparer> comparers;
|
||||||
private readonly Dictionary<Type, PropertyComparer> comparers;
|
/// <summary>Whether the binding list is currently sorted</summary>
|
||||||
/// <summary>Whether the binding list is currently sorted</summary>
|
private bool isSorted;
|
||||||
private bool isSorted;
|
/// <summary>Direction in which the binding list is currently sorted</summary>
|
||||||
/// <summary>Direction in which the binding list is currently sorted</summary>
|
private ListSortDirection listSortDirection;
|
||||||
private ListSortDirection listSortDirection;
|
/// <summary>Descriptor for the property by which the binding list is currently sorted</summary>
|
||||||
/// <summary>Descriptor for the property by which the binding list is currently sorted</summary>
|
private PropertyDescriptor propertyDescriptor;
|
||||||
private PropertyDescriptor propertyDescriptor;
|
|
||||||
|
}
|
||||||
}
|
|
||||||
|
} // namespace Nuclex.Support.Collections
|
||||||
} // namespace Nuclex.Support.Collections
|
|
||||||
|
|
|
@ -1,343 +1,342 @@
|
||||||
#region CPL License
|
#region Apache License 2.0
|
||||||
/*
|
/*
|
||||||
Nuclex Framework
|
Nuclex .NET Framework
|
||||||
Copyright (C) 2002-2017 Nuclex Development Labs
|
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
|
||||||
|
|
||||||
This library is free software; you can redistribute it and/or
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
modify it under the terms of the IBM Common Public License as
|
you may not use this file except in compliance with the License.
|
||||||
published by the IBM Corporation; either version 1.0 of the
|
You may obtain a copy of the License at
|
||||||
License, or (at your option) any later version.
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
This library is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
Unless required by applicable law or agreed to in writing, software
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
IBM Common Public License for more details.
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
You should have received a copy of the IBM Common Public
|
limitations under the License.
|
||||||
License along with this library
|
*/
|
||||||
*/
|
#endregion // Apache License 2.0
|
||||||
#endregion
|
|
||||||
|
using System;
|
||||||
using System;
|
using System.Threading;
|
||||||
using System.Threading;
|
using System.Collections;
|
||||||
using System.Collections;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Generic;
|
|
||||||
|
namespace Nuclex.Support.Collections {
|
||||||
namespace Nuclex.Support.Collections {
|
|
||||||
|
// More than 400 lines of code just to implement the .NET collection interfaces.
|
||||||
// More than 400 lines of code just to implement the .NET collection interfaces.
|
// Shows the niceties of having to support languages without generics and using
|
||||||
// Shows the niceties of having to support languages without generics and using
|
// an inferior design to make collections "more convenient" for the user :/
|
||||||
// an inferior design to make collections "more convenient" for the user :/
|
|
||||||
|
partial class TransformingReadOnlyCollection<TContainedItem, TExposedItem> {
|
||||||
partial class TransformingReadOnlyCollection<TContainedItem, TExposedItem> {
|
|
||||||
|
#region IList<TExposedItem> Members
|
||||||
#region IList<TExposedItem> Members
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Inserts an item to the TransformingReadOnlyCollection at the specified index.
|
||||||
/// Inserts an item to the TransformingReadOnlyCollection at the specified index.
|
/// </summary>
|
||||||
/// </summary>
|
/// <param name="index">
|
||||||
/// <param name="index">
|
/// The zero-based index at which item should be inserted.
|
||||||
/// The zero-based index at which item should be inserted.
|
/// </param>
|
||||||
/// </param>
|
/// <param name="item">
|
||||||
/// <param name="item">
|
/// The object to insert into the TransformingReadOnlyCollection
|
||||||
/// The object to insert into the TransformingReadOnlyCollection
|
/// </param>
|
||||||
/// </param>
|
/// <exception cref="System.NotSupportedException">
|
||||||
/// <exception cref="System.NotSupportedException">
|
/// The TransformingReadOnlyCollection is read-only.
|
||||||
/// The TransformingReadOnlyCollection is read-only.
|
/// </exception>
|
||||||
/// </exception>
|
/// <exception cref="System.ArgumentOutOfRangeException">
|
||||||
/// <exception cref="System.ArgumentOutOfRangeException">
|
/// index is not a valid index in the TransformingReadOnlyCollection.
|
||||||
/// index is not a valid index in the TransformingReadOnlyCollection.
|
/// </exception>
|
||||||
/// </exception>
|
void IList<TExposedItem>.Insert(int index, TExposedItem item) {
|
||||||
void IList<TExposedItem>.Insert(int index, TExposedItem item) {
|
throw new NotSupportedException("The collection is ready-only");
|
||||||
throw new NotSupportedException("The collection is ready-only");
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Removes the TransformingReadOnlyCollection item at the specified index.
|
||||||
/// Removes the TransformingReadOnlyCollection item at the specified index.
|
/// </summary>
|
||||||
/// </summary>
|
/// <param name="index">The zero-based index of the item to remove.</param>
|
||||||
/// <param name="index">The zero-based index of the item to remove.</param>
|
/// <exception cref="System.NotSupportedException">
|
||||||
/// <exception cref="System.NotSupportedException">
|
/// The TransformingReadOnlyCollection is read-only.
|
||||||
/// The TransformingReadOnlyCollection is read-only.
|
/// </exception>
|
||||||
/// </exception>
|
/// <exception cref="System.ArgumentOutOfRangeException">
|
||||||
/// <exception cref="System.ArgumentOutOfRangeException">
|
/// Index is not a valid index in the TransformingReadOnlyCollection.
|
||||||
/// Index is not a valid index in the TransformingReadOnlyCollection.
|
/// </exception>
|
||||||
/// </exception>
|
void IList<TExposedItem>.RemoveAt(int index) {
|
||||||
void IList<TExposedItem>.RemoveAt(int index) {
|
throw new NotSupportedException("The collection is ready-only");
|
||||||
throw new NotSupportedException("The collection is ready-only");
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Gets or sets the element at the specified index.</summary>
|
||||||
/// <summary>Gets or sets the element at the specified index.</summary>
|
/// <param name="index">The zero-based index of the element to get or set.</param>
|
||||||
/// <param name="index">The zero-based index of the element to get or set.</param>
|
/// <returns>The element at the specified index.</returns>
|
||||||
/// <returns>The element at the specified index.</returns>
|
/// <exception cref="System.ArgumentOutOfRangeException">
|
||||||
/// <exception cref="System.ArgumentOutOfRangeException">
|
/// Index is not a valid index in the TransformingReadOnlyCollection.
|
||||||
/// Index is not a valid index in the TransformingReadOnlyCollection.
|
/// </exception>
|
||||||
/// </exception>
|
/// <exception cref="System.NotSupportedException">
|
||||||
/// <exception cref="System.NotSupportedException">
|
/// The property is set and the TransformingReadOnlyCollection is read-only
|
||||||
/// The property is set and the TransformingReadOnlyCollection is read-only
|
/// </exception>
|
||||||
/// </exception>
|
TExposedItem IList<TExposedItem>.this[int index] {
|
||||||
TExposedItem IList<TExposedItem>.this[int index] {
|
get {
|
||||||
get {
|
return this[index];
|
||||||
return this[index];
|
}
|
||||||
}
|
set {
|
||||||
set {
|
throw new NotSupportedException("The collection is ready-only");
|
||||||
throw new NotSupportedException("The collection is ready-only");
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
#endregion
|
||||||
#endregion
|
|
||||||
|
#region ICollection<TExposedItem> Members
|
||||||
#region ICollection<TExposedItem> Members
|
|
||||||
|
/// <summary>Adds an item to the TransformingReadOnlyCollection.</summary>
|
||||||
/// <summary>Adds an item to the TransformingReadOnlyCollection.</summary>
|
/// <param name="item">The object to add to the TransformingReadOnlyCollection</param>
|
||||||
/// <param name="item">The object to add to the TransformingReadOnlyCollection</param>
|
/// <exception cref="System.NotSupportedException">
|
||||||
/// <exception cref="System.NotSupportedException">
|
/// The TransformingReadOnlyCollection is read-only.
|
||||||
/// The TransformingReadOnlyCollection is read-only.
|
/// </exception>
|
||||||
/// </exception>
|
void ICollection<TExposedItem>.Add(TExposedItem item) {
|
||||||
void ICollection<TExposedItem>.Add(TExposedItem item) {
|
throw new NotSupportedException("The collection is ready-only");
|
||||||
throw new NotSupportedException("The collection is ready-only");
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Removes all items from the TransformingReadOnlyCollection</summary>
|
||||||
/// <summary>Removes all items from the TransformingReadOnlyCollection</summary>
|
/// <exception cref="System.NotSupportedException">
|
||||||
/// <exception cref="System.NotSupportedException">
|
/// The TransformingReadOnlyCollection is read-only.
|
||||||
/// The TransformingReadOnlyCollection is read-only.
|
/// </exception>
|
||||||
/// </exception>
|
void ICollection<TExposedItem>.Clear() {
|
||||||
void ICollection<TExposedItem>.Clear() {
|
throw new NotSupportedException("The collection is ready-only");
|
||||||
throw new NotSupportedException("The collection is ready-only");
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Removes the first occurrence of a specific object from the
|
||||||
/// Removes the first occurrence of a specific object from the
|
/// TransformingReadOnlyCollection.
|
||||||
/// TransformingReadOnlyCollection.
|
/// </summary>
|
||||||
/// </summary>
|
/// <param name="item">
|
||||||
/// <param name="item">
|
/// The object to remove from the TransformingReadOnlyCollection
|
||||||
/// The object to remove from the TransformingReadOnlyCollection
|
/// </param>
|
||||||
/// </param>
|
/// <returns>
|
||||||
/// <returns>
|
/// True if item was successfully removed from the TransformingReadOnlyCollection;
|
||||||
/// True if item was successfully removed from the TransformingReadOnlyCollection;
|
/// otherwise, false. This method also returns false if item is not found in the
|
||||||
/// otherwise, false. This method also returns false if item is not found in the
|
/// original TransformingReadOnlyCollection.
|
||||||
/// original TransformingReadOnlyCollection.
|
/// </returns>
|
||||||
/// </returns>
|
/// <exception cref="System.NotSupportedException">
|
||||||
/// <exception cref="System.NotSupportedException">
|
/// The TransformingReadOnlyCollection is read-only.
|
||||||
/// The TransformingReadOnlyCollection is read-only.
|
/// </exception>
|
||||||
/// </exception>
|
bool ICollection<TExposedItem>.Remove(TExposedItem item) {
|
||||||
bool ICollection<TExposedItem>.Remove(TExposedItem item) {
|
throw new NotSupportedException("The collection is ready-only");
|
||||||
throw new NotSupportedException("The collection is ready-only");
|
}
|
||||||
}
|
|
||||||
|
#endregion
|
||||||
#endregion
|
|
||||||
|
#region IEnumerable Members
|
||||||
#region IEnumerable Members
|
|
||||||
|
/// <summary>Returns an enumerator that iterates through a collection.</summary>
|
||||||
/// <summary>Returns an enumerator that iterates through a collection.</summary>
|
/// <returns>
|
||||||
/// <returns>
|
/// A System.Collections.IEnumerator object that can be used to iterate through
|
||||||
/// A System.Collections.IEnumerator object that can be used to iterate through
|
/// the collection.
|
||||||
/// the collection.
|
/// </returns>
|
||||||
/// </returns>
|
IEnumerator IEnumerable.GetEnumerator() {
|
||||||
IEnumerator IEnumerable.GetEnumerator() {
|
return GetEnumerator();
|
||||||
return GetEnumerator();
|
}
|
||||||
}
|
|
||||||
|
#endregion
|
||||||
#endregion
|
|
||||||
|
#region IList Members
|
||||||
#region IList Members
|
|
||||||
|
/// <summary>Adds an item to the TransformingReadOnlyCollection.</summary>
|
||||||
/// <summary>Adds an item to the TransformingReadOnlyCollection.</summary>
|
/// <param name="value">
|
||||||
/// <param name="value">
|
/// The System.Object to add to the TransformingReadOnlyCollection.
|
||||||
/// The System.Object to add to the TransformingReadOnlyCollection.
|
/// </param>
|
||||||
/// </param>
|
/// <returns>The position into which the new element was inserted.</returns>
|
||||||
/// <returns>The position into which the new element was inserted.</returns>
|
/// <exception cref="System.NotSupportedException">
|
||||||
/// <exception cref="System.NotSupportedException">
|
/// The System.Collections.IList is read-only or the TransformingReadOnlyCollection
|
||||||
/// The System.Collections.IList is read-only or the TransformingReadOnlyCollection
|
/// has a fixed size.
|
||||||
/// has a fixed size.
|
/// </exception>
|
||||||
/// </exception>
|
int IList.Add(object value) {
|
||||||
int IList.Add(object value) {
|
throw new NotSupportedException("The collection is ready-only");
|
||||||
throw new NotSupportedException("The collection is ready-only");
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Removes all items from the TransformingReadOnlyCollection.</summary>
|
||||||
/// <summary>Removes all items from the TransformingReadOnlyCollection.</summary>
|
/// <exception cref="System.NotSupportedException">
|
||||||
/// <exception cref="System.NotSupportedException">
|
/// The TransformingReadOnlyCollection is read-only.
|
||||||
/// The TransformingReadOnlyCollection is read-only.
|
/// </exception>
|
||||||
/// </exception>
|
void IList.Clear() {
|
||||||
void IList.Clear() {
|
throw new NotSupportedException("The collection is ready-only");
|
||||||
throw new NotSupportedException("The collection is ready-only");
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Determines whether the TransformingReadOnlyCollection contains a specific value.
|
||||||
/// Determines whether the TransformingReadOnlyCollection contains a specific value.
|
/// </summary>
|
||||||
/// </summary>
|
/// <param name="value">
|
||||||
/// <param name="value">
|
/// The System.Object to locate in the TransformingReadOnlyCollection.
|
||||||
/// The System.Object to locate in the TransformingReadOnlyCollection.
|
/// </param>
|
||||||
/// </param>
|
/// <returns>
|
||||||
/// <returns>
|
/// True if the System.Object is found in the TransformingReadOnlyCollection;
|
||||||
/// True if the System.Object is found in the TransformingReadOnlyCollection;
|
/// otherwise, false.
|
||||||
/// otherwise, false.
|
/// </returns>
|
||||||
/// </returns>
|
bool IList.Contains(object value) {
|
||||||
bool IList.Contains(object value) {
|
return Contains((TExposedItem)value);
|
||||||
return Contains((TExposedItem)value);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Determines the index of a specific item in the TransformingReadOnlyCollection.
|
||||||
/// Determines the index of a specific item in the TransformingReadOnlyCollection.
|
/// </summary>
|
||||||
/// </summary>
|
/// <param name="value">
|
||||||
/// <param name="value">
|
/// The System.Object to locate in the TransformingReadOnlyCollection.
|
||||||
/// The System.Object to locate in the TransformingReadOnlyCollection.
|
/// </param>
|
||||||
/// </param>
|
/// <returns>
|
||||||
/// <returns>
|
/// The index of value if found in the list; otherwise, -1.
|
||||||
/// The index of value if found in the list; otherwise, -1.
|
/// </returns>
|
||||||
/// </returns>
|
int IList.IndexOf(object value) {
|
||||||
int IList.IndexOf(object value) {
|
return IndexOf((TExposedItem)value);
|
||||||
return IndexOf((TExposedItem)value);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Inserts an item to the TransformingReadOnlyCollection at the specified index.
|
||||||
/// Inserts an item to the TransformingReadOnlyCollection at the specified index.
|
/// </summary>
|
||||||
/// </summary>
|
/// <param name="index">
|
||||||
/// <param name="index">
|
/// The zero-based index at which value should be inserted.
|
||||||
/// The zero-based index at which value should be inserted.
|
/// </param>
|
||||||
/// </param>
|
/// <param name="value">
|
||||||
/// <param name="value">
|
/// The System.Object to insert into the TransformingReadOnlyCollection.
|
||||||
/// The System.Object to insert into the TransformingReadOnlyCollection.
|
/// </param>
|
||||||
/// </param>
|
/// <exception cref="System.ArgumentOutOfRangeException">
|
||||||
/// <exception cref="System.ArgumentOutOfRangeException">
|
/// Index is not a valid index in the TransformingReadOnlyCollection.
|
||||||
/// Index is not a valid index in the TransformingReadOnlyCollection.
|
/// </exception>
|
||||||
/// </exception>
|
/// <exception cref="System.NotSupportedException">
|
||||||
/// <exception cref="System.NotSupportedException">
|
/// The System.Collections.IList is read-only or the TransformingReadOnlyCollection
|
||||||
/// The System.Collections.IList is read-only or the TransformingReadOnlyCollection
|
/// has a fixed size.
|
||||||
/// has a fixed size.
|
/// </exception>
|
||||||
/// </exception>
|
/// <exception cref="System.NullReferenceException">
|
||||||
/// <exception cref="System.NullReferenceException">
|
/// Value is null reference in the TransformingReadOnlyCollection.
|
||||||
/// Value is null reference in the TransformingReadOnlyCollection.
|
/// </exception>
|
||||||
/// </exception>
|
void IList.Insert(int index, object value) {
|
||||||
void IList.Insert(int index, object value) {
|
throw new NotSupportedException("The collection is ready-only");
|
||||||
throw new NotSupportedException("The collection is ready-only");
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// A value indicating whether the TransformingReadOnlyCollection has a fixed
|
||||||
/// A value indicating whether the TransformingReadOnlyCollection has a fixed
|
/// size.
|
||||||
/// size.
|
/// </summary>
|
||||||
/// </summary>
|
bool IList.IsFixedSize {
|
||||||
bool IList.IsFixedSize {
|
get { return true; }
|
||||||
get { return true; }
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Removes the first occurrence of a specific object from the
|
||||||
/// Removes the first occurrence of a specific object from the
|
/// TransformingReadOnlyCollection.
|
||||||
/// TransformingReadOnlyCollection.
|
/// </summary>
|
||||||
/// </summary>
|
/// <param name="value">
|
||||||
/// <param name="value">
|
/// The System.Object to remove from the TransformingReadOnlyCollection.
|
||||||
/// The System.Object to remove from the TransformingReadOnlyCollection.
|
/// </param>
|
||||||
/// </param>
|
/// <exception cref="System.NotSupportedException">
|
||||||
/// <exception cref="System.NotSupportedException">
|
/// The TransformingReadOnlyCollection is read-only or the
|
||||||
/// The TransformingReadOnlyCollection is read-only or the
|
/// TransformingReadOnlyCollection has a fixed size.
|
||||||
/// TransformingReadOnlyCollection has a fixed size.
|
/// </exception>
|
||||||
/// </exception>
|
void IList.Remove(object value) {
|
||||||
void IList.Remove(object value) {
|
throw new NotSupportedException("The collection is ready-only");
|
||||||
throw new NotSupportedException("The collection is ready-only");
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Removes the TransformingReadOnlyCollection item at the specified index.
|
||||||
/// Removes the TransformingReadOnlyCollection item at the specified index.
|
/// </summary>
|
||||||
/// </summary>
|
/// <param name="index">The zero-based index of the item to remove.</param>
|
||||||
/// <param name="index">The zero-based index of the item to remove.</param>
|
/// <exception cref="System.ArgumentOutOfRangeException">
|
||||||
/// <exception cref="System.ArgumentOutOfRangeException">
|
/// Index is not a valid index in the TransformingReadOnlyCollection.
|
||||||
/// Index is not a valid index in the TransformingReadOnlyCollection.
|
/// </exception>
|
||||||
/// </exception>
|
/// <exception cref="System.NotSupportedException">
|
||||||
/// <exception cref="System.NotSupportedException">
|
/// The TransformingReadOnlyCollection is read-only or the
|
||||||
/// The TransformingReadOnlyCollection is read-only or the
|
/// TransformingReadOnlyCollection has a fixed size.
|
||||||
/// TransformingReadOnlyCollection has a fixed size.
|
/// </exception>
|
||||||
/// </exception>
|
void IList.RemoveAt(int index) {
|
||||||
void IList.RemoveAt(int index) {
|
throw new NotSupportedException("The collection is ready-only");
|
||||||
throw new NotSupportedException("The collection is ready-only");
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Gets or sets the element at the specified index.</summary>
|
||||||
/// <summary>Gets or sets the element at the specified index.</summary>
|
/// <param name="index">The zero-based index of the element to get or set.</param>
|
||||||
/// <param name="index">The zero-based index of the element to get or set.</param>
|
/// <returns>The element at the specified index</returns>
|
||||||
/// <returns>The element at the specified index</returns>
|
/// <exception cref="System.ArgumentOutOfRangeException">
|
||||||
/// <exception cref="System.ArgumentOutOfRangeException">
|
/// Index is not a valid index in the TransformingReadOnlyCollection
|
||||||
/// Index is not a valid index in the TransformingReadOnlyCollection
|
/// </exception>
|
||||||
/// </exception>
|
/// <exception cref="System.NotSupportedException">
|
||||||
/// <exception cref="System.NotSupportedException">
|
/// The property is set and the TransformingReadOnlyCollection is read-only.
|
||||||
/// The property is set and the TransformingReadOnlyCollection is read-only.
|
/// </exception>
|
||||||
/// </exception>
|
object IList.this[int index] {
|
||||||
object IList.this[int index] {
|
get {
|
||||||
get {
|
return this[index];
|
||||||
return this[index];
|
}
|
||||||
}
|
set {
|
||||||
set {
|
throw new NotSupportedException("The collection is ready-only");
|
||||||
throw new NotSupportedException("The collection is ready-only");
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
#endregion
|
||||||
#endregion
|
|
||||||
|
#region ICollection Members
|
||||||
#region ICollection Members
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Copies the elements of the TransformingReadOnlyCollection to an System.Array,
|
||||||
/// Copies the elements of the TransformingReadOnlyCollection to an System.Array,
|
/// starting at a particular System.Array index.
|
||||||
/// starting at a particular System.Array index.
|
/// </summary>
|
||||||
/// </summary>
|
/// <param name="array">
|
||||||
/// <param name="array">
|
/// The one-dimensional System.Array that is the destination of the elements
|
||||||
/// The one-dimensional System.Array that is the destination of the elements
|
/// copied from TransformingReadOnlyCollection. The System.Array must have zero-based
|
||||||
/// copied from TransformingReadOnlyCollection. The System.Array must have zero-based
|
/// indexing.
|
||||||
/// indexing.
|
/// </param>
|
||||||
/// </param>
|
/// <param name="index">The zero-based index in array at which copying begins.</param>
|
||||||
/// <param name="index">The zero-based index in array at which copying begins.</param>
|
/// <exception cref="System.ArgumentNullException">
|
||||||
/// <exception cref="System.ArgumentNullException">
|
/// Array is null.
|
||||||
/// Array is null.
|
/// </exception>
|
||||||
/// </exception>
|
/// <exception cref="System.ArgumentOutOfRangeException">
|
||||||
/// <exception cref="System.ArgumentOutOfRangeException">
|
/// Index is less than zero.
|
||||||
/// Index is less than zero.
|
/// </exception>
|
||||||
/// </exception>
|
/// <exception cref="System.ArgumentException">
|
||||||
/// <exception cref="System.ArgumentException">
|
/// Array is multidimensional or index is equal to or greater than the length
|
||||||
/// Array is multidimensional or index is equal to or greater than the length
|
/// of array or the number of elements in the source TransformingReadOnlyCollection
|
||||||
/// of array or the number of elements in the source TransformingReadOnlyCollection
|
/// is greater than the available space from index to the end of the destination
|
||||||
/// is greater than the available space from index to the end of the destination
|
/// array.
|
||||||
/// array.
|
/// </exception>
|
||||||
/// </exception>
|
/// <exception cref="System.InvalidCastException">
|
||||||
/// <exception cref="System.InvalidCastException">
|
/// The type of the source TransformingReadOnlyCollection cannot be cast
|
||||||
/// The type of the source TransformingReadOnlyCollection cannot be cast
|
/// automatically to the type of the destination array.
|
||||||
/// automatically to the type of the destination array.
|
/// </exception>
|
||||||
/// </exception>
|
void ICollection.CopyTo(Array array, int index) {
|
||||||
void ICollection.CopyTo(Array array, int index) {
|
CopyTo((TExposedItem[])array, index);
|
||||||
CopyTo((TExposedItem[])array, index);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// The number of elements contained in the TransformingReadOnlyCollection.
|
||||||
/// The number of elements contained in the TransformingReadOnlyCollection.
|
/// </summary>
|
||||||
/// </summary>
|
int ICollection.Count {
|
||||||
int ICollection.Count {
|
get { return Count; }
|
||||||
get { return Count; }
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// A value indicating whether access to the TransformingReadOnlyCollection
|
||||||
/// A value indicating whether access to the TransformingReadOnlyCollection
|
/// is synchronized (thread safe).
|
||||||
/// is synchronized (thread safe).
|
/// </summary>
|
||||||
/// </summary>
|
bool ICollection.IsSynchronized {
|
||||||
bool ICollection.IsSynchronized {
|
get { return false; }
|
||||||
get { return false; }
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// An object that can be used to synchronize access to the
|
||||||
/// An object that can be used to synchronize access to the
|
/// TransformingReadOnlyCollection.
|
||||||
/// TransformingReadOnlyCollection.
|
/// </summary>
|
||||||
/// </summary>
|
object ICollection.SyncRoot {
|
||||||
object ICollection.SyncRoot {
|
get {
|
||||||
get {
|
if(this.syncRoot == null) {
|
||||||
if(this.syncRoot == null) {
|
ICollection is2 = this.items as ICollection;
|
||||||
ICollection is2 = this.items as ICollection;
|
if(is2 != null) {
|
||||||
if(is2 != null) {
|
this.syncRoot = is2.SyncRoot;
|
||||||
this.syncRoot = is2.SyncRoot;
|
} else {
|
||||||
} else {
|
Interlocked.CompareExchange(ref this.syncRoot, new object(), null);
|
||||||
Interlocked.CompareExchange(ref this.syncRoot, new object(), null);
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
return this.syncRoot;
|
||||||
return this.syncRoot;
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
#endregion
|
||||||
#endregion
|
|
||||||
|
}
|
||||||
}
|
|
||||||
|
} // namespace Nuclex.Support.Collections
|
||||||
} // namespace Nuclex.Support.Collections
|
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,289 +1,288 @@
|
||||||
#region CPL License
|
#region Apache License 2.0
|
||||||
/*
|
/*
|
||||||
Nuclex Framework
|
Nuclex .NET Framework
|
||||||
Copyright (C) 2002-2017 Nuclex Development Labs
|
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
|
||||||
|
|
||||||
This library is free software; you can redistribute it and/or
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
modify it under the terms of the IBM Common Public License as
|
you may not use this file except in compliance with the License.
|
||||||
published by the IBM Corporation; either version 1.0 of the
|
You may obtain a copy of the License at
|
||||||
License, or (at your option) any later version.
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
This library is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
Unless required by applicable law or agreed to in writing, software
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
IBM Common Public License for more details.
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
You should have received a copy of the IBM Common Public
|
limitations under the License.
|
||||||
License along with this library
|
*/
|
||||||
*/
|
#endregion // Apache License 2.0
|
||||||
#endregion
|
|
||||||
|
using System;
|
||||||
using System;
|
using System.Collections;
|
||||||
using System.Collections;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Generic;
|
|
||||||
|
namespace Nuclex.Support.Collections {
|
||||||
namespace Nuclex.Support.Collections {
|
|
||||||
|
/// <summary>Collection that transforms the contents of another collection.</summary>
|
||||||
/// <summary>Collection that transforms the contents of another collection.</summary>
|
/// <typeparam name="TContainedItem">
|
||||||
/// <typeparam name="TContainedItem">
|
/// Type of the items contained in the wrapped collection.
|
||||||
/// Type of the items contained in the wrapped collection.
|
/// </typeparam>
|
||||||
/// </typeparam>
|
/// <typeparam name="TExposedItem">
|
||||||
/// <typeparam name="TExposedItem">
|
/// Type this collection exposes its items as.
|
||||||
/// Type this collection exposes its items as.
|
/// </typeparam>
|
||||||
/// </typeparam>
|
/// <remarks>
|
||||||
/// <remarks>
|
/// <para>
|
||||||
/// <para>
|
/// This collection is useful if you want to expose the objects of an arbitrary
|
||||||
/// This collection is useful if you want to expose the objects of an arbitrary
|
/// collection under a different type. It can be used, for example, to construct
|
||||||
/// collection under a different type. It can be used, for example, to construct
|
/// wrappers for the items in a collection on-the-fly, eliminating the need to
|
||||||
/// wrappers for the items in a collection on-the-fly, eliminating the need to
|
/// manage the wrappers in parallel to the real items and improving performance
|
||||||
/// manage the wrappers in parallel to the real items and improving performance
|
/// by only constructing a wrapper when an item is actually requested.
|
||||||
/// by only constructing a wrapper when an item is actually requested.
|
/// </para>
|
||||||
/// </para>
|
/// <para>
|
||||||
/// <para>
|
/// Another common use would be if you have a private collection of a non-public
|
||||||
/// Another common use would be if you have a private collection of a non-public
|
/// type that's derived from some publicly visible type. By using this collection,
|
||||||
/// type that's derived from some publicly visible type. By using this collection,
|
/// you can return the items under the publicly visible type while still having
|
||||||
/// you can return the items under the publicly visible type while still having
|
/// your private collection under the non-public type, eliminating the need to
|
||||||
/// your private collection under the non-public type, eliminating the need to
|
/// downcast each time you need to access elements of the non-public type.
|
||||||
/// downcast each time you need to access elements of the non-public type.
|
/// </para>
|
||||||
/// </para>
|
/// </remarks>
|
||||||
/// </remarks>
|
public abstract partial class TransformingReadOnlyCollection<
|
||||||
public abstract partial class TransformingReadOnlyCollection<
|
TContainedItem, TExposedItem
|
||||||
TContainedItem, TExposedItem
|
> : IList<TExposedItem>, IList {
|
||||||
> : IList<TExposedItem>, IList {
|
|
||||||
|
#region class TransformingEnumerator
|
||||||
#region class TransformingEnumerator
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// An enumerator that transforms the items returned by an enumerator of the
|
||||||
/// An enumerator that transforms the items returned by an enumerator of the
|
/// wrapped collection into the exposed type on-the-fly.
|
||||||
/// wrapped collection into the exposed type on-the-fly.
|
/// </summary>
|
||||||
/// </summary>
|
private class TransformingEnumerator : IEnumerator<TExposedItem> {
|
||||||
private class TransformingEnumerator : IEnumerator<TExposedItem> {
|
|
||||||
|
/// <summary>Initializes a new transforming enumerator</summary>
|
||||||
/// <summary>Initializes a new transforming enumerator</summary>
|
/// <param name="transformer">Owner; used to invoke the Transform() method</param>
|
||||||
/// <param name="transformer">Owner; used to invoke the Transform() method</param>
|
/// <param name="containedTypeEnumerator">Enumerator of the wrapped collection</param>
|
||||||
/// <param name="containedTypeEnumerator">Enumerator of the wrapped collection</param>
|
public TransformingEnumerator(
|
||||||
public TransformingEnumerator(
|
TransformingReadOnlyCollection<TContainedItem, TExposedItem> transformer,
|
||||||
TransformingReadOnlyCollection<TContainedItem, TExposedItem> transformer,
|
IEnumerator<TContainedItem> containedTypeEnumerator
|
||||||
IEnumerator<TContainedItem> containedTypeEnumerator
|
) {
|
||||||
) {
|
this.transformer = transformer;
|
||||||
this.transformer = transformer;
|
this.containedTypeEnumerator = containedTypeEnumerator;
|
||||||
this.containedTypeEnumerator = containedTypeEnumerator;
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Immediately releases all resources used by the instance</summary>
|
||||||
/// <summary>Immediately releases all resources used by the instance</summary>
|
public void Dispose() {
|
||||||
public void Dispose() {
|
this.containedTypeEnumerator.Dispose();
|
||||||
this.containedTypeEnumerator.Dispose();
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// The element in the collection at the current position of the enumerator.
|
||||||
/// The element in the collection at the current position of the enumerator.
|
/// </summary>
|
||||||
/// </summary>
|
public TExposedItem Current {
|
||||||
public TExposedItem Current {
|
get {
|
||||||
get {
|
return this.transformer.Transform(this.containedTypeEnumerator.Current);
|
||||||
return this.transformer.Transform(this.containedTypeEnumerator.Current);
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Gets the current element in the collection.</summary>
|
||||||
/// <summary>Gets the current element in the collection.</summary>
|
/// <returns>The current element in the collection.</returns>
|
||||||
/// <returns>The current element in the collection.</returns>
|
/// <exception cref="System.InvalidOperationException">
|
||||||
/// <exception cref="System.InvalidOperationException">
|
/// The enumerator is positioned before the first element of the collection
|
||||||
/// The enumerator is positioned before the first element of the collection
|
/// or after the last element.
|
||||||
/// or after the last element.
|
/// </exception>
|
||||||
/// </exception>
|
public bool MoveNext() {
|
||||||
public bool MoveNext() {
|
return this.containedTypeEnumerator.MoveNext();
|
||||||
return this.containedTypeEnumerator.MoveNext();
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Sets the enumerator to its initial position, which is before the first element
|
||||||
/// Sets the enumerator to its initial position, which is before the first element
|
/// in the collection.
|
||||||
/// in the collection.
|
/// </summary>
|
||||||
/// </summary>
|
/// <exception cref="System.InvalidOperationException">
|
||||||
/// <exception cref="System.InvalidOperationException">
|
/// The collection was modified after the enumerator was created.
|
||||||
/// The collection was modified after the enumerator was created.
|
/// </exception>
|
||||||
/// </exception>
|
public void Reset() {
|
||||||
public void Reset() {
|
this.containedTypeEnumerator.Reset();
|
||||||
this.containedTypeEnumerator.Reset();
|
}
|
||||||
}
|
|
||||||
|
/// <summary>The current element in the collection.</summary>
|
||||||
/// <summary>The current element in the collection.</summary>
|
/// <exception cref="System.InvalidOperationException">
|
||||||
/// <exception cref="System.InvalidOperationException">
|
/// The enumerator is positioned before the first element of the collection
|
||||||
/// The enumerator is positioned before the first element of the collection
|
/// or after the last element.
|
||||||
/// or after the last element.
|
/// </exception>
|
||||||
/// </exception>
|
object IEnumerator.Current {
|
||||||
object IEnumerator.Current {
|
get { return Current; }
|
||||||
get { return Current; }
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Collection that owns this enumerator; required to invoke the item
|
||||||
/// Collection that owns this enumerator; required to invoke the item
|
/// transformation method.
|
||||||
/// transformation method.
|
/// </summary>
|
||||||
/// </summary>
|
private TransformingReadOnlyCollection<TContainedItem, TExposedItem> transformer;
|
||||||
private TransformingReadOnlyCollection<TContainedItem, TExposedItem> transformer;
|
/// <summary>An enumerator from the wrapped collection</summary>
|
||||||
/// <summary>An enumerator from the wrapped collection</summary>
|
private IEnumerator<TContainedItem> containedTypeEnumerator;
|
||||||
private IEnumerator<TContainedItem> containedTypeEnumerator;
|
|
||||||
|
}
|
||||||
}
|
|
||||||
|
#endregion // class TransformingEnumerator
|
||||||
#endregion // class TransformingEnumerator
|
|
||||||
|
/// <summary>Initializes a new transforming collection wrapper</summary>
|
||||||
/// <summary>Initializes a new transforming collection wrapper</summary>
|
/// <param name="items">
|
||||||
/// <param name="items">
|
/// Internal list of items that are transformed into the exposed type when
|
||||||
/// Internal list of items that are transformed into the exposed type when
|
/// accessed through the TransformingReadOnlyCollection.
|
||||||
/// accessed through the TransformingReadOnlyCollection.
|
/// </param>
|
||||||
/// </param>
|
public TransformingReadOnlyCollection(IList<TContainedItem> items) {
|
||||||
public TransformingReadOnlyCollection(IList<TContainedItem> items) {
|
this.items = items;
|
||||||
this.items = items;
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Determines whether an element is in the TransformingReadOnlyCollection
|
||||||
/// Determines whether an element is in the TransformingReadOnlyCollection
|
/// </summary>
|
||||||
/// </summary>
|
/// <param name="item">
|
||||||
/// <param name="item">
|
/// The object to locate in the TransformingReadOnlyCollection.
|
||||||
/// The object to locate in the TransformingReadOnlyCollection.
|
/// The value can be null for reference types.
|
||||||
/// The value can be null for reference types.
|
/// </param>
|
||||||
/// </param>
|
/// <returns>
|
||||||
/// <returns>
|
/// True if value is found in the TransformingReadOnlyCollection; otherwise, false.
|
||||||
/// True if value is found in the TransformingReadOnlyCollection; otherwise, false.
|
/// </returns>
|
||||||
/// </returns>
|
/// <remarks>
|
||||||
/// <remarks>
|
/// The default implementation of this method is very unoptimized and will
|
||||||
/// The default implementation of this method is very unoptimized and will
|
/// enumerate all the items in the collection, transforming one after another
|
||||||
/// enumerate all the items in the collection, transforming one after another
|
/// to check whether the transformed item matches the item the user was
|
||||||
/// to check whether the transformed item matches the item the user was
|
/// looking for. It is recommended to provide a custom implementation of
|
||||||
/// looking for. It is recommended to provide a custom implementation of
|
/// this method, if possible.
|
||||||
/// this method, if possible.
|
/// </remarks>
|
||||||
/// </remarks>
|
public virtual bool Contains(TExposedItem item) {
|
||||||
public virtual bool Contains(TExposedItem item) {
|
return (IndexOf(item) != -1);
|
||||||
return (IndexOf(item) != -1);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Copies the entire TransformingReadOnlyCollection to a compatible one-dimensional
|
||||||
/// Copies the entire TransformingReadOnlyCollection to a compatible one-dimensional
|
/// System.Array, starting at the specified index of the target array.
|
||||||
/// System.Array, starting at the specified index of the target array.
|
/// </summary>
|
||||||
/// </summary>
|
/// <param name="array">
|
||||||
/// <param name="array">
|
/// The one-dimensional System.Array that is the destination of the elements copied
|
||||||
/// The one-dimensional System.Array that is the destination of the elements copied
|
/// from the TransformingReadOnlyCollection. The System.Array must have
|
||||||
/// from the TransformingReadOnlyCollection. The System.Array must have
|
/// zero-based indexing.
|
||||||
/// zero-based indexing.
|
/// </param>
|
||||||
/// </param>
|
/// <param name="index">
|
||||||
/// <param name="index">
|
/// The zero-based index in array at which copying begins.
|
||||||
/// The zero-based index in array at which copying begins.
|
/// </param>
|
||||||
/// </param>
|
/// <exception cref="System.ArgumentException">
|
||||||
/// <exception cref="System.ArgumentException">
|
/// Index is equal to or greater than the length of array or the number of elements
|
||||||
/// Index is equal to or greater than the length of array or the number of elements
|
/// in the source TransformingReadOnlyCollection is greater than the available space
|
||||||
/// in the source TransformingReadOnlyCollection is greater than the available space
|
/// from index to the end of the destination array.
|
||||||
/// from index to the end of the destination array.
|
/// </exception>
|
||||||
/// </exception>
|
/// <exception cref="System.ArgumentOutOfRangeException">
|
||||||
/// <exception cref="System.ArgumentOutOfRangeException">
|
/// Index is less than zero.
|
||||||
/// Index is less than zero.
|
/// </exception>
|
||||||
/// </exception>
|
/// <exception cref="System.ArgumentNullException">
|
||||||
/// <exception cref="System.ArgumentNullException">
|
/// Array is null.
|
||||||
/// Array is null.
|
/// </exception>
|
||||||
/// </exception>
|
public void CopyTo(TExposedItem[] array, int index) {
|
||||||
public void CopyTo(TExposedItem[] array, int index) {
|
if(this.items.Count > (array.Length - index)) {
|
||||||
if(this.items.Count > (array.Length - index)) {
|
throw new ArgumentException(
|
||||||
throw new ArgumentException(
|
"Array too small to fit the collection items starting at the specified index"
|
||||||
"Array too small to fit the collection items starting at the specified index"
|
);
|
||||||
);
|
}
|
||||||
}
|
|
||||||
|
for(int itemIndex = 0; itemIndex < this.items.Count; ++itemIndex) {
|
||||||
for(int itemIndex = 0; itemIndex < this.items.Count; ++itemIndex) {
|
array[itemIndex + index] = Transform(this.items[itemIndex]);
|
||||||
array[itemIndex + index] = Transform(this.items[itemIndex]);
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Returns an enumerator that iterates through the TransformingReadOnlyCollection.
|
||||||
/// Returns an enumerator that iterates through the TransformingReadOnlyCollection.
|
/// </summary>
|
||||||
/// </summary>
|
/// <returns>
|
||||||
/// <returns>
|
/// An enumerator or the TransformingReadOnlyCollection.
|
||||||
/// An enumerator or the TransformingReadOnlyCollection.
|
/// </returns>
|
||||||
/// </returns>
|
public IEnumerator<TExposedItem> GetEnumerator() {
|
||||||
public IEnumerator<TExposedItem> GetEnumerator() {
|
return new TransformingEnumerator(this, this.items.GetEnumerator());
|
||||||
return new TransformingEnumerator(this, this.items.GetEnumerator());
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Searches for the specified object and returns the zero-based index of the
|
||||||
/// Searches for the specified object and returns the zero-based index of the
|
/// first occurrence within the entire TransformingReadOnlyCollection.
|
||||||
/// first occurrence within the entire TransformingReadOnlyCollection.
|
/// </summary>
|
||||||
/// </summary>
|
/// <param name="item">
|
||||||
/// <param name="item">
|
/// The object to locate in the TransformingReadOnlyCollection. The value can
|
||||||
/// The object to locate in the TransformingReadOnlyCollection. The value can
|
/// be null for reference types.
|
||||||
/// be null for reference types.
|
/// </param>
|
||||||
/// </param>
|
/// <returns>
|
||||||
/// <returns>
|
/// The zero-based index of the first occurrence of item within the entire
|
||||||
/// The zero-based index of the first occurrence of item within the entire
|
/// TransformingReadOnlyCollection, if found; otherwise, -1.
|
||||||
/// TransformingReadOnlyCollection, if found; otherwise, -1.
|
/// </returns>
|
||||||
/// </returns>
|
/// <remarks>
|
||||||
/// <remarks>
|
/// The default implementation of this method is very unoptimized and will
|
||||||
/// The default implementation of this method is very unoptimized and will
|
/// enumerate all the items in the collection, transforming one after another
|
||||||
/// enumerate all the items in the collection, transforming one after another
|
/// to check whether the transformed item matches the item the user was
|
||||||
/// to check whether the transformed item matches the item the user was
|
/// looking for. It is recommended to provide a custom implementation of
|
||||||
/// looking for. It is recommended to provide a custom implementation of
|
/// this method, if possible.
|
||||||
/// this method, if possible.
|
/// </remarks>
|
||||||
/// </remarks>
|
public int IndexOf(TExposedItem item) {
|
||||||
public int IndexOf(TExposedItem item) {
|
|
||||||
|
if(item == null) {
|
||||||
if(item == null) {
|
|
||||||
|
for(int index = 0; index < this.items.Count; ++index) {
|
||||||
for(int index = 0; index < this.items.Count; ++index) {
|
if(Transform(this.items[index]) == null) {
|
||||||
if(Transform(this.items[index]) == null) {
|
return index;
|
||||||
return index;
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
} else {
|
||||||
} else {
|
|
||||||
|
var comparer = EqualityComparer<TExposedItem>.Default;
|
||||||
var comparer = EqualityComparer<TExposedItem>.Default;
|
for(int index = 0; index < this.items.Count; ++index) {
|
||||||
for(int index = 0; index < this.items.Count; ++index) {
|
if(comparer.Equals(Transform(this.items[index]), item)) {
|
||||||
if(comparer.Equals(Transform(this.items[index]), item)) {
|
return index;
|
||||||
return index;
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
}
|
||||||
}
|
|
||||||
|
return -1;
|
||||||
return -1;
|
|
||||||
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// The number of elements contained in the TransformingReadOnlyCollection instance
|
||||||
/// The number of elements contained in the TransformingReadOnlyCollection instance
|
/// </summary>
|
||||||
/// </summary>
|
public int Count {
|
||||||
public int Count {
|
get { return this.items.Count; }
|
||||||
get { return this.items.Count; }
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Gets the element at the specified index.</summary>
|
||||||
/// <summary>Gets the element at the specified index.</summary>
|
/// <param name="index">The zero-based index of the element to get.</param>
|
||||||
/// <param name="index">The zero-based index of the element to get.</param>
|
/// <returns>The element at the specified index.</returns>
|
||||||
/// <returns>The element at the specified index.</returns>
|
/// <exception cref="System.ArgumentOutOfRangeException">
|
||||||
/// <exception cref="System.ArgumentOutOfRangeException">
|
/// Index is less than zero or index is equal to or greater than
|
||||||
/// Index is less than zero or index is equal to or greater than
|
/// TransformingReadOnlyCollection.Count.
|
||||||
/// TransformingReadOnlyCollection.Count.
|
/// </exception>
|
||||||
/// </exception>
|
public TExposedItem this[int index] {
|
||||||
public TExposedItem this[int index] {
|
get { return Transform(this.items[index]); }
|
||||||
get { return Transform(this.items[index]); }
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Whether the List is write-protected</summary>
|
||||||
/// <summary>Whether the List is write-protected</summary>
|
public bool IsReadOnly {
|
||||||
public bool IsReadOnly {
|
get { return true; }
|
||||||
get { return true; }
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Transforms an item into the exposed type</summary>
|
||||||
/// <summary>Transforms an item into the exposed type</summary>
|
/// <param name="item">Item to be transformed</param>
|
||||||
/// <param name="item">Item to be transformed</param>
|
/// <returns>The transformed item</returns>
|
||||||
/// <returns>The transformed item</returns>
|
/// <remarks>
|
||||||
/// <remarks>
|
/// This method is used to transform an item in the wrapped collection into
|
||||||
/// This method is used to transform an item in the wrapped collection into
|
/// the exposed item type whenever the user accesses an item. Expect it to
|
||||||
/// the exposed item type whenever the user accesses an item. Expect it to
|
/// be called frequently, because the TransformingReadOnlyCollection does
|
||||||
/// be called frequently, because the TransformingReadOnlyCollection does
|
/// not cache or otherwise store the transformed items.
|
||||||
/// not cache or otherwise store the transformed items.
|
/// </remarks>
|
||||||
/// </remarks>
|
protected abstract TExposedItem Transform(TContainedItem item);
|
||||||
protected abstract TExposedItem Transform(TContainedItem item);
|
|
||||||
|
/// <summary>Items being transformed upon exposure by this collection</summary>
|
||||||
/// <summary>Items being transformed upon exposure by this collection</summary>
|
private IList<TContainedItem> items;
|
||||||
private IList<TContainedItem> items;
|
/// <summary>Synchronization root for threaded accesses to this collection</summary>
|
||||||
/// <summary>Synchronization root for threaded accesses to this collection</summary>
|
private object syncRoot;
|
||||||
private object syncRoot;
|
|
||||||
|
}
|
||||||
}
|
|
||||||
|
} // namespace Nuclex.Support.Collections
|
||||||
} // namespace Nuclex.Support.Collections
|
|
||||||
|
|
|
@ -1,88 +1,87 @@
|
||||||
#region CPL License
|
#region Apache License 2.0
|
||||||
/*
|
/*
|
||||||
Nuclex Framework
|
Nuclex .NET Framework
|
||||||
Copyright (C) 2002-2017 Nuclex Development Labs
|
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
|
||||||
|
|
||||||
This library is free software; you can redistribute it and/or
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
modify it under the terms of the IBM Common Public License as
|
you may not use this file except in compliance with the License.
|
||||||
published by the IBM Corporation; either version 1.0 of the
|
You may obtain a copy of the License at
|
||||||
License, or (at your option) any later version.
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
This library is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
Unless required by applicable law or agreed to in writing, software
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
IBM Common Public License for more details.
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
You should have received a copy of the IBM Common Public
|
limitations under the License.
|
||||||
License along with this library
|
*/
|
||||||
*/
|
#endregion // Apache License 2.0
|
||||||
#endregion
|
|
||||||
|
#if !NO_SETS
|
||||||
#if !NO_SETS
|
|
||||||
|
using System;
|
||||||
using System;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Generic;
|
|
||||||
|
#if UNITTEST
|
||||||
#if UNITTEST
|
|
||||||
|
using NUnit.Framework;
|
||||||
using NUnit.Framework;
|
|
||||||
|
namespace Nuclex.Support.Collections {
|
||||||
namespace Nuclex.Support.Collections {
|
|
||||||
|
/// <summary>Unit Test for the Variegator multi dictionary</summary>
|
||||||
/// <summary>Unit Test for the Variegator multi dictionary</summary>
|
[TestFixture]
|
||||||
[TestFixture]
|
internal class VariegatorTest {
|
||||||
internal class VariegatorTest {
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Tests whether the default constructor of the reverse comparer works
|
||||||
/// Tests whether the default constructor of the reverse comparer works
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void InstancesCanBeCreated() {
|
||||||
public void InstancesCanBeCreated() {
|
new Variegator<int, string>();
|
||||||
new Variegator<int, string>();
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Verifies that querying for a missing value leads to an exception being thrown
|
||||||
/// Verifies that querying for a missing value leads to an exception being thrown
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void QueryingMissingValueThrowsException() {
|
||||||
public void QueryingMissingValueThrowsException() {
|
var variegator = new Variegator<int, string>();
|
||||||
var variegator = new Variegator<int, string>();
|
Assert.Throws<KeyNotFoundException>(
|
||||||
Assert.Throws<KeyNotFoundException>(
|
() => {
|
||||||
() => {
|
variegator.Get(123);
|
||||||
variegator.Get(123);
|
}
|
||||||
}
|
);
|
||||||
);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Verifies that the variegator resolves ambiguous matches according to its design
|
||||||
/// Verifies that the variegator resolves ambiguous matches according to its design
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void AmbiguityResolvesToLeastRecentValue() {
|
||||||
public void AmbiguityResolvesToLeastRecentValue() {
|
var variegator = new Variegator<int, string>();
|
||||||
var variegator = new Variegator<int, string>();
|
variegator.Add(1, "one");
|
||||||
variegator.Add(1, "one");
|
variegator.Add(1, "eins");
|
||||||
variegator.Add(1, "eins");
|
|
||||||
|
string first = variegator.Get(1);
|
||||||
string first = variegator.Get(1);
|
string second = variegator.Get(1);
|
||||||
string second = variegator.Get(1);
|
|
||||||
|
// The variegator should have selected the first value by random and then
|
||||||
// The variegator should have selected the first value by random and then
|
// returned the other value on the second query
|
||||||
// returned the other value on the second query
|
Assert.AreNotEqual(first, second);
|
||||||
Assert.AreNotEqual(first, second);
|
|
||||||
|
// Now the variegator should return the first value again because it is
|
||||||
// Now the variegator should return the first value again because it is
|
// the least recently used value
|
||||||
// the least recently used value
|
Assert.AreEqual(first, variegator.Get(1));
|
||||||
Assert.AreEqual(first, variegator.Get(1));
|
|
||||||
|
// Repeating the query, the second should be returned again because now
|
||||||
// Repeating the query, the second should be returned again because now
|
// it has become the least recently used value
|
||||||
// it has become the least recently used value
|
Assert.AreEqual(second, variegator.Get(1));
|
||||||
Assert.AreEqual(second, variegator.Get(1));
|
}
|
||||||
}
|
|
||||||
|
}
|
||||||
}
|
|
||||||
|
} // namespace Nuclex.Support.Collections
|
||||||
} // namespace Nuclex.Support.Collections
|
|
||||||
|
#endif // UNITTEST
|
||||||
#endif // UNITTEST
|
|
||||||
|
#endif // !NO_SETS
|
||||||
#endif // !NO_SETS
|
|
||||||
|
|
|
@ -1,287 +1,286 @@
|
||||||
#region CPL License
|
#region Apache License 2.0
|
||||||
/*
|
/*
|
||||||
Nuclex Native Framework
|
Nuclex .NET Framework
|
||||||
Copyright (C) 2002-2017 Nuclex Development Labs
|
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
|
||||||
|
|
||||||
This library is free software; you can redistribute it and/or
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
modify it under the terms of the IBM Common Public License as
|
you may not use this file except in compliance with the License.
|
||||||
published by the IBM Corporation; either version 1.0 of the
|
You may obtain a copy of the License at
|
||||||
License, or (at your option) any later version.
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
This library is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
Unless required by applicable law or agreed to in writing, software
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
IBM Common Public License for more details.
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
You should have received a copy of the IBM Common Public
|
limitations under the License.
|
||||||
License along with this library
|
*/
|
||||||
*/
|
#endregion // Apache License 2.0
|
||||||
#endregion // CPL License
|
|
||||||
|
using System;
|
||||||
using System;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Generic;
|
|
||||||
|
#if !NO_SETS
|
||||||
#if !NO_SETS
|
|
||||||
|
namespace Nuclex.Support.Collections {
|
||||||
namespace Nuclex.Support.Collections {
|
|
||||||
|
/// <summary>Randomly selects between different options, trying to avoid repetition</summary>
|
||||||
/// <summary>Randomly selects between different options, trying to avoid repetition</summary>
|
/// <typeparam name="TKey">Type of keys through which values can be looked up</typeparam>
|
||||||
/// <typeparam name="TKey">Type of keys through which values can be looked up</typeparam>
|
/// <typeparam name="TValue">Type of values provided by the variegator</typeparam>
|
||||||
/// <typeparam name="TValue">Type of values provided by the variegator</typeparam>
|
/// <remarks>
|
||||||
/// <remarks>
|
/// <para>
|
||||||
/// <para>
|
/// This class is useful wherever randomness is involved in a game: picking random
|
||||||
/// This class is useful wherever randomness is involved in a game: picking random
|
/// actions for an NPC to execute, selecting different songs to play, displaying
|
||||||
/// actions for an NPC to execute, selecting different songs to play, displaying
|
/// different dialogue and more.
|
||||||
/// different dialogue and more.
|
/// </para>
|
||||||
/// </para>
|
/// <para>
|
||||||
/// <para>
|
/// In principle, it works like a multimap, associating keys with a number of values
|
||||||
/// In principle, it works like a multimap, associating keys with a number of values
|
/// and allowing you to look up values by their keys. Unlike a multimap, it will try
|
||||||
/// and allowing you to look up values by their keys. Unlike a multimap, it will try
|
/// to avoid handing out a previously provided value again as long as possible.
|
||||||
/// to avoid handing out a previously provided value again as long as possible.
|
/// </para>
|
||||||
/// </para>
|
/// <para>
|
||||||
/// <para>
|
/// A typical usage would be to set up a mapping between situations and dialogue lines.
|
||||||
/// A typical usage would be to set up a mapping between situations and dialogue lines.
|
/// Upon calling <see cref="Get(TKey)" /> with the situation 'detected-player-stealing',
|
||||||
/// Upon calling <see cref="Get(TKey)" /> with the situation 'detected-player-stealing',
|
/// the variegator would return a random (but not recently used) value which in this case
|
||||||
/// the variegator would return a random (but not recently used) value which in this case
|
/// might contain a commentary an NPC might make upon encountering that situation.
|
||||||
/// might contain a commentary an NPC might make upon encountering that situation.
|
/// Other NPCs requesting dialogue lines for the same situation would receive different
|
||||||
/// Other NPCs requesting dialogue lines for the same situation would receive different
|
/// random commentary for as long as long as available data allows.
|
||||||
/// random commentary for as long as long as available data allows.
|
/// </para>
|
||||||
/// </para>
|
/// </remarks>
|
||||||
/// </remarks>
|
public class Variegator<TKey, TValue> {
|
||||||
public class Variegator<TKey, TValue> {
|
|
||||||
|
/// <summary>Initializes a new variegator using the default history length</summary>
|
||||||
/// <summary>Initializes a new variegator using the default history length</summary>
|
public Variegator() : this(64) {}
|
||||||
public Variegator() : this(64) {}
|
|
||||||
|
/// <summary>Initializes a new variegator</summary>
|
||||||
/// <summary>Initializes a new variegator</summary>
|
/// <param name="historyLength">
|
||||||
/// <param name="historyLength">
|
/// How far into the past the variegator will look to avoid repetition
|
||||||
/// How far into the past the variegator will look to avoid repetition
|
/// </param>
|
||||||
/// </param>
|
public Variegator(int historyLength) {
|
||||||
public Variegator(int historyLength) {
|
this.historyLength = historyLength;
|
||||||
this.historyLength = historyLength;
|
this.history = new TValue[historyLength];
|
||||||
this.history = new TValue[historyLength];
|
this.values = new MultiDictionary<TKey, TValue>();
|
||||||
this.values = new MultiDictionary<TKey, TValue>();
|
|
||||||
|
this.randomNumberGenerator = new Random();
|
||||||
this.randomNumberGenerator = new Random();
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Removes all entries from the variegator</summary>
|
||||||
/// <summary>Removes all entries from the variegator</summary>
|
/// <remarks>
|
||||||
/// <remarks>
|
/// This is mainly useful if you are storing smart pointers to values of substantial
|
||||||
/// This is mainly useful if you are storing smart pointers to values of substantial
|
/// size (eg. audio clips instead of just resource proxies or paths) and need to
|
||||||
/// size (eg. audio clips instead of just resource proxies or paths) and need to
|
/// reclaim memory.
|
||||||
/// reclaim memory.
|
/// </remarks>
|
||||||
/// </remarks>
|
public void Clear() {
|
||||||
public void Clear() {
|
freeHistory();
|
||||||
freeHistory();
|
this.historyFull = false;
|
||||||
this.historyFull = false;
|
this.historyTailIndex = 0;
|
||||||
this.historyTailIndex = 0;
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Checks whether the variegator is empty</summary>
|
||||||
/// <summary>Checks whether the variegator is empty</summary>
|
/// <returns>True if there are no entries in the variegator</returns>
|
||||||
/// <returns>True if there are no entries in the variegator</returns>
|
public bool IsEmpty {
|
||||||
public bool IsEmpty {
|
get { return (Count == 0); }
|
||||||
get { return (Count == 0); }
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Returns the number of values in the variegator</summary>
|
||||||
/// <summary>Returns the number of values in the variegator</summary>
|
/// <returns>The number of values stored in the variegator</returns>
|
||||||
/// <returns>The number of values stored in the variegator</returns>
|
/// <remarks>
|
||||||
/// <remarks>
|
/// If the same value is added with different keys (a situation that doesn't make
|
||||||
/// If the same value is added with different keys (a situation that doesn't make
|
/// sense because such reuse should be covered by specifying multiple keys in
|
||||||
/// sense because such reuse should be covered by specifying multiple keys in
|
/// a query), it will be counted multiple times.
|
||||||
/// a query), it will be counted multiple times.
|
/// </remarks>
|
||||||
/// </remarks>
|
public int Count {
|
||||||
public int Count {
|
get { return ((System.Collections.ICollection)this.values).Count; }
|
||||||
get { return ((System.Collections.ICollection)this.values).Count; }
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Insert a new value that can be returned when requesting the specified key
|
||||||
/// Insert a new value that can be returned when requesting the specified key
|
/// </summary>
|
||||||
/// </summary>
|
/// <param name="key">Key of the value that will be inserted</param>
|
||||||
/// <param name="key">Key of the value that will be inserted</param>
|
/// <param name="value">Value that will be inserted under the provided key</param>
|
||||||
/// <param name="value">Value that will be inserted under the provided key</param>
|
public void Add(TKey key, TValue value) {
|
||||||
public void Add(TKey key, TValue value) {
|
this.values.Add(key, value);
|
||||||
this.values.Add(key, value);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Retrieves a random value associated with the specified key</summary>
|
||||||
/// <summary>Retrieves a random value associated with the specified key</summary>
|
/// <param name="key">For for which a value will be looked up</param>
|
||||||
/// <param name="key">For for which a value will be looked up</param>
|
/// <returns>A random value associated with the specified key</returns>
|
||||||
/// <returns>A random value associated with the specified key</returns>
|
public TValue Get(TKey key) {
|
||||||
public TValue Get(TKey key) {
|
ISet<TValue> candidates = new HashSet<TValue>();
|
||||||
ISet<TValue> candidates = new HashSet<TValue>();
|
{
|
||||||
{
|
ICollection<TValue> valueRange = this.values[key];
|
||||||
ICollection<TValue> valueRange = this.values[key];
|
|
||||||
|
// If possible access the values by index because it's faster and produces less
|
||||||
// If possible access the values by index because it's faster and produces less
|
// garbage, otherwise fall back to using an enumerator
|
||||||
// garbage, otherwise fall back to using an enumerator
|
var indexableValueRange = valueRange as IList<TValue>;
|
||||||
var indexableValueRange = valueRange as IList<TValue>;
|
if(indexableValueRange == null) {
|
||||||
if(indexableValueRange == null) {
|
foreach(TValue value in valueRange) {
|
||||||
foreach(TValue value in valueRange) {
|
candidates.Add(value);
|
||||||
candidates.Add(value);
|
}
|
||||||
}
|
} else {
|
||||||
} else {
|
for(int valueIndex = 0; valueIndex < indexableValueRange.Count; ++valueIndex) {
|
||||||
for(int valueIndex = 0; valueIndex < indexableValueRange.Count; ++valueIndex) {
|
candidates.Add(indexableValueRange[valueIndex]);
|
||||||
candidates.Add(indexableValueRange[valueIndex]);
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
TValue result = destructivePickCandidateValue(candidates);
|
||||||
TValue result = destructivePickCandidateValue(candidates);
|
addRecentlyUsedValue(result);
|
||||||
addRecentlyUsedValue(result);
|
return result;
|
||||||
return result;
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Retrieves a random value associated with one of the specified keys</summary>
|
||||||
/// <summary>Retrieves a random value associated with one of the specified keys</summary>
|
/// <param name="keys">Keys that will be considered</param>
|
||||||
/// <param name="keys">Keys that will be considered</param>
|
/// <remarks>
|
||||||
/// <remarks>
|
/// In many cases, you have generic situations (such as 'detected-player-stealing',
|
||||||
/// In many cases, you have generic situations (such as 'detected-player-stealing',
|
/// 'observed-hostile-action') and specified situations (such as
|
||||||
/// 'observed-hostile-action') and specified situations (such as
|
/// 'detected-player-stealing-from-beggar', 'observed-hostile-action-on-cop')
|
||||||
/// 'detected-player-stealing-from-beggar', 'observed-hostile-action-on-cop')
|
/// where a values from both pools should be considered. This method allows you
|
||||||
/// where a values from both pools should be considered. This method allows you
|
/// to specify any number of keys, creating a greater set of values the variegator
|
||||||
/// to specify any number of keys, creating a greater set of values the variegator
|
/// can pick between.
|
||||||
/// can pick between.
|
/// </remarks>
|
||||||
/// </remarks>
|
public TValue Get(params TKey[] keys) {
|
||||||
public TValue Get(params TKey[] keys) {
|
ISet<TValue> candidates = new HashSet<TValue>();
|
||||||
ISet<TValue> candidates = new HashSet<TValue>();
|
|
||||||
|
for(int index = 0; index < keys.Length; ++index) {
|
||||||
for(int index = 0; index < keys.Length; ++index) {
|
ICollection<TValue> valueRange = this.values[keys[index]];
|
||||||
ICollection<TValue> valueRange = this.values[keys[index]];
|
|
||||||
|
// If possible access the values by index because it's faster and produces less
|
||||||
// If possible access the values by index because it's faster and produces less
|
// garbage, otherwise fall back to using an enumerator
|
||||||
// garbage, otherwise fall back to using an enumerator
|
var indexableValueRange = valueRange as IList<TValue>;
|
||||||
var indexableValueRange = valueRange as IList<TValue>;
|
if(indexableValueRange == null) {
|
||||||
if(indexableValueRange == null) {
|
foreach(TValue value in valueRange) {
|
||||||
foreach(TValue value in valueRange) {
|
candidates.Add(value);
|
||||||
candidates.Add(value);
|
}
|
||||||
}
|
} else {
|
||||||
} else {
|
for(int valueIndex = 0; valueIndex < indexableValueRange.Count; ++valueIndex) {
|
||||||
for(int valueIndex = 0; valueIndex < indexableValueRange.Count; ++valueIndex) {
|
candidates.Add(indexableValueRange[valueIndex]);
|
||||||
candidates.Add(indexableValueRange[valueIndex]);
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
TValue result = destructivePickCandidateValue(candidates);
|
||||||
TValue result = destructivePickCandidateValue(candidates);
|
addRecentlyUsedValue(result);
|
||||||
addRecentlyUsedValue(result);
|
return result;
|
||||||
return result;
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Picks amongst the values in a set</summary>
|
||||||
/// <summary>Picks amongst the values in a set</summary>
|
/// <param name="candidates">
|
||||||
/// <param name="candidates">
|
/// Set containing the candidats values to consider. Will be destroyed.
|
||||||
/// Set containing the candidats values to consider. Will be destroyed.
|
/// </param>
|
||||||
/// </param>
|
/// <returns>The least recently used candidate value or a random one</returns>
|
||||||
/// <returns>The least recently used candidate value or a random one</returns>
|
private TValue destructivePickCandidateValue(ISet<TValue> candidates) {
|
||||||
private TValue destructivePickCandidateValue(ISet<TValue> candidates) {
|
removeRecentlyUsedValues(candidates);
|
||||||
removeRecentlyUsedValues(candidates);
|
|
||||||
|
switch(candidates.Count) {
|
||||||
switch(candidates.Count) {
|
case 0: {
|
||||||
case 0: {
|
throw new InvalidOperationException("No values mapped to this key");
|
||||||
throw new InvalidOperationException("No values mapped to this key");
|
}
|
||||||
}
|
case 1: {
|
||||||
case 1: {
|
using(IEnumerator<TValue> enumerator = candidates.GetEnumerator()) {
|
||||||
using(IEnumerator<TValue> enumerator = candidates.GetEnumerator()) {
|
enumerator.MoveNext(); // We can be sure this one returns true
|
||||||
enumerator.MoveNext(); // We can be sure this one returns true
|
return enumerator.Current;
|
||||||
return enumerator.Current;
|
}
|
||||||
}
|
}
|
||||||
}
|
default: {
|
||||||
default: {
|
int index = this.randomNumberGenerator.Next(candidates.Count);
|
||||||
int index = this.randomNumberGenerator.Next(candidates.Count);
|
using(IEnumerator<TValue> enumerator = candidates.GetEnumerator()) {
|
||||||
using(IEnumerator<TValue> enumerator = candidates.GetEnumerator()) {
|
do {
|
||||||
do {
|
--index;
|
||||||
--index;
|
enumerator.MoveNext(); // We can be sure this one returns true
|
||||||
enumerator.MoveNext(); // We can be sure this one returns true
|
} while(index >= 0);
|
||||||
} while(index >= 0);
|
|
||||||
|
return enumerator.Current;
|
||||||
return enumerator.Current;
|
}
|
||||||
}
|
|
||||||
|
throw new InvalidOperationException(
|
||||||
throw new InvalidOperationException(
|
"ISet.Count was off or random number generator malfunctioned"
|
||||||
"ISet.Count was off or random number generator malfunctioned"
|
);
|
||||||
);
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Adds a recently used value to the history</summary>
|
||||||
/// <summary>Adds a recently used value to the history</summary>
|
/// <param name="value">Value that will be added to the history</param>
|
||||||
/// <param name="value">Value that will be added to the history</param>
|
private void addRecentlyUsedValue(TValue value) {
|
||||||
private void addRecentlyUsedValue(TValue value) {
|
if(this.historyTailIndex == this.historyLength) {
|
||||||
if(this.historyTailIndex == this.historyLength) {
|
this.historyFull = true;
|
||||||
this.historyFull = true;
|
this.history[0] = value;
|
||||||
this.history[0] = value;
|
this.historyTailIndex = 1;
|
||||||
this.historyTailIndex = 1;
|
} else {
|
||||||
} else {
|
this.history[this.historyTailIndex] = value;
|
||||||
this.history[this.historyTailIndex] = value;
|
++this.historyTailIndex;
|
||||||
++this.historyTailIndex;
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Removes all values that are in the recent use list from a set</summary>
|
||||||
/// <summary>Removes all values that are in the recent use list from a set</summary>
|
/// <param name="candidates">Set from which recently used values are removed</param>
|
||||||
/// <param name="candidates">Set from which recently used values are removed</param>
|
/// <remarks>
|
||||||
/// <remarks>
|
/// Stops removing values when there's only 1 value left in the set
|
||||||
/// Stops removing values when there's only 1 value left in the set
|
/// </remarks>
|
||||||
/// </remarks>
|
private void removeRecentlyUsedValues(ISet<TValue> candidates) {
|
||||||
private void removeRecentlyUsedValues(ISet<TValue> candidates) {
|
if(candidates.Count <= 1) {
|
||||||
if(candidates.Count <= 1) {
|
return;
|
||||||
return;
|
}
|
||||||
}
|
|
||||||
|
if(this.historyFull) { // History buffer has wrapped around
|
||||||
if(this.historyFull) { // History buffer has wrapped around
|
int index = this.historyTailIndex;
|
||||||
int index = this.historyTailIndex;
|
while(index > 0) {
|
||||||
while(index > 0) {
|
--index;
|
||||||
--index;
|
if(candidates.Remove(this.history[index])) {
|
||||||
if(candidates.Remove(this.history[index])) {
|
if(candidates.Count <= 1) {
|
||||||
if(candidates.Count <= 1) {
|
return;
|
||||||
return;
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
index = this.historyLength;
|
||||||
index = this.historyLength;
|
while(index > this.historyTailIndex) {
|
||||||
while(index > this.historyTailIndex) {
|
--index;
|
||||||
--index;
|
if(candidates.Remove(this.history[index])) {
|
||||||
if(candidates.Remove(this.history[index])) {
|
if(candidates.Count <= 1) {
|
||||||
if(candidates.Count <= 1) {
|
return;
|
||||||
return;
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
} else { // History buffer was not full yet
|
||||||
} else { // History buffer was not full yet
|
int index = this.historyTailIndex;
|
||||||
int index = this.historyTailIndex;
|
while(index > 0) {
|
||||||
while(index > 0) {
|
--index;
|
||||||
--index;
|
if(candidates.Remove(this.history[index])) {
|
||||||
if(candidates.Remove(this.history[index])) {
|
if(candidates.Count <= 1) {
|
||||||
if(candidates.Count <= 1) {
|
return;
|
||||||
return;
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Frees all memory used by the individual history entries</summary>
|
||||||
/// <summary>Frees all memory used by the individual history entries</summary>
|
/// <remarks>
|
||||||
/// <remarks>
|
/// The history array itself is kept alive and the tail index + full flag will
|
||||||
/// The history array itself is kept alive and the tail index + full flag will
|
/// not be reset.
|
||||||
/// not be reset.
|
/// </remarks>
|
||||||
/// </remarks>
|
private void freeHistory() {
|
||||||
private void freeHistory() {
|
Array.Clear(this.history, 0, this.historyLength);
|
||||||
Array.Clear(this.history, 0, this.historyLength);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Stores the entries the variegator can select from by their keys</summary>
|
||||||
/// <summary>Stores the entries the variegator can select from by their keys</summary>
|
private IMultiDictionary<TKey, TValue> values;
|
||||||
private IMultiDictionary<TKey, TValue> values;
|
|
||||||
|
/// <summary>Random number generator that will be used to pick random values</summary>
|
||||||
/// <summary>Random number generator that will be used to pick random values</summary>
|
private Random randomNumberGenerator;
|
||||||
private Random randomNumberGenerator;
|
/// <summary>Number of entries in the recently used list</summary>
|
||||||
/// <summary>Number of entries in the recently used list</summary>
|
private int historyLength;
|
||||||
private int historyLength;
|
|
||||||
|
/// <summary>Array containing the most recently provided values</summary>
|
||||||
/// <summary>Array containing the most recently provided values</summary>
|
private TValue[] history;
|
||||||
private TValue[] history;
|
/// <summary>Index of the tail in the recently used value array</summary>
|
||||||
/// <summary>Index of the tail in the recently used value array</summary>
|
private int historyTailIndex;
|
||||||
private int historyTailIndex;
|
/// <summary>Whether the recently used value history is at capacity</summary>
|
||||||
/// <summary>Whether the recently used value history is at capacity</summary>
|
private bool historyFull;
|
||||||
private bool historyFull;
|
|
||||||
|
}
|
||||||
}
|
|
||||||
|
} // namespace Nuclex.Support.Collections
|
||||||
} // namespace Nuclex.Support.Collections
|
|
||||||
|
#endif // !NO_SETS
|
||||||
#endif // !NO_SETS
|
|
||||||
|
|
|
@ -1,211 +1,210 @@
|
||||||
#region CPL License
|
#region Apache License 2.0
|
||||||
/*
|
/*
|
||||||
Nuclex Framework
|
Nuclex .NET Framework
|
||||||
Copyright (C) 2002-2017 Nuclex Development Labs
|
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
|
||||||
|
|
||||||
This library is free software; you can redistribute it and/or
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
modify it under the terms of the IBM Common Public License as
|
you may not use this file except in compliance with the License.
|
||||||
published by the IBM Corporation; either version 1.0 of the
|
You may obtain a copy of the License at
|
||||||
License, or (at your option) any later version.
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
This library is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
Unless required by applicable law or agreed to in writing, software
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
IBM Common Public License for more details.
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
You should have received a copy of the IBM Common Public
|
limitations under the License.
|
||||||
License along with this library
|
*/
|
||||||
*/
|
#endregion // Apache License 2.0
|
||||||
#endregion
|
|
||||||
|
using System;
|
||||||
using System;
|
using System.Threading;
|
||||||
using System.Threading;
|
using System.Collections;
|
||||||
using System.Collections;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Generic;
|
|
||||||
|
namespace Nuclex.Support.Collections {
|
||||||
namespace Nuclex.Support.Collections {
|
|
||||||
|
partial class WeakCollection<TItem> {
|
||||||
partial class WeakCollection<TItem> {
|
|
||||||
|
#region IEnumerable Members
|
||||||
#region IEnumerable Members
|
|
||||||
|
/// <summary>Returns an enumerator that iterates through a collection.</summary>
|
||||||
/// <summary>Returns an enumerator that iterates through a collection.</summary>
|
/// <returns>
|
||||||
/// <returns>
|
/// A System.Collections.IEnumerator object that can be used to iterate through
|
||||||
/// A System.Collections.IEnumerator object that can be used to iterate through
|
/// the collection.
|
||||||
/// the collection.
|
/// </returns>
|
||||||
/// </returns>
|
IEnumerator IEnumerable.GetEnumerator() {
|
||||||
IEnumerator IEnumerable.GetEnumerator() {
|
return GetEnumerator();
|
||||||
return GetEnumerator();
|
}
|
||||||
}
|
|
||||||
|
#endregion
|
||||||
#endregion
|
|
||||||
|
#region IList Members
|
||||||
#region IList Members
|
|
||||||
|
/// <summary>Adds an item to the WeakCollection.</summary>
|
||||||
/// <summary>Adds an item to the WeakCollection.</summary>
|
/// <param name="value">The System.Object to add to the WeakCollection.</param>
|
||||||
/// <param name="value">The System.Object to add to the WeakCollection.</param>
|
/// <returns>The position into which the new element was inserted.</returns>
|
||||||
/// <returns>The position into which the new element was inserted.</returns>
|
/// <exception cref="System.NotSupportedException">
|
||||||
/// <exception cref="System.NotSupportedException">
|
/// The System.Collections.IList is read-only or the WeakCollection has a fixed size.
|
||||||
/// The System.Collections.IList is read-only or the WeakCollection has a fixed size.
|
/// </exception>
|
||||||
/// </exception>
|
int IList.Add(object value) {
|
||||||
int IList.Add(object value) {
|
TItem valueAsItemType = downcastToItemType(value);
|
||||||
TItem valueAsItemType = downcastToItemType(value);
|
return (this.items as IList).Add(new WeakReference<TItem>(valueAsItemType));
|
||||||
return (this.items as IList).Add(new WeakReference<TItem>(valueAsItemType));
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Determines whether the WeakCollection contains a specific value.
|
||||||
/// Determines whether the WeakCollection contains a specific value.
|
/// </summary>
|
||||||
/// </summary>
|
/// <param name="value">The System.Object to locate in the WeakCollection.</param>
|
||||||
/// <param name="value">The System.Object to locate in the WeakCollection.</param>
|
/// <returns>
|
||||||
/// <returns>
|
/// True if the System.Object is found in the WeakCollection; otherwise, false.
|
||||||
/// True if the System.Object is found in the WeakCollection; otherwise, false.
|
/// </returns>
|
||||||
/// </returns>
|
bool IList.Contains(object value) {
|
||||||
bool IList.Contains(object value) {
|
TItem valueAsItemType = downcastToItemType(value);
|
||||||
TItem valueAsItemType = downcastToItemType(value);
|
return Contains(valueAsItemType);
|
||||||
return Contains(valueAsItemType);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Determines the index of a specific item in the WeakCollection.</summary>
|
||||||
/// <summary>Determines the index of a specific item in the WeakCollection.</summary>
|
/// <param name="value">The System.Object to locate in the WeakCollection.</param>
|
||||||
/// <param name="value">The System.Object to locate in the WeakCollection.</param>
|
/// <returns>
|
||||||
/// <returns>
|
/// The index of value if found in the list; otherwise, -1.
|
||||||
/// The index of value if found in the list; otherwise, -1.
|
/// </returns>
|
||||||
/// </returns>
|
int IList.IndexOf(object value) {
|
||||||
int IList.IndexOf(object value) {
|
TItem valueAsItemType = downcastToItemType(value);
|
||||||
TItem valueAsItemType = downcastToItemType(value);
|
return IndexOf(valueAsItemType);
|
||||||
return IndexOf(valueAsItemType);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Inserts an item to the WeakCollection at the specified index.
|
||||||
/// Inserts an item to the WeakCollection at the specified index.
|
/// </summary>
|
||||||
/// </summary>
|
/// <param name="index">
|
||||||
/// <param name="index">
|
/// The zero-based index at which value should be inserted.
|
||||||
/// The zero-based index at which value should be inserted.
|
/// </param>
|
||||||
/// </param>
|
/// <param name="value">The System.Object to insert into the WeakCollection.</param>
|
||||||
/// <param name="value">The System.Object to insert into the WeakCollection.</param>
|
/// <exception cref="System.ArgumentOutOfRangeException">
|
||||||
/// <exception cref="System.ArgumentOutOfRangeException">
|
/// Index is not a valid index in the TransformingReadOnlyCollection.
|
||||||
/// Index is not a valid index in the TransformingReadOnlyCollection.
|
/// </exception>
|
||||||
/// </exception>
|
/// <exception cref="System.NotSupportedException">
|
||||||
/// <exception cref="System.NotSupportedException">
|
/// The System.Collections.IList is read-only or the WeakCollection has a fixed size.
|
||||||
/// The System.Collections.IList is read-only or the WeakCollection has a fixed size.
|
/// </exception>
|
||||||
/// </exception>
|
/// <exception cref="System.NullReferenceException">
|
||||||
/// <exception cref="System.NullReferenceException">
|
/// Value is null reference in the WeakCollection.
|
||||||
/// Value is null reference in the WeakCollection.
|
/// </exception>
|
||||||
/// </exception>
|
void IList.Insert(int index, object value) {
|
||||||
void IList.Insert(int index, object value) {
|
TItem valueAsItemType = downcastToItemType(value);
|
||||||
TItem valueAsItemType = downcastToItemType(value);
|
Insert(index, valueAsItemType);
|
||||||
Insert(index, valueAsItemType);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// A value indicating whether the WeakCollection has a fixed size.
|
||||||
/// A value indicating whether the WeakCollection has a fixed size.
|
/// </summary>
|
||||||
/// </summary>
|
bool IList.IsFixedSize {
|
||||||
bool IList.IsFixedSize {
|
get { return (this.items as IList).IsFixedSize; }
|
||||||
get { return (this.items as IList).IsFixedSize; }
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Removes the first occurrence of a specific object from the WeakCollection.
|
||||||
/// Removes the first occurrence of a specific object from the WeakCollection.
|
/// </summary>
|
||||||
/// </summary>
|
/// <param name="value">The System.Object to remove from the WeakCollection.</param>
|
||||||
/// <param name="value">The System.Object to remove from the WeakCollection.</param>
|
/// <exception cref="System.NotSupportedException">
|
||||||
/// <exception cref="System.NotSupportedException">
|
/// The WeakCollection is read-only or the WeakCollection has a fixed size.
|
||||||
/// The WeakCollection is read-only or the WeakCollection has a fixed size.
|
/// </exception>
|
||||||
/// </exception>
|
void IList.Remove(object value) {
|
||||||
void IList.Remove(object value) {
|
TItem valueAsItemType = downcastToItemType(value);
|
||||||
TItem valueAsItemType = downcastToItemType(value);
|
Remove(valueAsItemType);
|
||||||
Remove(valueAsItemType);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Gets or sets the element at the specified index.</summary>
|
||||||
/// <summary>Gets or sets the element at the specified index.</summary>
|
/// <param name="index">The zero-based index of the element to get or set.</param>
|
||||||
/// <param name="index">The zero-based index of the element to get or set.</param>
|
/// <returns>The element at the specified index</returns>
|
||||||
/// <returns>The element at the specified index</returns>
|
/// <exception cref="System.ArgumentOutOfRangeException">
|
||||||
/// <exception cref="System.ArgumentOutOfRangeException">
|
/// Index is not a valid index in the WeakCollection
|
||||||
/// Index is not a valid index in the WeakCollection
|
/// </exception>
|
||||||
/// </exception>
|
object IList.this[int index] {
|
||||||
object IList.this[int index] {
|
get { return this[index]; }
|
||||||
get { return this[index]; }
|
set {
|
||||||
set {
|
TItem valueAsItemType = downcastToItemType(value);
|
||||||
TItem valueAsItemType = downcastToItemType(value);
|
this[index] = valueAsItemType;
|
||||||
this[index] = valueAsItemType;
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
#endregion
|
||||||
#endregion
|
|
||||||
|
#region ICollection Members
|
||||||
#region ICollection Members
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Copies the elements of the WeakCollection to an System.Array, starting at
|
||||||
/// Copies the elements of the WeakCollection to an System.Array, starting at
|
/// a particular System.Array index.
|
||||||
/// a particular System.Array index.
|
/// </summary>
|
||||||
/// </summary>
|
/// <param name="array">
|
||||||
/// <param name="array">
|
/// The one-dimensional System.Array that is the destination of the elements
|
||||||
/// The one-dimensional System.Array that is the destination of the elements
|
/// copied from WeakCollection. The System.Array must have zero-based indexing.
|
||||||
/// copied from WeakCollection. The System.Array must have zero-based indexing.
|
/// </param>
|
||||||
/// </param>
|
/// <param name="index">The zero-based index in array at which copying begins.</param>
|
||||||
/// <param name="index">The zero-based index in array at which copying begins.</param>
|
/// <exception cref="System.ArgumentNullException">
|
||||||
/// <exception cref="System.ArgumentNullException">
|
/// Array is null.
|
||||||
/// Array is null.
|
/// </exception>
|
||||||
/// </exception>
|
/// <exception cref="System.ArgumentOutOfRangeException">
|
||||||
/// <exception cref="System.ArgumentOutOfRangeException">
|
/// Index is less than zero.
|
||||||
/// Index is less than zero.
|
/// </exception>
|
||||||
/// </exception>
|
/// <exception cref="System.ArgumentException">
|
||||||
/// <exception cref="System.ArgumentException">
|
/// Array is multidimensional or index is equal to or greater than the length
|
||||||
/// Array is multidimensional or index is equal to or greater than the length
|
/// of array or the number of elements in the source WeakCollection is greater than
|
||||||
/// of array or the number of elements in the source WeakCollection is greater than
|
/// the available space from index to the end of the destination array.
|
||||||
/// the available space from index to the end of the destination array.
|
/// </exception>
|
||||||
/// </exception>
|
/// <exception cref="System.InvalidCastException">
|
||||||
/// <exception cref="System.InvalidCastException">
|
/// The type of the source WeakCollection cannot be cast automatically to the type of
|
||||||
/// The type of the source WeakCollection cannot be cast automatically to the type of
|
/// the destination array.
|
||||||
/// the destination array.
|
/// </exception>
|
||||||
/// </exception>
|
void ICollection.CopyTo(Array array, int index) {
|
||||||
void ICollection.CopyTo(Array array, int index) {
|
CopyTo((TItem[])array, index);
|
||||||
CopyTo((TItem[])array, index);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// A value indicating whether access to the WeakCollection is
|
||||||
/// A value indicating whether access to the WeakCollection is
|
/// synchronized (thread safe).
|
||||||
/// synchronized (thread safe).
|
/// </summary>
|
||||||
/// </summary>
|
bool ICollection.IsSynchronized {
|
||||||
bool ICollection.IsSynchronized {
|
get { return false; }
|
||||||
get { return false; }
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// An object that can be used to synchronize access to the WeakCollection.
|
||||||
/// An object that can be used to synchronize access to the WeakCollection.
|
/// </summary>
|
||||||
/// </summary>
|
object ICollection.SyncRoot {
|
||||||
object ICollection.SyncRoot {
|
get {
|
||||||
get {
|
if(this.syncRoot == null) {
|
||||||
if(this.syncRoot == null) {
|
ICollection is2 = this.items as ICollection;
|
||||||
ICollection is2 = this.items as ICollection;
|
if(is2 != null) {
|
||||||
if(is2 != null) {
|
this.syncRoot = is2.SyncRoot;
|
||||||
this.syncRoot = is2.SyncRoot;
|
} else {
|
||||||
} else {
|
Interlocked.CompareExchange(ref this.syncRoot, new object(), null);
|
||||||
Interlocked.CompareExchange(ref this.syncRoot, new object(), null);
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
return this.syncRoot;
|
||||||
return this.syncRoot;
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
#endregion
|
||||||
#endregion
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Downcasts an object reference to a reference to the collection's item type
|
||||||
/// Downcasts an object reference to a reference to the collection's item type
|
/// </summary>
|
||||||
/// </summary>
|
/// <param name="value">Object reference that will be downcast</param>
|
||||||
/// <param name="value">Object reference that will be downcast</param>
|
/// <returns>
|
||||||
/// <returns>
|
/// The specified object referecne as a reference to the collection's item type
|
||||||
/// The specified object referecne as a reference to the collection's item type
|
/// </returns>
|
||||||
/// </returns>
|
private static TItem downcastToItemType(object value) {
|
||||||
private static TItem downcastToItemType(object value) {
|
TItem valueAsItemType = value as TItem;
|
||||||
TItem valueAsItemType = value as TItem;
|
if(!ReferenceEquals(value, null)) {
|
||||||
if(!ReferenceEquals(value, null)) {
|
if(valueAsItemType == null) {
|
||||||
if(valueAsItemType == null) {
|
throw new ArgumentException("Object is not of a compatible type", "value");
|
||||||
throw new ArgumentException("Object is not of a compatible type", "value");
|
}
|
||||||
}
|
}
|
||||||
}
|
return valueAsItemType;
|
||||||
return valueAsItemType;
|
}
|
||||||
}
|
|
||||||
|
}
|
||||||
}
|
|
||||||
|
} // namespace Nuclex.Support.Collections
|
||||||
} // namespace Nuclex.Support.Collections
|
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,339 +1,338 @@
|
||||||
#region CPL License
|
#region Apache License 2.0
|
||||||
/*
|
/*
|
||||||
Nuclex Framework
|
Nuclex .NET Framework
|
||||||
Copyright (C) 2002-2017 Nuclex Development Labs
|
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
|
||||||
|
|
||||||
This library is free software; you can redistribute it and/or
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
modify it under the terms of the IBM Common Public License as
|
you may not use this file except in compliance with the License.
|
||||||
published by the IBM Corporation; either version 1.0 of the
|
You may obtain a copy of the License at
|
||||||
License, or (at your option) any later version.
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
This library is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
Unless required by applicable law or agreed to in writing, software
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
IBM Common Public License for more details.
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
You should have received a copy of the IBM Common Public
|
limitations under the License.
|
||||||
License along with this library
|
*/
|
||||||
*/
|
#endregion // Apache License 2.0
|
||||||
#endregion
|
|
||||||
|
using System;
|
||||||
using System;
|
using System.Collections;
|
||||||
using System.Collections;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Generic;
|
|
||||||
|
namespace Nuclex.Support.Collections {
|
||||||
namespace Nuclex.Support.Collections {
|
|
||||||
|
/// <summary>Collection of weakly referenced objects</summary>
|
||||||
/// <summary>Collection of weakly referenced objects</summary>
|
/// <remarks>
|
||||||
/// <remarks>
|
/// This collection tries to expose the interface of a normal collection, but stores
|
||||||
/// This collection tries to expose the interface of a normal collection, but stores
|
/// objects as weak references. When an object is accessed, it can return null.
|
||||||
/// objects as weak references. When an object is accessed, it can return null.
|
/// when the collection detects that one of its items was garbage collected, it
|
||||||
/// when the collection detects that one of its items was garbage collected, it
|
/// will silently remove that item.
|
||||||
/// will silently remove that item.
|
/// </remarks>
|
||||||
/// </remarks>
|
public partial class WeakCollection<TItem> : IList<TItem>, IList
|
||||||
public partial class WeakCollection<TItem> : IList<TItem>, IList
|
where TItem : class {
|
||||||
where TItem : class {
|
|
||||||
|
#region class UnpackingEnumerator
|
||||||
#region class UnpackingEnumerator
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// An enumerator that unpacks the items returned by an enumerator of the
|
||||||
/// An enumerator that unpacks the items returned by an enumerator of the
|
/// weak reference collection into the actual item type on-the-fly.
|
||||||
/// weak reference collection into the actual item type on-the-fly.
|
/// </summary>
|
||||||
/// </summary>
|
private class UnpackingEnumerator : IEnumerator<TItem> {
|
||||||
private class UnpackingEnumerator : IEnumerator<TItem> {
|
|
||||||
|
/// <summary>Initializes a new unpacking enumerator</summary>
|
||||||
/// <summary>Initializes a new unpacking enumerator</summary>
|
/// <param name="containedTypeEnumerator">
|
||||||
/// <param name="containedTypeEnumerator">
|
/// Enumerator of the weak reference collection
|
||||||
/// Enumerator of the weak reference collection
|
/// </param>
|
||||||
/// </param>
|
public UnpackingEnumerator(
|
||||||
public UnpackingEnumerator(
|
IEnumerator<WeakReference<TItem>> containedTypeEnumerator
|
||||||
IEnumerator<WeakReference<TItem>> containedTypeEnumerator
|
) {
|
||||||
) {
|
this.containedTypeEnumerator = containedTypeEnumerator;
|
||||||
this.containedTypeEnumerator = containedTypeEnumerator;
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Immediately releases all resources used by the instance</summary>
|
||||||
/// <summary>Immediately releases all resources used by the instance</summary>
|
public void Dispose() {
|
||||||
public void Dispose() {
|
this.containedTypeEnumerator.Dispose();
|
||||||
this.containedTypeEnumerator.Dispose();
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// The element in the collection at the current position of the enumerator.
|
||||||
/// The element in the collection at the current position of the enumerator.
|
/// </summary>
|
||||||
/// </summary>
|
public TItem Current {
|
||||||
public TItem Current {
|
get { return this.containedTypeEnumerator.Current.Target; }
|
||||||
get { return this.containedTypeEnumerator.Current.Target; }
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Gets the current element in the collection.</summary>
|
||||||
/// <summary>Gets the current element in the collection.</summary>
|
/// <returns>The current element in the collection.</returns>
|
||||||
/// <returns>The current element in the collection.</returns>
|
/// <exception cref="System.InvalidOperationException">
|
||||||
/// <exception cref="System.InvalidOperationException">
|
/// The enumerator is positioned before the first element of the collection
|
||||||
/// The enumerator is positioned before the first element of the collection
|
/// or after the last element.
|
||||||
/// or after the last element.
|
/// </exception>
|
||||||
/// </exception>
|
public bool MoveNext() {
|
||||||
public bool MoveNext() {
|
return this.containedTypeEnumerator.MoveNext();
|
||||||
return this.containedTypeEnumerator.MoveNext();
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Sets the enumerator to its initial position, which is before the first element
|
||||||
/// Sets the enumerator to its initial position, which is before the first element
|
/// in the collection.
|
||||||
/// in the collection.
|
/// </summary>
|
||||||
/// </summary>
|
/// <exception cref="System.InvalidOperationException">
|
||||||
/// <exception cref="System.InvalidOperationException">
|
/// The collection was modified after the enumerator was created.
|
||||||
/// The collection was modified after the enumerator was created.
|
/// </exception>
|
||||||
/// </exception>
|
public void Reset() {
|
||||||
public void Reset() {
|
this.containedTypeEnumerator.Reset();
|
||||||
this.containedTypeEnumerator.Reset();
|
}
|
||||||
}
|
|
||||||
|
/// <summary>The current element in the collection.</summary>
|
||||||
/// <summary>The current element in the collection.</summary>
|
/// <exception cref="System.InvalidOperationException">
|
||||||
/// <exception cref="System.InvalidOperationException">
|
/// The enumerator is positioned before the first element of the collection
|
||||||
/// The enumerator is positioned before the first element of the collection
|
/// or after the last element.
|
||||||
/// or after the last element.
|
/// </exception>
|
||||||
/// </exception>
|
object IEnumerator.Current {
|
||||||
object IEnumerator.Current {
|
get { return Current; }
|
||||||
get { return Current; }
|
}
|
||||||
}
|
|
||||||
|
/// <summary>An enumerator from the wrapped collection</summary>
|
||||||
/// <summary>An enumerator from the wrapped collection</summary>
|
private IEnumerator<WeakReference<TItem>> containedTypeEnumerator;
|
||||||
private IEnumerator<WeakReference<TItem>> containedTypeEnumerator;
|
|
||||||
|
}
|
||||||
}
|
|
||||||
|
#endregion // class UnpackingEnumerator
|
||||||
#endregion // class UnpackingEnumerator
|
|
||||||
|
/// <summary>Initializes a new weak reference collection</summary>
|
||||||
/// <summary>Initializes a new weak reference collection</summary>
|
/// <param name="items">
|
||||||
/// <param name="items">
|
/// Internal list of weak references that are unpacking when accessed through
|
||||||
/// Internal list of weak references that are unpacking when accessed through
|
/// the WeakCollection's interface.
|
||||||
/// the WeakCollection's interface.
|
/// </param>
|
||||||
/// </param>
|
public WeakCollection(IList<WeakReference<TItem>> items) :
|
||||||
public WeakCollection(IList<WeakReference<TItem>> items) :
|
this(items, EqualityComparer<TItem>.Default) { }
|
||||||
this(items, EqualityComparer<TItem>.Default) { }
|
|
||||||
|
/// <summary>Initializes a new weak reference collection</summary>
|
||||||
/// <summary>Initializes a new weak reference collection</summary>
|
/// <param name="items">
|
||||||
/// <param name="items">
|
/// Internal list of weak references that are unpacking when accessed through
|
||||||
/// Internal list of weak references that are unpacking when accessed through
|
/// the WeakCollection's interface.
|
||||||
/// the WeakCollection's interface.
|
/// </param>
|
||||||
/// </param>
|
/// <param name="comparer">
|
||||||
/// <param name="comparer">
|
/// Comparer used to identify and compare items to each other
|
||||||
/// Comparer used to identify and compare items to each other
|
/// </param>
|
||||||
/// </param>
|
public WeakCollection(
|
||||||
public WeakCollection(
|
IList<WeakReference<TItem>> items, IEqualityComparer<TItem> comparer
|
||||||
IList<WeakReference<TItem>> items, IEqualityComparer<TItem> comparer
|
) {
|
||||||
) {
|
this.items = items;
|
||||||
this.items = items;
|
this.comparer = comparer;
|
||||||
this.comparer = comparer;
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Determines whether an element is in the WeakCollection
|
||||||
/// Determines whether an element is in the WeakCollection
|
/// </summary>
|
||||||
/// </summary>
|
/// <param name="item">
|
||||||
/// <param name="item">
|
/// The object to locate in the WeakCollection. The value can be null.
|
||||||
/// The object to locate in the WeakCollection. The value can be null.
|
/// </param>
|
||||||
/// </param>
|
/// <returns>
|
||||||
/// <returns>
|
/// True if value is found in the WeakCollection; otherwise, false.
|
||||||
/// True if value is found in the WeakCollection; otherwise, false.
|
/// </returns>
|
||||||
/// </returns>
|
/// <remarks>
|
||||||
/// <remarks>
|
/// The default implementation of this method is very unoptimized and will
|
||||||
/// The default implementation of this method is very unoptimized and will
|
/// enumerate all the items in the collection, transforming one after another
|
||||||
/// enumerate all the items in the collection, transforming one after another
|
/// to check whether the transformed item matches the item the user was
|
||||||
/// to check whether the transformed item matches the item the user was
|
/// looking for. It is recommended to provide a custom implementation of
|
||||||
/// looking for. It is recommended to provide a custom implementation of
|
/// this method, if possible.
|
||||||
/// this method, if possible.
|
/// </remarks>
|
||||||
/// </remarks>
|
public virtual bool Contains(TItem item) {
|
||||||
public virtual bool Contains(TItem item) {
|
return (IndexOf(item) != -1);
|
||||||
return (IndexOf(item) != -1);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Copies the entire WeakCollection to a compatible one-dimensional
|
||||||
/// Copies the entire WeakCollection to a compatible one-dimensional
|
/// System.Array, starting at the specified index of the target array.
|
||||||
/// System.Array, starting at the specified index of the target array.
|
/// </summary>
|
||||||
/// </summary>
|
/// <param name="array">
|
||||||
/// <param name="array">
|
/// The one-dimensional System.Array that is the destination of the elements copied
|
||||||
/// The one-dimensional System.Array that is the destination of the elements copied
|
/// from the WeakCollection. The System.Array must have zero-based indexing.
|
||||||
/// from the WeakCollection. The System.Array must have zero-based indexing.
|
/// </param>
|
||||||
/// </param>
|
/// <param name="index">
|
||||||
/// <param name="index">
|
/// The zero-based index in array at which copying begins.
|
||||||
/// The zero-based index in array at which copying begins.
|
/// </param>
|
||||||
/// </param>
|
/// <exception cref="System.ArgumentException">
|
||||||
/// <exception cref="System.ArgumentException">
|
/// Index is equal to or greater than the length of array or the number of elements
|
||||||
/// Index is equal to or greater than the length of array or the number of elements
|
/// in the source WeakCollection is greater than the available space from index to
|
||||||
/// in the source WeakCollection is greater than the available space from index to
|
/// the end of the destination array.
|
||||||
/// the end of the destination array.
|
/// </exception>
|
||||||
/// </exception>
|
/// <exception cref="System.ArgumentOutOfRangeException">
|
||||||
/// <exception cref="System.ArgumentOutOfRangeException">
|
/// Index is less than zero.
|
||||||
/// Index is less than zero.
|
/// </exception>
|
||||||
/// </exception>
|
/// <exception cref="System.ArgumentNullException">
|
||||||
/// <exception cref="System.ArgumentNullException">
|
/// Array is null.
|
||||||
/// Array is null.
|
/// </exception>
|
||||||
/// </exception>
|
public void CopyTo(TItem[] array, int index) {
|
||||||
public void CopyTo(TItem[] array, int index) {
|
if(this.items.Count > (array.Length - index)) {
|
||||||
if(this.items.Count > (array.Length - index)) {
|
throw new ArgumentException(
|
||||||
throw new ArgumentException(
|
"Array too small to fit the collection items starting at the specified index"
|
||||||
"Array too small to fit the collection items starting at the specified index"
|
);
|
||||||
);
|
}
|
||||||
}
|
|
||||||
|
for(int itemIndex = 0; itemIndex < this.items.Count; ++itemIndex) {
|
||||||
for(int itemIndex = 0; itemIndex < this.items.Count; ++itemIndex) {
|
array[itemIndex + index] = this.items[itemIndex].Target;
|
||||||
array[itemIndex + index] = this.items[itemIndex].Target;
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Removes all items from the WeakCollection</summary>
|
||||||
/// <summary>Removes all items from the WeakCollection</summary>
|
public void Clear() {
|
||||||
public void Clear() {
|
this.items.Clear();
|
||||||
this.items.Clear();
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Returns an enumerator that iterates through the WeakCollection.
|
||||||
/// Returns an enumerator that iterates through the WeakCollection.
|
/// </summary>
|
||||||
/// </summary>
|
/// <returns>An enumerator or the WeakCollection.</returns>
|
||||||
/// <returns>An enumerator or the WeakCollection.</returns>
|
public IEnumerator<TItem> GetEnumerator() {
|
||||||
public IEnumerator<TItem> GetEnumerator() {
|
return new UnpackingEnumerator(this.items.GetEnumerator());
|
||||||
return new UnpackingEnumerator(this.items.GetEnumerator());
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Searches for the specified object and returns the zero-based index of the
|
||||||
/// Searches for the specified object and returns the zero-based index of the
|
/// first occurrence within the entire WeakCollection.
|
||||||
/// first occurrence within the entire WeakCollection.
|
/// </summary>
|
||||||
/// </summary>
|
/// <param name="item">
|
||||||
/// <param name="item">
|
/// The object to locate in the WeakCollection. The value can
|
||||||
/// The object to locate in the WeakCollection. The value can
|
/// be null for reference types.
|
||||||
/// be null for reference types.
|
/// </param>
|
||||||
/// </param>
|
/// <returns>
|
||||||
/// <returns>
|
/// The zero-based index of the first occurrence of item within the entire
|
||||||
/// The zero-based index of the first occurrence of item within the entire
|
/// WeakCollection, if found; otherwise, -1.
|
||||||
/// WeakCollection, if found; otherwise, -1.
|
/// </returns>
|
||||||
/// </returns>
|
/// <remarks>
|
||||||
/// <remarks>
|
/// The default implementation of this method is very unoptimized and will
|
||||||
/// The default implementation of this method is very unoptimized and will
|
/// enumerate all the items in the collection, transforming one after another
|
||||||
/// enumerate all the items in the collection, transforming one after another
|
/// to check whether the transformed item matches the item the user was
|
||||||
/// to check whether the transformed item matches the item the user was
|
/// looking for. It is recommended to provide a custom implementation of
|
||||||
/// looking for. It is recommended to provide a custom implementation of
|
/// this method, if possible.
|
||||||
/// this method, if possible.
|
/// </remarks>
|
||||||
/// </remarks>
|
public int IndexOf(TItem item) {
|
||||||
public int IndexOf(TItem item) {
|
for(int index = 0; index < this.items.Count; ++index) {
|
||||||
for(int index = 0; index < this.items.Count; ++index) {
|
TItem itemAtIndex = this.items[index].Target;
|
||||||
TItem itemAtIndex = this.items[index].Target;
|
if((itemAtIndex == null) || (item == null)) {
|
||||||
if((itemAtIndex == null) || (item == null)) {
|
if(ReferenceEquals(item, itemAtIndex)) {
|
||||||
if(ReferenceEquals(item, itemAtIndex)) {
|
return index;
|
||||||
return index;
|
}
|
||||||
}
|
} else {
|
||||||
} else {
|
if(this.comparer.Equals(itemAtIndex, item)) {
|
||||||
if(this.comparer.Equals(itemAtIndex, item)) {
|
return index;
|
||||||
return index;
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
return -1;
|
||||||
return -1;
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// The number of elements contained in the WeakCollection instance
|
||||||
/// The number of elements contained in the WeakCollection instance
|
/// </summary>
|
||||||
/// </summary>
|
public int Count {
|
||||||
public int Count {
|
get { return this.items.Count; }
|
||||||
get { return this.items.Count; }
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Gets the element at the specified index.</summary>
|
||||||
/// <summary>Gets the element at the specified index.</summary>
|
/// <param name="index">The zero-based index of the element to get.</param>
|
||||||
/// <param name="index">The zero-based index of the element to get.</param>
|
/// <returns>The element at the specified index.</returns>
|
||||||
/// <returns>The element at the specified index.</returns>
|
/// <exception cref="System.ArgumentOutOfRangeException">
|
||||||
/// <exception cref="System.ArgumentOutOfRangeException">
|
/// Index is less than zero or index is equal to or greater than
|
||||||
/// Index is less than zero or index is equal to or greater than
|
/// WeakCollection.Count.
|
||||||
/// WeakCollection.Count.
|
/// </exception>
|
||||||
/// </exception>
|
public TItem this[int index] {
|
||||||
public TItem this[int index] {
|
get { return this.items[index].Target; }
|
||||||
get { return this.items[index].Target; }
|
set { this.items[index] = new WeakReference<TItem>(value); }
|
||||||
set { this.items[index] = new WeakReference<TItem>(value); }
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Removes the first occurrence of a specific object from the WeakCollection.
|
||||||
/// Removes the first occurrence of a specific object from the WeakCollection.
|
/// </summary>
|
||||||
/// </summary>
|
/// <param name="item">The object to remove from the WeakCollection</param>
|
||||||
/// <param name="item">The object to remove from the WeakCollection</param>
|
/// <returns>
|
||||||
/// <returns>
|
/// True if item was successfully removed from the WeakCollection; otherwise, false.
|
||||||
/// True if item was successfully removed from the WeakCollection; otherwise, false.
|
/// </returns>
|
||||||
/// </returns>
|
public bool Remove(TItem item) {
|
||||||
public bool Remove(TItem item) {
|
for(int index = 0; index < this.items.Count; ++index) {
|
||||||
for(int index = 0; index < this.items.Count; ++index) {
|
TItem itemAtIndex = this.items[index].Target;
|
||||||
TItem itemAtIndex = this.items[index].Target;
|
if((itemAtIndex == null) || (item == null)) {
|
||||||
if((itemAtIndex == null) || (item == null)) {
|
if(ReferenceEquals(item, itemAtIndex)) {
|
||||||
if(ReferenceEquals(item, itemAtIndex)) {
|
this.items.RemoveAt(index);
|
||||||
this.items.RemoveAt(index);
|
return true;
|
||||||
return true;
|
}
|
||||||
}
|
} else {
|
||||||
} else {
|
if(this.comparer.Equals(item, itemAtIndex)) {
|
||||||
if(this.comparer.Equals(item, itemAtIndex)) {
|
this.items.RemoveAt(index);
|
||||||
this.items.RemoveAt(index);
|
return true;
|
||||||
return true;
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
return false;
|
||||||
return false;
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Adds an item to the WeakCollection.</summary>
|
||||||
/// <summary>Adds an item to the WeakCollection.</summary>
|
/// <param name="item">The object to add to the WeakCollection</param>
|
||||||
/// <param name="item">The object to add to the WeakCollection</param>
|
public void Add(TItem item) {
|
||||||
public void Add(TItem item) {
|
this.items.Add(new WeakReference<TItem>(item));
|
||||||
this.items.Add(new WeakReference<TItem>(item));
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Inserts an item to the WeakCollection at the specified index.</summary>
|
||||||
/// <summary>Inserts an item to the WeakCollection at the specified index.</summary>
|
/// <param name="index">
|
||||||
/// <param name="index">
|
/// The zero-based index at which item should be inserted.
|
||||||
/// The zero-based index at which item should be inserted.
|
/// </param>
|
||||||
/// </param>
|
/// <param name="item">The object to insert into the WeakCollection</param>
|
||||||
/// <param name="item">The object to insert into the WeakCollection</param>
|
/// <exception cref="System.ArgumentOutOfRangeException">
|
||||||
/// <exception cref="System.ArgumentOutOfRangeException">
|
/// index is not a valid index in the WeakCollection.
|
||||||
/// index is not a valid index in the WeakCollection.
|
/// </exception>
|
||||||
/// </exception>
|
public void Insert(int index, TItem item) {
|
||||||
public void Insert(int index, TItem item) {
|
this.items.Insert(index, new WeakReference<TItem>(item));
|
||||||
this.items.Insert(index, new WeakReference<TItem>(item));
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Removes the WeakCollection item at the specified index.
|
||||||
/// Removes the WeakCollection item at the specified index.
|
/// </summary>
|
||||||
/// </summary>
|
/// <param name="index">The zero-based index of the item to remove.</param>
|
||||||
/// <param name="index">The zero-based index of the item to remove.</param>
|
/// <exception cref="System.ArgumentOutOfRangeException">
|
||||||
/// <exception cref="System.ArgumentOutOfRangeException">
|
/// Index is not a valid index in the WeakCollection.
|
||||||
/// Index is not a valid index in the WeakCollection.
|
/// </exception>
|
||||||
/// </exception>
|
public void RemoveAt(int index) {
|
||||||
public void RemoveAt(int index) {
|
this.items.RemoveAt(index);
|
||||||
this.items.RemoveAt(index);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Whether the List is write-protected</summary>
|
||||||
/// <summary>Whether the List is write-protected</summary>
|
public bool IsReadOnly {
|
||||||
public bool IsReadOnly {
|
get { return this.items.IsReadOnly; }
|
||||||
get { return this.items.IsReadOnly; }
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Removes the items that have been garbage collected from the collection
|
||||||
/// Removes the items that have been garbage collected from the collection
|
/// </summary>
|
||||||
/// </summary>
|
public void RemoveDeadItems() {
|
||||||
public void RemoveDeadItems() {
|
int newCount = 0;
|
||||||
int newCount = 0;
|
|
||||||
|
// Eliminate all items that have been garbage collected by shifting
|
||||||
// Eliminate all items that have been garbage collected by shifting
|
for(int index = 0; index < this.items.Count; ++index) {
|
||||||
for(int index = 0; index < this.items.Count; ++index) {
|
if(this.items[index].IsAlive) {
|
||||||
if(this.items[index].IsAlive) {
|
this.items[newCount] = this.items[index];
|
||||||
this.items[newCount] = this.items[index];
|
++newCount;
|
||||||
++newCount;
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
// If any garbage collected items were found, resize the collection so
|
||||||
// If any garbage collected items were found, resize the collection so
|
// the space that became empty in the previous shifting process will be freed
|
||||||
// the space that became empty in the previous shifting process will be freed
|
while(this.items.Count > newCount) {
|
||||||
while(this.items.Count > newCount) {
|
this.items.RemoveAt(this.items.Count - 1);
|
||||||
this.items.RemoveAt(this.items.Count - 1);
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Weak references to the items contained in the collection</summary>
|
||||||
/// <summary>Weak references to the items contained in the collection</summary>
|
private IList<WeakReference<TItem>> items;
|
||||||
private IList<WeakReference<TItem>> items;
|
/// <summary>Used to identify and compare items in the collection</summary>
|
||||||
/// <summary>Used to identify and compare items in the collection</summary>
|
private IEqualityComparer<TItem> comparer;
|
||||||
private IEqualityComparer<TItem> comparer;
|
/// <summary>Synchronization root for threaded accesses to this collection</summary>
|
||||||
/// <summary>Synchronization root for threaded accesses to this collection</summary>
|
private object syncRoot;
|
||||||
private object syncRoot;
|
|
||||||
|
}
|
||||||
}
|
|
||||||
|
} // namespace Nuclex.Support.Collections
|
||||||
} // namespace Nuclex.Support.Collections
|
|
||||||
|
|
|
@ -1,124 +1,123 @@
|
||||||
#region CPL License
|
#region Apache License 2.0
|
||||||
/*
|
/*
|
||||||
Nuclex Framework
|
Nuclex .NET Framework
|
||||||
Copyright (C) 2002-2017 Nuclex Development Labs
|
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
|
||||||
|
|
||||||
This library is free software; you can redistribute it and/or
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
modify it under the terms of the IBM Common Public License as
|
you may not use this file except in compliance with the License.
|
||||||
published by the IBM Corporation; either version 1.0 of the
|
You may obtain a copy of the License at
|
||||||
License, or (at your option) any later version.
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
This library is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
Unless required by applicable law or agreed to in writing, software
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
IBM Common Public License for more details.
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
You should have received a copy of the IBM Common Public
|
limitations under the License.
|
||||||
License along with this library
|
*/
|
||||||
*/
|
#endregion // Apache License 2.0
|
||||||
#endregion
|
|
||||||
|
#if UNITTEST
|
||||||
#if UNITTEST
|
|
||||||
|
using System;
|
||||||
using System;
|
using System.IO;
|
||||||
using System.IO;
|
|
||||||
|
using NUnit.Framework;
|
||||||
using NUnit.Framework;
|
|
||||||
|
namespace Nuclex.Support {
|
||||||
namespace Nuclex.Support {
|
|
||||||
|
/// <summary>Unit Test for the enumeration helper class</summary>
|
||||||
/// <summary>Unit Test for the enumeration helper class</summary>
|
[TestFixture]
|
||||||
[TestFixture]
|
internal class EnumHelperTest {
|
||||||
internal class EnumHelperTest {
|
|
||||||
|
#region enum TestEnumeration
|
||||||
#region enum TestEnumeration
|
|
||||||
|
/// <summary>An enumeration used for unit testing</summary>
|
||||||
/// <summary>An enumeration used for unit testing</summary>
|
internal enum TestEnumeration {
|
||||||
internal enum TestEnumeration {
|
/// <summary>First arbitrary enumeration value</summary>
|
||||||
/// <summary>First arbitrary enumeration value</summary>
|
One = -2,
|
||||||
One = -2,
|
/// <summary>Third arbitrary enumeration value</summary>
|
||||||
/// <summary>Third arbitrary enumeration value</summary>
|
Three = 33,
|
||||||
Three = 33,
|
/// <summary>Second arbitrary enumeration value</summary>
|
||||||
/// <summary>Second arbitrary enumeration value</summary>
|
Two = 23
|
||||||
Two = 23
|
}
|
||||||
}
|
|
||||||
|
#endregion // enum TestEnumeration
|
||||||
#endregion // enum TestEnumeration
|
|
||||||
|
#region enum EmptyEnumeration
|
||||||
#region enum EmptyEnumeration
|
|
||||||
|
internal enum EmptyEnumeration { }
|
||||||
internal enum EmptyEnumeration { }
|
|
||||||
|
#endregion // enum EmptyEnumeration
|
||||||
#endregion // enum EmptyEnumeration
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Verifies that the enum helper can list the members of an enumeration
|
||||||
/// Verifies that the enum helper can list the members of an enumeration
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void TestGetValues() {
|
||||||
public void TestGetValues() {
|
CollectionAssert.AreEquivalent(
|
||||||
CollectionAssert.AreEquivalent(
|
new TestEnumeration[] {
|
||||||
new TestEnumeration[] {
|
TestEnumeration.One, TestEnumeration.Two, TestEnumeration.Three
|
||||||
TestEnumeration.One, TestEnumeration.Two, TestEnumeration.Three
|
},
|
||||||
},
|
EnumHelper.GetValues<TestEnumeration>()
|
||||||
EnumHelper.GetValues<TestEnumeration>()
|
);
|
||||||
);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Verifies that the enum helper can locate the highest value in an enumeration
|
||||||
/// Verifies that the enum helper can locate the highest value in an enumeration
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void TestGetHighestValue() {
|
||||||
public void TestGetHighestValue() {
|
Assert.AreEqual(
|
||||||
Assert.AreEqual(
|
TestEnumeration.Three, EnumHelper.GetHighestValue<TestEnumeration>()
|
||||||
TestEnumeration.Three, EnumHelper.GetHighestValue<TestEnumeration>()
|
);
|
||||||
);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Verifies that the enum helper can locate the lowest value in an enumeration
|
||||||
/// Verifies that the enum helper can locate the lowest value in an enumeration
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void TestGetLowestValue() {
|
||||||
public void TestGetLowestValue() {
|
Assert.AreEqual(
|
||||||
Assert.AreEqual(
|
TestEnumeration.One, EnumHelper.GetLowestValue<TestEnumeration>()
|
||||||
TestEnumeration.One, EnumHelper.GetLowestValue<TestEnumeration>()
|
);
|
||||||
);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Tests whether an exception is thrown if the GetValues() method is used on
|
||||||
/// Tests whether an exception is thrown if the GetValues() method is used on
|
/// a non-enumeration type
|
||||||
/// a non-enumeration type
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void TestThrowOnNonEnumType() {
|
||||||
public void TestThrowOnNonEnumType() {
|
Assert.Throws<ArgumentException>(
|
||||||
Assert.Throws<ArgumentException>(
|
delegate() { EnumHelper.GetValues<int>(); }
|
||||||
delegate() { EnumHelper.GetValues<int>(); }
|
);
|
||||||
);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Verifies that the default value for an enumeration is returned if
|
||||||
/// Verifies that the default value for an enumeration is returned if
|
/// the GetLowestValue() method is used on an empty enumeration
|
||||||
/// the GetLowestValue() method is used on an empty enumeration
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void TestLowestValueInEmptyEnumeration() {
|
||||||
public void TestLowestValueInEmptyEnumeration() {
|
Assert.AreEqual(
|
||||||
Assert.AreEqual(
|
default(EmptyEnumeration), EnumHelper.GetLowestValue<EmptyEnumeration>()
|
||||||
default(EmptyEnumeration), EnumHelper.GetLowestValue<EmptyEnumeration>()
|
);
|
||||||
);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Verifies that the default value for an enumeration is returned if
|
||||||
/// Verifies that the default value for an enumeration is returned if
|
/// the GetHighestValue() method is used on an empty enumeration
|
||||||
/// the GetHighestValue() method is used on an empty enumeration
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void TestHighestValueInEmptyEnumeration() {
|
||||||
public void TestHighestValueInEmptyEnumeration() {
|
Assert.AreEqual(
|
||||||
Assert.AreEqual(
|
default(EmptyEnumeration), EnumHelper.GetHighestValue<EmptyEnumeration>()
|
||||||
default(EmptyEnumeration), EnumHelper.GetHighestValue<EmptyEnumeration>()
|
);
|
||||||
);
|
}
|
||||||
}
|
|
||||||
|
}
|
||||||
}
|
|
||||||
|
} // namespace Nuclex.Support
|
||||||
} // namespace Nuclex.Support
|
|
||||||
|
#endif // UNITTEST
|
||||||
#endif // UNITTEST
|
|
||||||
|
|
|
@ -1,97 +1,96 @@
|
||||||
#region CPL License
|
#region Apache License 2.0
|
||||||
/*
|
/*
|
||||||
Nuclex Framework
|
Nuclex .NET Framework
|
||||||
Copyright (C) 2002-2017 Nuclex Development Labs
|
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
|
||||||
|
|
||||||
This library is free software; you can redistribute it and/or
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
modify it under the terms of the IBM Common Public License as
|
you may not use this file except in compliance with the License.
|
||||||
published by the IBM Corporation; either version 1.0 of the
|
You may obtain a copy of the License at
|
||||||
License, or (at your option) any later version.
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
This library is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
Unless required by applicable law or agreed to in writing, software
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
IBM Common Public License for more details.
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
You should have received a copy of the IBM Common Public
|
limitations under the License.
|
||||||
License along with this library
|
*/
|
||||||
*/
|
#endregion // Apache License 2.0
|
||||||
#endregion
|
|
||||||
|
using System;
|
||||||
using System;
|
|
||||||
|
namespace Nuclex.Support {
|
||||||
namespace Nuclex.Support {
|
|
||||||
|
/// <summary>Helper methods for enumerations</summary>
|
||||||
/// <summary>Helper methods for enumerations</summary>
|
public static class EnumHelper {
|
||||||
public static class EnumHelper {
|
|
||||||
|
/// <summary>Returns the highest value encountered in an enumeration</summary>
|
||||||
/// <summary>Returns the highest value encountered in an enumeration</summary>
|
/// <typeparam name="TEnumeration">
|
||||||
/// <typeparam name="TEnumeration">
|
/// Enumeration of which the highest value will be returned
|
||||||
/// Enumeration of which the highest value will be returned
|
/// </typeparam>
|
||||||
/// </typeparam>
|
/// <returns>The highest value in the enumeration</returns>
|
||||||
/// <returns>The highest value in the enumeration</returns>
|
public static TEnumeration GetHighestValue<TEnumeration>()
|
||||||
public static TEnumeration GetHighestValue<TEnumeration>()
|
where TEnumeration : IComparable {
|
||||||
where TEnumeration : IComparable {
|
TEnumeration[] values = GetValues<TEnumeration>();
|
||||||
TEnumeration[] values = GetValues<TEnumeration>();
|
|
||||||
|
// If the enumeration is empty, return nothing
|
||||||
// If the enumeration is empty, return nothing
|
if(values.Length == 0) {
|
||||||
if(values.Length == 0) {
|
return default(TEnumeration);
|
||||||
return default(TEnumeration);
|
}
|
||||||
}
|
|
||||||
|
// Look for the highest value in the enumeration. We initialize the highest value
|
||||||
// Look for the highest value in the enumeration. We initialize the highest value
|
// to the first enumeration value so we don't have to use some arbitrary starting
|
||||||
// to the first enumeration value so we don't have to use some arbitrary starting
|
// value which might actually appear in the enumeration.
|
||||||
// value which might actually appear in the enumeration.
|
TEnumeration highestValue = values[0];
|
||||||
TEnumeration highestValue = values[0];
|
for(int index = 1; index < values.Length; ++index) {
|
||||||
for(int index = 1; index < values.Length; ++index) {
|
if(values[index].CompareTo(highestValue) > 0) {
|
||||||
if(values[index].CompareTo(highestValue) > 0) {
|
highestValue = values[index];
|
||||||
highestValue = values[index];
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
return highestValue;
|
||||||
return highestValue;
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Returns the lowest value encountered in an enumeration</summary>
|
||||||
/// <summary>Returns the lowest value encountered in an enumeration</summary>
|
/// <typeparam name="TEnumeration">
|
||||||
/// <typeparam name="TEnumeration">
|
/// Enumeration of which the lowest value will be returned
|
||||||
/// Enumeration of which the lowest value will be returned
|
/// </typeparam>
|
||||||
/// </typeparam>
|
/// <returns>The lowest value in the enumeration</returns>
|
||||||
/// <returns>The lowest value in the enumeration</returns>
|
public static TEnumeration GetLowestValue<TEnumeration>()
|
||||||
public static TEnumeration GetLowestValue<TEnumeration>()
|
where TEnumeration : IComparable {
|
||||||
where TEnumeration : IComparable {
|
TEnumeration[] values = GetValues<TEnumeration>();
|
||||||
TEnumeration[] values = GetValues<TEnumeration>();
|
|
||||||
|
// If the enumeration is empty, return nothing
|
||||||
// If the enumeration is empty, return nothing
|
if(values.Length == 0) {
|
||||||
if(values.Length == 0) {
|
return default(TEnumeration);
|
||||||
return default(TEnumeration);
|
}
|
||||||
}
|
|
||||||
|
// Look for the lowest value in the enumeration. We initialize the lowest value
|
||||||
// Look for the lowest value in the enumeration. We initialize the lowest value
|
// to the first enumeration value so we don't have to use some arbitrary starting
|
||||||
// to the first enumeration value so we don't have to use some arbitrary starting
|
// value which might actually appear in the enumeration.
|
||||||
// value which might actually appear in the enumeration.
|
TEnumeration lowestValue = values[0];
|
||||||
TEnumeration lowestValue = values[0];
|
for(int index = 1; index < values.Length; ++index) {
|
||||||
for(int index = 1; index < values.Length; ++index) {
|
if(values[index].CompareTo(lowestValue) < 0) {
|
||||||
if(values[index].CompareTo(lowestValue) < 0) {
|
lowestValue = values[index];
|
||||||
lowestValue = values[index];
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
return lowestValue;
|
||||||
return lowestValue;
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Retrieves a list of all values contained in an enumeration</summary>
|
||||||
/// <summary>Retrieves a list of all values contained in an enumeration</summary>
|
/// <typeparam name="TEnum">
|
||||||
/// <typeparam name="TEnum">
|
/// Type of the enumeration whose values will be returned
|
||||||
/// Type of the enumeration whose values will be returned
|
/// </typeparam>
|
||||||
/// </typeparam>
|
/// <returns>All values contained in the specified enumeration</returns>
|
||||||
/// <returns>All values contained in the specified enumeration</returns>
|
/// <remarks>
|
||||||
/// <remarks>
|
/// This method produces collectable garbage so it's best to only call it once
|
||||||
/// This method produces collectable garbage so it's best to only call it once
|
/// and cache the result.
|
||||||
/// and cache the result.
|
/// </remarks>
|
||||||
/// </remarks>
|
public static TEnum[] GetValues<TEnum>() {
|
||||||
public static TEnum[] GetValues<TEnum>() {
|
return (TEnum[])Enum.GetValues(typeof(TEnum));
|
||||||
return (TEnum[])Enum.GetValues(typeof(TEnum));
|
}
|
||||||
}
|
|
||||||
|
}
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace Nuclex.Support
|
} // namespace Nuclex.Support
|
|
@ -1,266 +1,265 @@
|
||||||
#region CPL License
|
#region Apache License 2.0
|
||||||
/*
|
/*
|
||||||
Nuclex Framework
|
Nuclex .NET Framework
|
||||||
Copyright (C) 2002-2017 Nuclex Development Labs
|
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
|
||||||
|
|
||||||
This library is free software; you can redistribute it and/or
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
modify it under the terms of the IBM Common Public License as
|
you may not use this file except in compliance with the License.
|
||||||
published by the IBM Corporation; either version 1.0 of the
|
You may obtain a copy of the License at
|
||||||
License, or (at your option) any later version.
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
This library is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
Unless required by applicable law or agreed to in writing, software
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
IBM Common Public License for more details.
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
You should have received a copy of the IBM Common Public
|
limitations under the License.
|
||||||
License along with this library
|
*/
|
||||||
*/
|
#endregion // Apache License 2.0
|
||||||
#endregion
|
|
||||||
|
#if UNITTEST
|
||||||
#if UNITTEST
|
|
||||||
|
using System;
|
||||||
using System;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Generic;
|
|
||||||
|
using NUnit.Framework;
|
||||||
using NUnit.Framework;
|
|
||||||
|
namespace Nuclex.Support {
|
||||||
namespace Nuclex.Support {
|
|
||||||
|
/// <summary>Unit Test for the FloatHelper class</summary>
|
||||||
/// <summary>Unit Test for the FloatHelper class</summary>
|
[TestFixture]
|
||||||
[TestFixture]
|
internal class FloatHelperTest {
|
||||||
internal class FloatHelperTest {
|
|
||||||
|
/// <summary>Tests the floating point value comparison helper</summary>
|
||||||
/// <summary>Tests the floating point value comparison helper</summary>
|
[Test]
|
||||||
[Test]
|
public void UlpDistancesOnFloatsCompareAsEqual() {
|
||||||
public void UlpDistancesOnFloatsCompareAsEqual() {
|
Assert.IsTrue(
|
||||||
Assert.IsTrue(
|
FloatHelper.AreAlmostEqual(0.00000001f, 0.0000000100000008f, 1),
|
||||||
FloatHelper.AreAlmostEqual(0.00000001f, 0.0000000100000008f, 1),
|
"Minimal difference between very small floating point numbers is considered equal"
|
||||||
"Minimal difference between very small floating point numbers is considered equal"
|
);
|
||||||
);
|
Assert.IsFalse(
|
||||||
Assert.IsFalse(
|
FloatHelper.AreAlmostEqual(0.00000001f, 0.0000000100000017f, 1),
|
||||||
FloatHelper.AreAlmostEqual(0.00000001f, 0.0000000100000017f, 1),
|
"Larger difference between very small floating point numbers is not considered equal"
|
||||||
"Larger difference between very small floating point numbers is not considered equal"
|
);
|
||||||
);
|
|
||||||
|
Assert.IsTrue(
|
||||||
Assert.IsTrue(
|
FloatHelper.AreAlmostEqual(1000000.00f, 1000000.06f, 1),
|
||||||
FloatHelper.AreAlmostEqual(1000000.00f, 1000000.06f, 1),
|
"Minimal difference between very large floating point numbers is considered equal"
|
||||||
"Minimal difference between very large floating point numbers is considered equal"
|
);
|
||||||
);
|
Assert.IsFalse(
|
||||||
Assert.IsFalse(
|
FloatHelper.AreAlmostEqual(1000000.00f, 1000000.13f, 1),
|
||||||
FloatHelper.AreAlmostEqual(1000000.00f, 1000000.13f, 1),
|
"Larger difference between very large floating point numbers is not considered equal"
|
||||||
"Larger difference between very large floating point numbers is not considered equal"
|
);
|
||||||
);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Tests the double precision floating point value comparison helper</summary>
|
||||||
/// <summary>Tests the double precision floating point value comparison helper</summary>
|
[Test]
|
||||||
[Test]
|
public void UlpDistancesOnDoublesCompareAsEqual() {
|
||||||
public void UlpDistancesOnDoublesCompareAsEqual() {
|
Assert.IsTrue(
|
||||||
Assert.IsTrue(
|
FloatHelper.AreAlmostEqual(0.00000001, 0.000000010000000000000002, 1),
|
||||||
FloatHelper.AreAlmostEqual(0.00000001, 0.000000010000000000000002, 1),
|
"Minimal difference between very small double precision floating point " +
|
||||||
"Minimal difference between very small double precision floating point " +
|
"numbers is considered equal"
|
||||||
"numbers is considered equal"
|
);
|
||||||
);
|
Assert.IsFalse(
|
||||||
Assert.IsFalse(
|
FloatHelper.AreAlmostEqual(0.00000001, 0.000000010000000000000004, 1),
|
||||||
FloatHelper.AreAlmostEqual(0.00000001, 0.000000010000000000000004, 1),
|
"Larger difference between very small double precision floating point " +
|
||||||
"Larger difference between very small double precision floating point " +
|
"numbers is not considered equal"
|
||||||
"numbers is not considered equal"
|
);
|
||||||
);
|
|
||||||
|
Assert.IsTrue(
|
||||||
Assert.IsTrue(
|
FloatHelper.AreAlmostEqual(1000000.00, 1000000.0000000001, 1),
|
||||||
FloatHelper.AreAlmostEqual(1000000.00, 1000000.0000000001, 1),
|
"Minimal difference between very large double precision floating point " +
|
||||||
"Minimal difference between very large double precision floating point " +
|
"numbers is considered equal"
|
||||||
"numbers is considered equal"
|
);
|
||||||
);
|
Assert.IsFalse(
|
||||||
Assert.IsFalse(
|
FloatHelper.AreAlmostEqual(1000000.00, 1000000.0000000002, 1),
|
||||||
FloatHelper.AreAlmostEqual(1000000.00, 1000000.0000000002, 1),
|
"Larger difference between very large double precision floating point " +
|
||||||
"Larger difference between very large double precision floating point " +
|
"numbers is not considered equal"
|
||||||
"numbers is not considered equal"
|
);
|
||||||
);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Tests the integer reinterpretation functions</summary>
|
||||||
/// <summary>Tests the integer reinterpretation functions</summary>
|
[Test]
|
||||||
[Test]
|
public void IntegersCanBeReinterpretedAsFloats() {
|
||||||
public void IntegersCanBeReinterpretedAsFloats() {
|
Assert.AreEqual(
|
||||||
Assert.AreEqual(
|
12345.0f,
|
||||||
12345.0f,
|
FloatHelper.ReinterpretAsFloat(FloatHelper.ReinterpretAsInt(12345.0f)),
|
||||||
FloatHelper.ReinterpretAsFloat(FloatHelper.ReinterpretAsInt(12345.0f)),
|
"Number hasn't changed after mirrored reinterpretation"
|
||||||
"Number hasn't changed after mirrored reinterpretation"
|
);
|
||||||
);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Tests the long reinterpretation functions</summary>
|
||||||
/// <summary>Tests the long reinterpretation functions</summary>
|
[Test]
|
||||||
[Test]
|
public void LongsCanBeReinterpretedAsDoubles() {
|
||||||
public void LongsCanBeReinterpretedAsDoubles() {
|
Assert.AreEqual(
|
||||||
Assert.AreEqual(
|
12345.67890,
|
||||||
12345.67890,
|
FloatHelper.ReinterpretAsDouble(FloatHelper.ReinterpretAsLong(12345.67890)),
|
||||||
FloatHelper.ReinterpretAsDouble(FloatHelper.ReinterpretAsLong(12345.67890)),
|
"Number hasn't changed after mirrored reinterpretation"
|
||||||
"Number hasn't changed after mirrored reinterpretation"
|
);
|
||||||
);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Tests the floating point reinterpretation functions</summary>
|
||||||
/// <summary>Tests the floating point reinterpretation functions</summary>
|
[Test]
|
||||||
[Test]
|
public void FloatsCanBeReinterpretedAsIntegers() {
|
||||||
public void FloatsCanBeReinterpretedAsIntegers() {
|
Assert.AreEqual(
|
||||||
Assert.AreEqual(
|
12345,
|
||||||
12345,
|
FloatHelper.ReinterpretAsInt(FloatHelper.ReinterpretAsFloat(12345)),
|
||||||
FloatHelper.ReinterpretAsInt(FloatHelper.ReinterpretAsFloat(12345)),
|
"Number hasn't changed after mirrored reinterpretation"
|
||||||
"Number hasn't changed after mirrored reinterpretation"
|
);
|
||||||
);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Verifies that the IsZero() method can distinguish zero from very small values
|
||||||
/// Verifies that the IsZero() method can distinguish zero from very small values
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void CanDetermineIfFloatIsZero() {
|
||||||
public void CanDetermineIfFloatIsZero() {
|
Assert.IsTrue(FloatHelper.IsZero(FloatHelper.PositiveZeroFloat));
|
||||||
Assert.IsTrue(FloatHelper.IsZero(FloatHelper.PositiveZeroFloat));
|
Assert.IsTrue(FloatHelper.IsZero(FloatHelper.NegativeZeroFloat));
|
||||||
Assert.IsTrue(FloatHelper.IsZero(FloatHelper.NegativeZeroFloat));
|
Assert.IsFalse(FloatHelper.IsZero(1.401298E-45f));
|
||||||
Assert.IsFalse(FloatHelper.IsZero(1.401298E-45f));
|
Assert.IsFalse(FloatHelper.IsZero(-1.401298E-45f));
|
||||||
Assert.IsFalse(FloatHelper.IsZero(-1.401298E-45f));
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Verifies that the IsZero() method can distinguish zero from very small values
|
||||||
/// Verifies that the IsZero() method can distinguish zero from very small values
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void CanDetermineIfDoubleIsZero() {
|
||||||
public void CanDetermineIfDoubleIsZero() {
|
Assert.IsTrue(FloatHelper.IsZero(FloatHelper.PositiveZeroDouble));
|
||||||
Assert.IsTrue(FloatHelper.IsZero(FloatHelper.PositiveZeroDouble));
|
Assert.IsTrue(FloatHelper.IsZero(FloatHelper.NegativeZeroDouble));
|
||||||
Assert.IsTrue(FloatHelper.IsZero(FloatHelper.NegativeZeroDouble));
|
Assert.IsFalse(FloatHelper.IsZero(4.94065645841247E-324));
|
||||||
Assert.IsFalse(FloatHelper.IsZero(4.94065645841247E-324));
|
Assert.IsFalse(FloatHelper.IsZero(-4.94065645841247E-324));
|
||||||
Assert.IsFalse(FloatHelper.IsZero(-4.94065645841247E-324));
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Tests the double prevision floating point reinterpretation functions
|
||||||
/// Tests the double prevision floating point reinterpretation functions
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void DoublesCanBeReinterpretedAsLongs() {
|
||||||
public void DoublesCanBeReinterpretedAsLongs() {
|
Assert.AreEqual(
|
||||||
Assert.AreEqual(
|
1234567890,
|
||||||
1234567890,
|
FloatHelper.ReinterpretAsLong(FloatHelper.ReinterpretAsDouble(1234567890)),
|
||||||
FloatHelper.ReinterpretAsLong(FloatHelper.ReinterpretAsDouble(1234567890)),
|
"Number hasn't changed after mirrored reinterpretation"
|
||||||
"Number hasn't changed after mirrored reinterpretation"
|
);
|
||||||
);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Verifies that two denormalized floats can be compared in ulps
|
||||||
/// Verifies that two denormalized floats can be compared in ulps
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void DenormalizedFloatsCanBeCompared() {
|
||||||
public void DenormalizedFloatsCanBeCompared() {
|
float zero = 0.0f;
|
||||||
float zero = 0.0f;
|
float zeroPlusOneUlp = FloatHelper.ReinterpretAsFloat(
|
||||||
float zeroPlusOneUlp = FloatHelper.ReinterpretAsFloat(
|
FloatHelper.ReinterpretAsInt(zero) + 1
|
||||||
FloatHelper.ReinterpretAsInt(zero) + 1
|
);
|
||||||
);
|
float zeroMinusOneUlp = -zeroPlusOneUlp;
|
||||||
float zeroMinusOneUlp = -zeroPlusOneUlp;
|
|
||||||
|
// Across zero
|
||||||
// Across zero
|
Assert.IsFalse(FloatHelper.AreAlmostEqual(zeroMinusOneUlp, zeroPlusOneUlp, 1));
|
||||||
Assert.IsFalse(FloatHelper.AreAlmostEqual(zeroMinusOneUlp, zeroPlusOneUlp, 1));
|
Assert.IsTrue(FloatHelper.AreAlmostEqual(zeroPlusOneUlp, zeroMinusOneUlp, 2));
|
||||||
Assert.IsTrue(FloatHelper.AreAlmostEqual(zeroPlusOneUlp, zeroMinusOneUlp, 2));
|
|
||||||
|
// Against zero
|
||||||
// Against zero
|
Assert.IsFalse(FloatHelper.AreAlmostEqual(zero, zeroPlusOneUlp, 0));
|
||||||
Assert.IsFalse(FloatHelper.AreAlmostEqual(zero, zeroPlusOneUlp, 0));
|
Assert.IsTrue(FloatHelper.AreAlmostEqual(zero, zeroPlusOneUlp, 1));
|
||||||
Assert.IsTrue(FloatHelper.AreAlmostEqual(zero, zeroPlusOneUlp, 1));
|
Assert.IsFalse(FloatHelper.AreAlmostEqual(zero, zeroMinusOneUlp, 0));
|
||||||
Assert.IsFalse(FloatHelper.AreAlmostEqual(zero, zeroMinusOneUlp, 0));
|
Assert.IsTrue(FloatHelper.AreAlmostEqual(zero, zeroMinusOneUlp, 1));
|
||||||
Assert.IsTrue(FloatHelper.AreAlmostEqual(zero, zeroMinusOneUlp, 1));
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Verifies that the negative floating point zero is within one ulp of the positive
|
||||||
/// Verifies that the negative floating point zero is within one ulp of the positive
|
/// floating point zero and vice versa
|
||||||
/// floating point zero and vice versa
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void NegativeZeroFloatEqualsPositiveZero() {
|
||||||
public void NegativeZeroFloatEqualsPositiveZero() {
|
Assert.IsTrue(
|
||||||
Assert.IsTrue(
|
FloatHelper.AreAlmostEqual(
|
||||||
FloatHelper.AreAlmostEqual(
|
FloatHelper.NegativeZeroFloat, FloatHelper.PositiveZeroFloat, 0
|
||||||
FloatHelper.NegativeZeroFloat, FloatHelper.PositiveZeroFloat, 0
|
)
|
||||||
)
|
);
|
||||||
);
|
Assert.IsTrue(
|
||||||
Assert.IsTrue(
|
FloatHelper.AreAlmostEqual(
|
||||||
FloatHelper.AreAlmostEqual(
|
FloatHelper.PositiveZeroFloat, FloatHelper.NegativeZeroFloat, 0
|
||||||
FloatHelper.PositiveZeroFloat, FloatHelper.NegativeZeroFloat, 0
|
)
|
||||||
)
|
);
|
||||||
);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Verifies that floats can be compared across the zero boundary</summary>
|
||||||
/// <summary>Verifies that floats can be compared across the zero boundary</summary>
|
[Test]
|
||||||
[Test]
|
public void FloatsCanBeComparedAcrossZeroInUlps() {
|
||||||
public void FloatsCanBeComparedAcrossZeroInUlps() {
|
float tenUlps = float.Epsilon * 10.0f;
|
||||||
float tenUlps = float.Epsilon * 10.0f;
|
|
||||||
|
Assert.IsTrue(FloatHelper.AreAlmostEqual(-tenUlps, tenUlps, 20));
|
||||||
Assert.IsTrue(FloatHelper.AreAlmostEqual(-tenUlps, tenUlps, 20));
|
Assert.IsTrue(FloatHelper.AreAlmostEqual(tenUlps, -tenUlps, 20));
|
||||||
Assert.IsTrue(FloatHelper.AreAlmostEqual(tenUlps, -tenUlps, 20));
|
Assert.IsFalse(FloatHelper.AreAlmostEqual(-tenUlps, tenUlps, 19));
|
||||||
Assert.IsFalse(FloatHelper.AreAlmostEqual(-tenUlps, tenUlps, 19));
|
|
||||||
|
Assert.IsTrue(FloatHelper.AreAlmostEqual(-tenUlps, 0, 10));
|
||||||
Assert.IsTrue(FloatHelper.AreAlmostEqual(-tenUlps, 0, 10));
|
Assert.IsTrue(FloatHelper.AreAlmostEqual(0, -tenUlps, 10));
|
||||||
Assert.IsTrue(FloatHelper.AreAlmostEqual(0, -tenUlps, 10));
|
Assert.IsFalse(FloatHelper.AreAlmostEqual(-tenUlps, 0, 9));
|
||||||
Assert.IsFalse(FloatHelper.AreAlmostEqual(-tenUlps, 0, 9));
|
|
||||||
|
Assert.IsTrue(FloatHelper.AreAlmostEqual(0, tenUlps, 10));
|
||||||
Assert.IsTrue(FloatHelper.AreAlmostEqual(0, tenUlps, 10));
|
Assert.IsTrue(FloatHelper.AreAlmostEqual(tenUlps, 0, 10));
|
||||||
Assert.IsTrue(FloatHelper.AreAlmostEqual(tenUlps, 0, 10));
|
Assert.IsFalse(FloatHelper.AreAlmostEqual(0, tenUlps, 9));
|
||||||
Assert.IsFalse(FloatHelper.AreAlmostEqual(0, tenUlps, 9));
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Verifies that two denormalized doubles can be compared in ulps
|
||||||
/// Verifies that two denormalized doubles can be compared in ulps
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void DenormalizedDoublesCanBeCompared() {
|
||||||
public void DenormalizedDoublesCanBeCompared() {
|
double zero = 0.0;
|
||||||
double zero = 0.0;
|
double zeroPlusOneUlp = FloatHelper.ReinterpretAsDouble(
|
||||||
double zeroPlusOneUlp = FloatHelper.ReinterpretAsDouble(
|
FloatHelper.ReinterpretAsLong(zero) + 1
|
||||||
FloatHelper.ReinterpretAsLong(zero) + 1
|
);
|
||||||
);
|
double zeroMinusOneUlp = -zeroPlusOneUlp;
|
||||||
double zeroMinusOneUlp = -zeroPlusOneUlp;
|
|
||||||
|
// Across zero
|
||||||
// Across zero
|
Assert.IsFalse(FloatHelper.AreAlmostEqual(zeroMinusOneUlp, zeroPlusOneUlp, 1));
|
||||||
Assert.IsFalse(FloatHelper.AreAlmostEqual(zeroMinusOneUlp, zeroPlusOneUlp, 1));
|
Assert.IsTrue(FloatHelper.AreAlmostEqual(zeroPlusOneUlp, zeroMinusOneUlp, 2));
|
||||||
Assert.IsTrue(FloatHelper.AreAlmostEqual(zeroPlusOneUlp, zeroMinusOneUlp, 2));
|
|
||||||
|
// Against zero
|
||||||
// Against zero
|
Assert.IsFalse(FloatHelper.AreAlmostEqual(zero, zeroPlusOneUlp, 0));
|
||||||
Assert.IsFalse(FloatHelper.AreAlmostEqual(zero, zeroPlusOneUlp, 0));
|
Assert.IsTrue(FloatHelper.AreAlmostEqual(zero, zeroPlusOneUlp, 1));
|
||||||
Assert.IsTrue(FloatHelper.AreAlmostEqual(zero, zeroPlusOneUlp, 1));
|
Assert.IsFalse(FloatHelper.AreAlmostEqual(zero, zeroMinusOneUlp, 0));
|
||||||
Assert.IsFalse(FloatHelper.AreAlmostEqual(zero, zeroMinusOneUlp, 0));
|
Assert.IsTrue(FloatHelper.AreAlmostEqual(zero, zeroMinusOneUlp, 1));
|
||||||
Assert.IsTrue(FloatHelper.AreAlmostEqual(zero, zeroMinusOneUlp, 1));
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Verifies that the negative double precision floating point zero is within one ulp
|
||||||
/// Verifies that the negative double precision floating point zero is within one ulp
|
/// of the positive double precision floating point zero and vice versa
|
||||||
/// of the positive double precision floating point zero and vice versa
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void NegativeZeroDoubleEqualsPositiveZero() {
|
||||||
public void NegativeZeroDoubleEqualsPositiveZero() {
|
Assert.IsTrue(
|
||||||
Assert.IsTrue(
|
FloatHelper.AreAlmostEqual(
|
||||||
FloatHelper.AreAlmostEqual(
|
FloatHelper.NegativeZeroDouble, FloatHelper.NegativeZeroDouble, 0
|
||||||
FloatHelper.NegativeZeroDouble, FloatHelper.NegativeZeroDouble, 0
|
)
|
||||||
)
|
);
|
||||||
);
|
Assert.IsTrue(
|
||||||
Assert.IsTrue(
|
FloatHelper.AreAlmostEqual(
|
||||||
FloatHelper.AreAlmostEqual(
|
FloatHelper.NegativeZeroDouble, FloatHelper.NegativeZeroDouble, 0
|
||||||
FloatHelper.NegativeZeroDouble, FloatHelper.NegativeZeroDouble, 0
|
)
|
||||||
)
|
);
|
||||||
);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Verifies that doubles can be compared across the zero boundary</summary>
|
||||||
/// <summary>Verifies that doubles can be compared across the zero boundary</summary>
|
[Test]
|
||||||
[Test]
|
public void DoublesCanBeComparedAcrossZeroInUlps() {
|
||||||
public void DoublesCanBeComparedAcrossZeroInUlps() {
|
double tenUlps = double.Epsilon * 10.0;
|
||||||
double tenUlps = double.Epsilon * 10.0;
|
|
||||||
|
Assert.IsTrue(FloatHelper.AreAlmostEqual(-tenUlps, tenUlps, 20));
|
||||||
Assert.IsTrue(FloatHelper.AreAlmostEqual(-tenUlps, tenUlps, 20));
|
Assert.IsTrue(FloatHelper.AreAlmostEqual(tenUlps, -tenUlps, 20));
|
||||||
Assert.IsTrue(FloatHelper.AreAlmostEqual(tenUlps, -tenUlps, 20));
|
Assert.IsFalse(FloatHelper.AreAlmostEqual(-tenUlps, tenUlps, 19));
|
||||||
Assert.IsFalse(FloatHelper.AreAlmostEqual(-tenUlps, tenUlps, 19));
|
|
||||||
|
Assert.IsTrue(FloatHelper.AreAlmostEqual(-tenUlps, 0, 10));
|
||||||
Assert.IsTrue(FloatHelper.AreAlmostEqual(-tenUlps, 0, 10));
|
Assert.IsTrue(FloatHelper.AreAlmostEqual(0, -tenUlps, 10));
|
||||||
Assert.IsTrue(FloatHelper.AreAlmostEqual(0, -tenUlps, 10));
|
Assert.IsFalse(FloatHelper.AreAlmostEqual(-tenUlps, 0, 9));
|
||||||
Assert.IsFalse(FloatHelper.AreAlmostEqual(-tenUlps, 0, 9));
|
|
||||||
|
Assert.IsTrue(FloatHelper.AreAlmostEqual(0, tenUlps, 10));
|
||||||
Assert.IsTrue(FloatHelper.AreAlmostEqual(0, tenUlps, 10));
|
Assert.IsTrue(FloatHelper.AreAlmostEqual(tenUlps, 0, 10));
|
||||||
Assert.IsTrue(FloatHelper.AreAlmostEqual(tenUlps, 0, 10));
|
Assert.IsFalse(FloatHelper.AreAlmostEqual(0, tenUlps, 9));
|
||||||
Assert.IsFalse(FloatHelper.AreAlmostEqual(0, tenUlps, 9));
|
}
|
||||||
}
|
|
||||||
|
}
|
||||||
}
|
|
||||||
|
} // namespace Nuclex.Support
|
||||||
} // namespace Nuclex.Support
|
|
||||||
|
#endif // UNITTEST
|
||||||
#endif // UNITTEST
|
|
||||||
|
|
|
@ -1,314 +1,313 @@
|
||||||
#region CPL License
|
#region Apache License 2.0
|
||||||
/*
|
/*
|
||||||
Nuclex Framework
|
Nuclex .NET Framework
|
||||||
Copyright (C) 2002-2017 Nuclex Development Labs
|
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
|
||||||
|
|
||||||
This library is free software; you can redistribute it and/or
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
modify it under the terms of the IBM Common Public License as
|
you may not use this file except in compliance with the License.
|
||||||
published by the IBM Corporation; either version 1.0 of the
|
You may obtain a copy of the License at
|
||||||
License, or (at your option) any later version.
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
This library is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
Unless required by applicable law or agreed to in writing, software
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
IBM Common Public License for more details.
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
You should have received a copy of the IBM Common Public
|
limitations under the License.
|
||||||
License along with this library
|
*/
|
||||||
*/
|
#endregion // Apache License 2.0
|
||||||
#endregion
|
|
||||||
|
using System;
|
||||||
using System;
|
using System.Runtime.InteropServices;
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
|
namespace Nuclex.Support {
|
||||||
namespace Nuclex.Support {
|
|
||||||
|
/// <summary>Helper routines for working with floating point numbers</summary>
|
||||||
/// <summary>Helper routines for working with floating point numbers</summary>
|
/// <remarks>
|
||||||
/// <remarks>
|
/// <para>
|
||||||
/// <para>
|
/// The floating point comparison code is based on this excellent article:
|
||||||
/// The floating point comparison code is based on this excellent article:
|
/// http://www.cygnus-software.com/papers/comparingfloats/comparingfloats.htm
|
||||||
/// http://www.cygnus-software.com/papers/comparingfloats/comparingfloats.htm
|
/// </para>
|
||||||
/// </para>
|
/// <para>
|
||||||
/// <para>
|
/// "ULP" means Unit in the Last Place and in the context of this library refers to
|
||||||
/// "ULP" means Unit in the Last Place and in the context of this library refers to
|
/// the distance between two adjacent floating point numbers. IEEE floating point
|
||||||
/// the distance between two adjacent floating point numbers. IEEE floating point
|
/// numbers can only represent a finite subset of natural numbers, with greater
|
||||||
/// numbers can only represent a finite subset of natural numbers, with greater
|
/// accuracy for smaller numbers and lower accuracy for very large numbers.
|
||||||
/// accuracy for smaller numbers and lower accuracy for very large numbers.
|
/// </para>
|
||||||
/// </para>
|
/// <para>
|
||||||
/// <para>
|
/// If a comparison is allowed "2 ulps" of deviation, that means the values are
|
||||||
/// If a comparison is allowed "2 ulps" of deviation, that means the values are
|
/// allowed to deviate by up to 2 adjacent floating point values, which might be
|
||||||
/// allowed to deviate by up to 2 adjacent floating point values, which might be
|
/// as low as 0.0000001 for small numbers or as high as 10.0 for large numbers.
|
||||||
/// as low as 0.0000001 for small numbers or as high as 10.0 for large numbers.
|
/// </para>
|
||||||
/// </para>
|
/// </remarks>
|
||||||
/// </remarks>
|
public static class FloatHelper {
|
||||||
public static class FloatHelper {
|
|
||||||
|
#region struct FloatIntUnion
|
||||||
#region struct FloatIntUnion
|
|
||||||
|
/// <summary>Union of a floating point variable and an integer</summary>
|
||||||
/// <summary>Union of a floating point variable and an integer</summary>
|
[StructLayout(LayoutKind.Explicit)]
|
||||||
[StructLayout(LayoutKind.Explicit)]
|
private struct FloatIntUnion {
|
||||||
private struct FloatIntUnion {
|
|
||||||
|
/// <summary>The union's value as a floating point variable</summary>
|
||||||
/// <summary>The union's value as a floating point variable</summary>
|
[FieldOffset(0)]
|
||||||
[FieldOffset(0)]
|
public float Float;
|
||||||
public float Float;
|
|
||||||
|
/// <summary>The union's value as an integer</summary>
|
||||||
/// <summary>The union's value as an integer</summary>
|
[FieldOffset(0)]
|
||||||
[FieldOffset(0)]
|
public int Int;
|
||||||
public int Int;
|
|
||||||
|
/// <summary>The union's value as an unsigned integer</summary>
|
||||||
/// <summary>The union's value as an unsigned integer</summary>
|
[FieldOffset(0)]
|
||||||
[FieldOffset(0)]
|
public uint UInt;
|
||||||
public uint UInt;
|
|
||||||
|
}
|
||||||
}
|
|
||||||
|
#endregion // struct FloatIntUnion
|
||||||
#endregion // struct FloatIntUnion
|
|
||||||
|
#region struct DoubleLongUnion
|
||||||
#region struct DoubleLongUnion
|
|
||||||
|
/// <summary>Union of a double precision floating point variable and a long</summary>
|
||||||
/// <summary>Union of a double precision floating point variable and a long</summary>
|
[StructLayout(LayoutKind.Explicit)]
|
||||||
[StructLayout(LayoutKind.Explicit)]
|
private struct DoubleLongUnion {
|
||||||
private struct DoubleLongUnion {
|
|
||||||
|
/// <summary>The union's value as a double precision floating point variable</summary>
|
||||||
/// <summary>The union's value as a double precision floating point variable</summary>
|
[FieldOffset(0)]
|
||||||
[FieldOffset(0)]
|
public double Double;
|
||||||
public double Double;
|
|
||||||
|
/// <summary>The union's value as a long</summary>
|
||||||
/// <summary>The union's value as a long</summary>
|
[FieldOffset(0)]
|
||||||
[FieldOffset(0)]
|
public long Long;
|
||||||
public long Long;
|
|
||||||
|
/// <summary>The union's value as an unsigned long</summary>
|
||||||
/// <summary>The union's value as an unsigned long</summary>
|
[FieldOffset(0)]
|
||||||
[FieldOffset(0)]
|
public ulong ULong;
|
||||||
public ulong ULong;
|
|
||||||
|
}
|
||||||
}
|
|
||||||
|
#endregion // struct DoubleLongUnion
|
||||||
#endregion // struct DoubleLongUnion
|
|
||||||
|
/// <summary>A floating point value that holds a positive zero</summary>
|
||||||
/// <summary>A floating point value that holds a positive zero</summary>
|
public const float PositiveZeroFloat = +0.0f;
|
||||||
public const float PositiveZeroFloat = +0.0f;
|
|
||||||
|
/// <summary>A floating point value that holds a negative zero</summary>
|
||||||
/// <summary>A floating point value that holds a negative zero</summary>
|
/// <remarks>
|
||||||
/// <remarks>
|
/// Negative zeros have a special representation in IEEE 752 floating point math
|
||||||
/// Negative zeros have a special representation in IEEE 752 floating point math
|
/// </remarks>
|
||||||
/// </remarks>
|
public const float NegativeZeroFloat = -0.0f;
|
||||||
public const float NegativeZeroFloat = -0.0f;
|
|
||||||
|
/// <summary>A double precision floating point value that holds a positive zero</summary>
|
||||||
/// <summary>A double precision floating point value that holds a positive zero</summary>
|
public const double PositiveZeroDouble = +0.0;
|
||||||
public const double PositiveZeroDouble = +0.0;
|
|
||||||
|
/// <summary>A doublep precision floating point value that holds a negative zero</summary>
|
||||||
/// <summary>A doublep precision floating point value that holds a negative zero</summary>
|
/// <remarks>
|
||||||
/// <remarks>
|
/// Negative zeros have a special representation in IEEE 752 floating point math
|
||||||
/// Negative zeros have a special representation in IEEE 752 floating point math
|
/// </remarks>
|
||||||
/// </remarks>
|
public const double NegativeZeroDouble = -0.0;
|
||||||
public const double NegativeZeroDouble = -0.0;
|
|
||||||
|
/// <summary>Checks whether the floating point value is exactly zero</summary>
|
||||||
/// <summary>Checks whether the floating point value is exactly zero</summary>
|
/// <param name="value">Value that will be checked for being zero</param>
|
||||||
/// <param name="value">Value that will be checked for being zero</param>
|
/// <returns>True if the value is zero, false otherwise</returns>
|
||||||
/// <returns>True if the value is zero, false otherwise</returns>
|
public static bool IsZero(float value) {
|
||||||
public static bool IsZero(float value) {
|
return (value == PositiveZeroFloat) || (value == NegativeZeroFloat);
|
||||||
return (value == PositiveZeroFloat) || (value == NegativeZeroFloat);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Checks whether the double precision floating point value is exactly zero
|
||||||
/// Checks whether the double precision floating point value is exactly zero
|
/// </summary>
|
||||||
/// </summary>
|
/// <param name="value">Value that will be checked for being zero</param>
|
||||||
/// <param name="value">Value that will be checked for being zero</param>
|
/// <returns>True if the value is zero, false otherwise</returns>
|
||||||
/// <returns>True if the value is zero, false otherwise</returns>
|
public static bool IsZero(double value) {
|
||||||
public static bool IsZero(double value) {
|
return (value == PositiveZeroDouble) || (value == NegativeZeroDouble);
|
||||||
return (value == PositiveZeroDouble) || (value == NegativeZeroDouble);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Compares two floating point values for equality</summary>
|
||||||
/// <summary>Compares two floating point values for equality</summary>
|
/// <param name="left">First floating point value to be compared</param>
|
||||||
/// <param name="left">First floating point value to be compared</param>
|
/// <param name="right">Second floating point value t be compared</param>
|
||||||
/// <param name="right">Second floating point value t be compared</param>
|
/// <param name="maxUlps">
|
||||||
/// <param name="maxUlps">
|
/// Maximum number of representable floating point values that are allowed to
|
||||||
/// Maximum number of representable floating point values that are allowed to
|
/// be between the left and the right floating point values
|
||||||
/// be between the left and the right floating point values
|
/// </param>
|
||||||
/// </param>
|
/// <returns>True if both numbers are equal or close to being equal</returns>
|
||||||
/// <returns>True if both numbers are equal or close to being equal</returns>
|
/// <remarks>
|
||||||
/// <remarks>
|
/// <para>
|
||||||
/// <para>
|
/// Floating point values can only represent a finite subset of natural numbers.
|
||||||
/// Floating point values can only represent a finite subset of natural numbers.
|
/// For example, the values 2.00000000 and 2.00000024 can be stored in a float,
|
||||||
/// For example, the values 2.00000000 and 2.00000024 can be stored in a float,
|
/// but nothing inbetween them.
|
||||||
/// but nothing inbetween them.
|
/// </para>
|
||||||
/// </para>
|
/// <para>
|
||||||
/// <para>
|
/// This comparison will count how many possible floating point values are between
|
||||||
/// This comparison will count how many possible floating point values are between
|
/// the left and the right number. If the number of possible values between both
|
||||||
/// the left and the right number. If the number of possible values between both
|
/// numbers is less than or equal to maxUlps, then the numbers are considered as
|
||||||
/// numbers is less than or equal to maxUlps, then the numbers are considered as
|
/// being equal.
|
||||||
/// being equal.
|
/// </para>
|
||||||
/// </para>
|
/// <para>
|
||||||
/// <para>
|
/// Implementation partially follows the code outlined here (link now defunct):
|
||||||
/// Implementation partially follows the code outlined here (link now defunct):
|
/// http://www.anttirt.net/2007/08/19/proper-floating-point-comparisons/
|
||||||
/// http://www.anttirt.net/2007/08/19/proper-floating-point-comparisons/
|
/// And here:
|
||||||
/// And here:
|
/// http://www.altdevblogaday.com/2012/02/22/comparing-floating-point-numbers-2012-edition/
|
||||||
/// http://www.altdevblogaday.com/2012/02/22/comparing-floating-point-numbers-2012-edition/
|
/// </para>
|
||||||
/// </para>
|
/// </remarks>
|
||||||
/// </remarks>
|
public static bool AreAlmostEqual(float left, float right, int maxUlps) {
|
||||||
public static bool AreAlmostEqual(float left, float right, int maxUlps) {
|
var leftUnion = new FloatIntUnion();
|
||||||
var leftUnion = new FloatIntUnion();
|
var rightUnion = new FloatIntUnion();
|
||||||
var rightUnion = new FloatIntUnion();
|
|
||||||
|
leftUnion.Float = left;
|
||||||
leftUnion.Float = left;
|
rightUnion.Float = right;
|
||||||
rightUnion.Float = right;
|
|
||||||
|
if(leftUnion.Int < 0) {
|
||||||
if(leftUnion.Int < 0) {
|
leftUnion.Int = unchecked((int)0x80000000 - leftUnion.Int);
|
||||||
leftUnion.Int = unchecked((int)0x80000000 - leftUnion.Int);
|
}
|
||||||
}
|
if(rightUnion.Int < 0) {
|
||||||
if(rightUnion.Int < 0) {
|
rightUnion.Int = unchecked((int)0x80000000 - rightUnion.Int);
|
||||||
rightUnion.Int = unchecked((int)0x80000000 - rightUnion.Int);
|
}
|
||||||
}
|
|
||||||
|
return Math.Abs(rightUnion.Int - leftUnion.Int) <= maxUlps;
|
||||||
return Math.Abs(rightUnion.Int - leftUnion.Int) <= maxUlps;
|
}
|
||||||
}
|
#if false
|
||||||
#if false
|
public static bool OldAreAlmostEqual(float left, float right, int maxUlps) {
|
||||||
public static bool OldAreAlmostEqual(float left, float right, int maxUlps) {
|
FloatInt32Union leftUnion = new FloatInt32Union();
|
||||||
FloatInt32Union leftUnion = new FloatInt32Union();
|
FloatInt32Union rightUnion = new FloatInt32Union();
|
||||||
FloatInt32Union rightUnion = new FloatInt32Union();
|
|
||||||
|
leftUnion.Float = left;
|
||||||
leftUnion.Float = left;
|
rightUnion.Float = right;
|
||||||
rightUnion.Float = right;
|
|
||||||
|
uint leftSignMask = (leftUnion.UInt >> 31);
|
||||||
uint leftSignMask = (leftUnion.UInt >> 31);
|
uint rightSignMask = (rightUnion.UInt >> 31);
|
||||||
uint rightSignMask = (rightUnion.UInt >> 31);
|
|
||||||
|
uint leftTemp = ((0x80000000 - leftUnion.UInt) & leftSignMask);
|
||||||
uint leftTemp = ((0x80000000 - leftUnion.UInt) & leftSignMask);
|
leftUnion.UInt = leftTemp | (leftUnion.UInt & ~leftSignMask);
|
||||||
leftUnion.UInt = leftTemp | (leftUnion.UInt & ~leftSignMask);
|
|
||||||
|
uint rightTemp = ((0x80000000 - rightUnion.UInt) & rightSignMask);
|
||||||
uint rightTemp = ((0x80000000 - rightUnion.UInt) & rightSignMask);
|
rightUnion.UInt = rightTemp | (rightUnion.UInt & ~rightSignMask);
|
||||||
rightUnion.UInt = rightTemp | (rightUnion.UInt & ~rightSignMask);
|
|
||||||
|
return (Math.Abs(leftUnion.Int - rightUnion.Int) <= maxUlps);
|
||||||
return (Math.Abs(leftUnion.Int - rightUnion.Int) <= maxUlps);
|
}
|
||||||
}
|
#endif
|
||||||
#endif
|
|
||||||
|
/// <summary>Compares two double precision floating point values for equality</summary>
|
||||||
/// <summary>Compares two double precision floating point values for equality</summary>
|
/// <param name="left">First double precision floating point value to be compared</param>
|
||||||
/// <param name="left">First double precision floating point value to be compared</param>
|
/// <param name="right">Second double precision floating point value t be compared</param>
|
||||||
/// <param name="right">Second double precision floating point value t be compared</param>
|
/// <param name="maxUlps">
|
||||||
/// <param name="maxUlps">
|
/// Maximum number of representable double precision floating point values that are
|
||||||
/// Maximum number of representable double precision floating point values that are
|
/// allowed to be between the left and the right double precision floating point values
|
||||||
/// allowed to be between the left and the right double precision floating point values
|
/// </param>
|
||||||
/// </param>
|
/// <returns>True if both numbers are equal or close to being equal</returns>
|
||||||
/// <returns>True if both numbers are equal or close to being equal</returns>
|
/// <remarks>
|
||||||
/// <remarks>
|
/// <para>
|
||||||
/// <para>
|
/// Double precision floating point values can only represent a limited series of
|
||||||
/// Double precision floating point values can only represent a limited series of
|
/// natural numbers. For example, the values 2.0000000000000000 and 2.0000000000000004
|
||||||
/// natural numbers. For example, the values 2.0000000000000000 and 2.0000000000000004
|
/// can be stored in a double, but nothing inbetween them.
|
||||||
/// can be stored in a double, but nothing inbetween them.
|
/// </para>
|
||||||
/// </para>
|
/// <para>
|
||||||
/// <para>
|
/// This comparison will count how many possible double precision floating point
|
||||||
/// This comparison will count how many possible double precision floating point
|
/// values are between the left and the right number. If the number of possible
|
||||||
/// values are between the left and the right number. If the number of possible
|
/// values between both numbers is less than or equal to maxUlps, then the numbers
|
||||||
/// values between both numbers is less than or equal to maxUlps, then the numbers
|
/// are considered as being equal.
|
||||||
/// are considered as being equal.
|
/// </para>
|
||||||
/// </para>
|
/// <para>
|
||||||
/// <para>
|
/// Implementation partially follows the code outlined here:
|
||||||
/// Implementation partially follows the code outlined here:
|
/// http://www.anttirt.net/2007/08/19/proper-floating-point-comparisons/
|
||||||
/// http://www.anttirt.net/2007/08/19/proper-floating-point-comparisons/
|
/// And here:
|
||||||
/// And here:
|
/// http://www.altdevblogaday.com/2012/02/22/comparing-floating-point-numbers-2012-edition/
|
||||||
/// http://www.altdevblogaday.com/2012/02/22/comparing-floating-point-numbers-2012-edition/
|
/// </para>
|
||||||
/// </para>
|
/// </remarks>
|
||||||
/// </remarks>
|
public static bool AreAlmostEqual(double left, double right, long maxUlps) {
|
||||||
public static bool AreAlmostEqual(double left, double right, long maxUlps) {
|
var leftUnion = new DoubleLongUnion();
|
||||||
var leftUnion = new DoubleLongUnion();
|
var rightUnion = new DoubleLongUnion();
|
||||||
var rightUnion = new DoubleLongUnion();
|
|
||||||
|
leftUnion.Double = left;
|
||||||
leftUnion.Double = left;
|
rightUnion.Double = right;
|
||||||
rightUnion.Double = right;
|
|
||||||
|
if(leftUnion.Long < 0) {
|
||||||
if(leftUnion.Long < 0) {
|
leftUnion.Long = unchecked((long)0x8000000000000000 - leftUnion.Long);
|
||||||
leftUnion.Long = unchecked((long)0x8000000000000000 - leftUnion.Long);
|
}
|
||||||
}
|
if(rightUnion.Long < 0) {
|
||||||
if(rightUnion.Long < 0) {
|
rightUnion.Long = unchecked((long)0x8000000000000000 - rightUnion.Long);
|
||||||
rightUnion.Long = unchecked((long)0x8000000000000000 - rightUnion.Long);
|
}
|
||||||
}
|
|
||||||
|
return Math.Abs(rightUnion.Long - leftUnion.Long) <= maxUlps;
|
||||||
return Math.Abs(rightUnion.Long - leftUnion.Long) <= maxUlps;
|
}
|
||||||
}
|
#if false
|
||||||
#if false
|
public static bool OldAreAlmostEqual(double left, double right, long maxUlps) {
|
||||||
public static bool OldAreAlmostEqual(double left, double right, long maxUlps) {
|
DoubleInt64Union leftUnion = new DoubleInt64Union();
|
||||||
DoubleInt64Union leftUnion = new DoubleInt64Union();
|
DoubleInt64Union rightUnion = new DoubleInt64Union();
|
||||||
DoubleInt64Union rightUnion = new DoubleInt64Union();
|
|
||||||
|
leftUnion.Double = left;
|
||||||
leftUnion.Double = left;
|
rightUnion.Double = right;
|
||||||
rightUnion.Double = right;
|
|
||||||
|
ulong leftSignMask = (leftUnion.ULong >> 63);
|
||||||
ulong leftSignMask = (leftUnion.ULong >> 63);
|
ulong rightSignMask = (rightUnion.ULong >> 63);
|
||||||
ulong rightSignMask = (rightUnion.ULong >> 63);
|
|
||||||
|
ulong leftTemp = ((0x8000000000000000 - leftUnion.ULong) & leftSignMask);
|
||||||
ulong leftTemp = ((0x8000000000000000 - leftUnion.ULong) & leftSignMask);
|
leftUnion.ULong = leftTemp | (leftUnion.ULong & ~leftSignMask);
|
||||||
leftUnion.ULong = leftTemp | (leftUnion.ULong & ~leftSignMask);
|
|
||||||
|
ulong rightTemp = ((0x8000000000000000 - rightUnion.ULong) & rightSignMask);
|
||||||
ulong rightTemp = ((0x8000000000000000 - rightUnion.ULong) & rightSignMask);
|
rightUnion.ULong = rightTemp | (rightUnion.ULong & ~rightSignMask);
|
||||||
rightUnion.ULong = rightTemp | (rightUnion.ULong & ~rightSignMask);
|
|
||||||
|
return (Math.Abs(leftUnion.Long - rightUnion.Long) <= maxUlps);
|
||||||
return (Math.Abs(leftUnion.Long - rightUnion.Long) <= maxUlps);
|
}
|
||||||
}
|
#endif
|
||||||
#endif
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Reinterprets the memory contents of a floating point value as an integer value
|
||||||
/// Reinterprets the memory contents of a floating point value as an integer value
|
/// </summary>
|
||||||
/// </summary>
|
/// <param name="value">
|
||||||
/// <param name="value">
|
/// Floating point value whose memory contents to reinterpret
|
||||||
/// Floating point value whose memory contents to reinterpret
|
/// </param>
|
||||||
/// </param>
|
/// <returns>
|
||||||
/// <returns>
|
/// The memory contents of the floating point value interpreted as an integer
|
||||||
/// The memory contents of the floating point value interpreted as an integer
|
/// </returns>
|
||||||
/// </returns>
|
public static int ReinterpretAsInt(this float value) {
|
||||||
public static int ReinterpretAsInt(this float value) {
|
FloatIntUnion union = new FloatIntUnion();
|
||||||
FloatIntUnion union = new FloatIntUnion();
|
union.Float = value;
|
||||||
union.Float = value;
|
return union.Int;
|
||||||
return union.Int;
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Reinterprets the memory contents of a double precision floating point
|
||||||
/// Reinterprets the memory contents of a double precision floating point
|
/// value as an integer value
|
||||||
/// value as an integer value
|
/// </summary>
|
||||||
/// </summary>
|
/// <param name="value">
|
||||||
/// <param name="value">
|
/// Double precision floating point value whose memory contents to reinterpret
|
||||||
/// Double precision floating point value whose memory contents to reinterpret
|
/// </param>
|
||||||
/// </param>
|
/// <returns>
|
||||||
/// <returns>
|
/// The memory contents of the double precision floating point value
|
||||||
/// The memory contents of the double precision floating point value
|
/// interpreted as an integer
|
||||||
/// interpreted as an integer
|
/// </returns>
|
||||||
/// </returns>
|
public static long ReinterpretAsLong(this double value) {
|
||||||
public static long ReinterpretAsLong(this double value) {
|
DoubleLongUnion union = new DoubleLongUnion();
|
||||||
DoubleLongUnion union = new DoubleLongUnion();
|
union.Double = value;
|
||||||
union.Double = value;
|
return union.Long;
|
||||||
return union.Long;
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Reinterprets the memory contents of an integer as a floating point value
|
||||||
/// Reinterprets the memory contents of an integer as a floating point value
|
/// </summary>
|
||||||
/// </summary>
|
/// <param name="value">Integer value whose memory contents to reinterpret</param>
|
||||||
/// <param name="value">Integer value whose memory contents to reinterpret</param>
|
/// <returns>
|
||||||
/// <returns>
|
/// The memory contents of the integer value interpreted as a floating point value
|
||||||
/// The memory contents of the integer value interpreted as a floating point value
|
/// </returns>
|
||||||
/// </returns>
|
public static float ReinterpretAsFloat(this int value) {
|
||||||
public static float ReinterpretAsFloat(this int value) {
|
FloatIntUnion union = new FloatIntUnion();
|
||||||
FloatIntUnion union = new FloatIntUnion();
|
union.Int = value;
|
||||||
union.Int = value;
|
return union.Float;
|
||||||
return union.Float;
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Reinterprets the memory contents of an integer value as a double precision
|
||||||
/// Reinterprets the memory contents of an integer value as a double precision
|
/// floating point value
|
||||||
/// floating point value
|
/// </summary>
|
||||||
/// </summary>
|
/// <param name="value">Integer whose memory contents to reinterpret</param>
|
||||||
/// <param name="value">Integer whose memory contents to reinterpret</param>
|
/// <returns>
|
||||||
/// <returns>
|
/// The memory contents of the integer interpreted as a double precision
|
||||||
/// The memory contents of the integer interpreted as a double precision
|
/// floating point value
|
||||||
/// floating point value
|
/// </returns>
|
||||||
/// </returns>
|
public static double ReinterpretAsDouble(this long value) {
|
||||||
public static double ReinterpretAsDouble(this long value) {
|
DoubleLongUnion union = new DoubleLongUnion();
|
||||||
DoubleLongUnion union = new DoubleLongUnion();
|
union.Long = value;
|
||||||
union.Long = value;
|
return union.Double;
|
||||||
return union.Double;
|
}
|
||||||
}
|
|
||||||
|
}
|
||||||
}
|
|
||||||
|
} // namespace Nuclex.Support
|
||||||
} // namespace Nuclex.Support
|
|
||||||
|
|
|
@ -1,35 +1,34 @@
|
||||||
#region CPL License
|
#region Apache License 2.0
|
||||||
/*
|
/*
|
||||||
Nuclex Framework
|
Nuclex .NET Framework
|
||||||
Copyright (C) 2002-2017 Nuclex Development Labs
|
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
|
||||||
|
|
||||||
This library is free software; you can redistribute it and/or
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
modify it under the terms of the IBM Common Public License as
|
you may not use this file except in compliance with the License.
|
||||||
published by the IBM Corporation; either version 1.0 of the
|
You may obtain a copy of the License at
|
||||||
License, or (at your option) any later version.
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
This library is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
Unless required by applicable law or agreed to in writing, software
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
IBM Common Public License for more details.
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
You should have received a copy of the IBM Common Public
|
limitations under the License.
|
||||||
License along with this library
|
*/
|
||||||
*/
|
#endregion // Apache License 2.0
|
||||||
#endregion
|
|
||||||
|
using System;
|
||||||
using System;
|
|
||||||
|
namespace Nuclex.Support {
|
||||||
namespace Nuclex.Support {
|
|
||||||
|
/// <summary>How to behave in in respect to the garbage collector</summary>
|
||||||
/// <summary>How to behave in in respect to the garbage collector</summary>
|
public enum GarbagePolicy {
|
||||||
public enum GarbagePolicy {
|
|
||||||
|
/// <summary>Avoid feeding the garbage collector whenever possible</summary>
|
||||||
/// <summary>Avoid feeding the garbage collector whenever possible</summary>
|
Avoid,
|
||||||
Avoid,
|
/// <summary>Accept garbage production</summary>
|
||||||
/// <summary>Accept garbage production</summary>
|
Accept
|
||||||
Accept
|
|
||||||
|
}
|
||||||
}
|
|
||||||
|
} // namespace Nuclex.Support
|
||||||
} // namespace Nuclex.Support
|
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,459 +1,458 @@
|
||||||
#region CPL License
|
#region Apache License 2.0
|
||||||
/*
|
/*
|
||||||
Nuclex Framework
|
Nuclex .NET Framework
|
||||||
Copyright (C) 2002-2017 Nuclex Development Labs
|
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
|
||||||
|
|
||||||
This library is free software; you can redistribute it and/or
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
modify it under the terms of the IBM Common Public License as
|
you may not use this file except in compliance with the License.
|
||||||
published by the IBM Corporation; either version 1.0 of the
|
You may obtain a copy of the License at
|
||||||
License, or (at your option) any later version.
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
This library is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
Unless required by applicable law or agreed to in writing, software
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
IBM Common Public License for more details.
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
You should have received a copy of the IBM Common Public
|
limitations under the License.
|
||||||
License along with this library
|
*/
|
||||||
*/
|
#endregion // Apache License 2.0
|
||||||
#endregion
|
|
||||||
|
using System;
|
||||||
using System;
|
using System.Diagnostics;
|
||||||
using System.Diagnostics;
|
using System.IO;
|
||||||
using System.IO;
|
|
||||||
|
namespace Nuclex.Support.IO {
|
||||||
namespace Nuclex.Support.IO {
|
|
||||||
|
/// <summary>Chains a series of independent streams into a single stream</summary>
|
||||||
/// <summary>Chains a series of independent streams into a single stream</summary>
|
/// <remarks>
|
||||||
/// <remarks>
|
/// <para>
|
||||||
/// <para>
|
/// This class can be used to chain multiple independent streams into a single
|
||||||
/// This class can be used to chain multiple independent streams into a single
|
/// stream that acts as if its chained streams were only one combined stream.
|
||||||
/// stream that acts as if its chained streams were only one combined stream.
|
/// It is useful to avoid creating huge memory streams or temporary files when
|
||||||
/// It is useful to avoid creating huge memory streams or temporary files when
|
/// you just need to prepend or append some data to a stream or if you need to
|
||||||
/// you just need to prepend or append some data to a stream or if you need to
|
/// read a file that was split into several parts as if it was a single file.
|
||||||
/// read a file that was split into several parts as if it was a single file.
|
/// </para>
|
||||||
/// </para>
|
/// <para>
|
||||||
/// <para>
|
/// It is not recommended to change the size of any chained stream after it
|
||||||
/// It is not recommended to change the size of any chained stream after it
|
/// has become part of a stream chainer, though the stream chainer will do its
|
||||||
/// has become part of a stream chainer, though the stream chainer will do its
|
/// best to cope with the changes as they occur. Increasing the length of a
|
||||||
/// best to cope with the changes as they occur. Increasing the length of a
|
/// chained stream is generally not an issue for streams that support seeking,
|
||||||
/// chained stream is generally not an issue for streams that support seeking,
|
/// but reducing the length might invalidate the stream chainer's file pointer,
|
||||||
/// but reducing the length might invalidate the stream chainer's file pointer,
|
/// resulting in an IOException when Read() or Write() is next called.
|
||||||
/// resulting in an IOException when Read() or Write() is next called.
|
/// </para>
|
||||||
/// </para>
|
/// </remarks>
|
||||||
/// </remarks>
|
public class ChainStream : Stream {
|
||||||
public class ChainStream : Stream {
|
|
||||||
|
/// <summary>Initializes a new stream chainer</summary>
|
||||||
/// <summary>Initializes a new stream chainer</summary>
|
/// <param name="streams">Array of streams that will be chained together</param>
|
||||||
/// <param name="streams">Array of streams that will be chained together</param>
|
public ChainStream(params Stream[] streams) {
|
||||||
public ChainStream(params Stream[] streams) {
|
this.streams = (Stream[])streams.Clone();
|
||||||
this.streams = (Stream[])streams.Clone();
|
|
||||||
|
determineCapabilities();
|
||||||
determineCapabilities();
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Whether data can be read from the stream</summary>
|
||||||
/// <summary>Whether data can be read from the stream</summary>
|
public override bool CanRead {
|
||||||
public override bool CanRead {
|
get { return this.allStreamsCanRead; }
|
||||||
get { return this.allStreamsCanRead; }
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Whether the stream supports seeking</summary>
|
||||||
/// <summary>Whether the stream supports seeking</summary>
|
public override bool CanSeek {
|
||||||
public override bool CanSeek {
|
get { return this.allStreamsCanSeek; }
|
||||||
get { return this.allStreamsCanSeek; }
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Whether data can be written into the stream</summary>
|
||||||
/// <summary>Whether data can be written into the stream</summary>
|
public override bool CanWrite {
|
||||||
public override bool CanWrite {
|
get { return this.allStreamsCanWrite; }
|
||||||
get { return this.allStreamsCanWrite; }
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Clears all buffers for this stream and causes any buffered data to be written
|
||||||
/// Clears all buffers for this stream and causes any buffered data to be written
|
/// to the underlying device.
|
||||||
/// to the underlying device.
|
/// </summary>
|
||||||
/// </summary>
|
public override void Flush() {
|
||||||
public override void Flush() {
|
for(int index = 0; index < this.streams.Length; ++index) {
|
||||||
for(int index = 0; index < this.streams.Length; ++index) {
|
this.streams[index].Flush();
|
||||||
this.streams[index].Flush();
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Length of the stream in bytes</summary>
|
||||||
/// <summary>Length of the stream in bytes</summary>
|
/// <exception cref="NotSupportedException">
|
||||||
/// <exception cref="NotSupportedException">
|
/// At least one of the chained streams does not support seeking
|
||||||
/// At least one of the chained streams does not support seeking
|
/// </exception>
|
||||||
/// </exception>
|
public override long Length {
|
||||||
public override long Length {
|
get {
|
||||||
get {
|
if(!this.allStreamsCanSeek) {
|
||||||
if(!this.allStreamsCanSeek) {
|
throw makeSeekNotSupportedException("determine length");
|
||||||
throw makeSeekNotSupportedException("determine length");
|
}
|
||||||
}
|
|
||||||
|
// Sum up the length of all chained streams
|
||||||
// Sum up the length of all chained streams
|
long length = 0;
|
||||||
long length = 0;
|
for(int index = 0; index < this.streams.Length; ++index) {
|
||||||
for(int index = 0; index < this.streams.Length; ++index) {
|
length += this.streams[index].Length;
|
||||||
length += this.streams[index].Length;
|
}
|
||||||
}
|
|
||||||
|
return length;
|
||||||
return length;
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Absolute position of the file pointer within the stream</summary>
|
||||||
/// <summary>Absolute position of the file pointer within the stream</summary>
|
/// <exception cref="NotSupportedException">
|
||||||
/// <exception cref="NotSupportedException">
|
/// At least one of the chained streams does not support seeking
|
||||||
/// At least one of the chained streams does not support seeking
|
/// </exception>
|
||||||
/// </exception>
|
public override long Position {
|
||||||
public override long Position {
|
get {
|
||||||
get {
|
if(!this.allStreamsCanSeek) {
|
||||||
if(!this.allStreamsCanSeek) {
|
throw makeSeekNotSupportedException("seek");
|
||||||
throw makeSeekNotSupportedException("seek");
|
}
|
||||||
}
|
|
||||||
|
return this.position;
|
||||||
return this.position;
|
}
|
||||||
}
|
set { moveFilePointer(value); }
|
||||||
set { moveFilePointer(value); }
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Reads a sequence of bytes from the stream and advances the position of
|
||||||
/// Reads a sequence of bytes from the stream and advances the position of
|
/// the file pointer by the number of bytes read.
|
||||||
/// the file pointer by the number of bytes read.
|
/// </summary>
|
||||||
/// </summary>
|
/// <param name="buffer">Buffer that will receive the data read from the stream</param>
|
||||||
/// <param name="buffer">Buffer that will receive the data read from the stream</param>
|
/// <param name="offset">
|
||||||
/// <param name="offset">
|
/// Offset in the buffer at which the stream will place the data read
|
||||||
/// Offset in the buffer at which the stream will place the data read
|
/// </param>
|
||||||
/// </param>
|
/// <param name="count">Maximum number of bytes that will be read</param>
|
||||||
/// <param name="count">Maximum number of bytes that will be read</param>
|
/// <returns>
|
||||||
/// <returns>
|
/// The number of bytes that were actually read from the stream and written into
|
||||||
/// The number of bytes that were actually read from the stream and written into
|
/// the provided buffer
|
||||||
/// the provided buffer
|
/// </returns>
|
||||||
/// </returns>
|
/// <exception cref="NotSupportedException">
|
||||||
/// <exception cref="NotSupportedException">
|
/// The chained stream at the current position does not support reading
|
||||||
/// The chained stream at the current position does not support reading
|
/// </exception>
|
||||||
/// </exception>
|
public override int Read(byte[] buffer, int offset, int count) {
|
||||||
public override int Read(byte[] buffer, int offset, int count) {
|
if(!this.allStreamsCanRead) {
|
||||||
if(!this.allStreamsCanRead) {
|
throw new NotSupportedException(
|
||||||
throw new NotSupportedException(
|
"Can't read: at least one of the chained streams doesn't support reading"
|
||||||
"Can't read: at least one of the chained streams doesn't support reading"
|
);
|
||||||
);
|
}
|
||||||
}
|
|
||||||
|
int totalBytesRead = 0;
|
||||||
int totalBytesRead = 0;
|
int lastStreamIndex = this.streams.Length - 1;
|
||||||
int lastStreamIndex = this.streams.Length - 1;
|
|
||||||
|
if(this.allStreamsCanSeek) {
|
||||||
if(this.allStreamsCanSeek) {
|
|
||||||
|
// Find out from which stream and at which position we need to begin reading
|
||||||
// Find out from which stream and at which position we need to begin reading
|
int streamIndex;
|
||||||
int streamIndex;
|
long streamOffset;
|
||||||
long streamOffset;
|
findStreamIndexAndOffset(this.position, out streamIndex, out streamOffset);
|
||||||
findStreamIndexAndOffset(this.position, out streamIndex, out streamOffset);
|
|
||||||
|
// Try to read from the stream our current file pointer falls into. If more
|
||||||
// Try to read from the stream our current file pointer falls into. If more
|
// data was requested than the stream contains, read each stream to its end
|
||||||
// data was requested than the stream contains, read each stream to its end
|
// until we either have enough data or run out of streams.
|
||||||
// until we either have enough data or run out of streams.
|
while(count > 0) {
|
||||||
while(count > 0) {
|
Stream currentStream = this.streams[streamIndex];
|
||||||
Stream currentStream = this.streams[streamIndex];
|
|
||||||
|
// Read up to count bytes from the current stream. Count is decreased each
|
||||||
// Read up to count bytes from the current stream. Count is decreased each
|
// time we successfully get data and holds the number of bytes remaining
|
||||||
// time we successfully get data and holds the number of bytes remaining
|
// to be read
|
||||||
// to be read
|
long maximumBytes = Math.Min(count, currentStream.Length - streamOffset);
|
||||||
long maximumBytes = Math.Min(count, currentStream.Length - streamOffset);
|
currentStream.Position = streamOffset;
|
||||||
currentStream.Position = streamOffset;
|
int bytesRead = currentStream.Read(buffer, offset, (int)maximumBytes);
|
||||||
int bytesRead = currentStream.Read(buffer, offset, (int)maximumBytes);
|
|
||||||
|
// Accumulate the total number of bytes we read for the return value
|
||||||
// Accumulate the total number of bytes we read for the return value
|
totalBytesRead += bytesRead;
|
||||||
totalBytesRead += bytesRead;
|
|
||||||
|
// If the stream returned partial data, stop here. Also, if this was the
|
||||||
// If the stream returned partial data, stop here. Also, if this was the
|
// last stream we queried, this is as far as we can go.
|
||||||
// last stream we queried, this is as far as we can go.
|
if((bytesRead < maximumBytes) || (streamIndex == lastStreamIndex)) {
|
||||||
if((bytesRead < maximumBytes) || (streamIndex == lastStreamIndex)) {
|
break;
|
||||||
break;
|
}
|
||||||
}
|
|
||||||
|
// Move on to the next stream in the chain
|
||||||
// Move on to the next stream in the chain
|
++streamIndex;
|
||||||
++streamIndex;
|
streamOffset = 0;
|
||||||
streamOffset = 0;
|
count -= bytesRead;
|
||||||
count -= bytesRead;
|
offset += bytesRead;
|
||||||
offset += bytesRead;
|
}
|
||||||
}
|
|
||||||
|
this.position += totalBytesRead;
|
||||||
this.position += totalBytesRead;
|
|
||||||
|
} else {
|
||||||
} else {
|
|
||||||
|
// Try to read from the active read stream. If the end of the active read
|
||||||
// Try to read from the active read stream. If the end of the active read
|
// stream is reached, switch to the next stream in the chain until we have
|
||||||
// stream is reached, switch to the next stream in the chain until we have
|
// no more streams left to read from
|
||||||
// no more streams left to read from
|
while(this.activeReadStreamIndex <= lastStreamIndex) {
|
||||||
while(this.activeReadStreamIndex <= lastStreamIndex) {
|
|
||||||
|
// Try to read from the stream. The stream can either return any amount
|
||||||
// Try to read from the stream. The stream can either return any amount
|
// of data > 0 if there's still data left ot be read or 0 if the end of
|
||||||
// of data > 0 if there's still data left ot be read or 0 if the end of
|
// the stream was reached
|
||||||
// the stream was reached
|
Stream activeStream = this.streams[this.activeReadStreamIndex];
|
||||||
Stream activeStream = this.streams[this.activeReadStreamIndex];
|
if(activeStream.CanSeek) {
|
||||||
if(activeStream.CanSeek) {
|
activeStream.Position = this.activeReadStreamPosition;
|
||||||
activeStream.Position = this.activeReadStreamPosition;
|
}
|
||||||
}
|
totalBytesRead = activeStream.Read(buffer, offset, count);
|
||||||
totalBytesRead = activeStream.Read(buffer, offset, count);
|
|
||||||
|
// If we got any data, we're done, exit the loop
|
||||||
// If we got any data, we're done, exit the loop
|
if(totalBytesRead != 0) {
|
||||||
if(totalBytesRead != 0) {
|
break;
|
||||||
break;
|
} else { // Otherwise, go to the next stream in the chain
|
||||||
} else { // Otherwise, go to the next stream in the chain
|
this.activeReadStreamPosition = 0;
|
||||||
this.activeReadStreamPosition = 0;
|
++this.activeReadStreamIndex;
|
||||||
++this.activeReadStreamIndex;
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
this.activeReadStreamPosition += totalBytesRead;
|
||||||
this.activeReadStreamPosition += totalBytesRead;
|
|
||||||
|
}
|
||||||
}
|
|
||||||
|
return totalBytesRead;
|
||||||
return totalBytesRead;
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Changes the position of the file pointer</summary>
|
||||||
/// <summary>Changes the position of the file pointer</summary>
|
/// <param name="offset">
|
||||||
/// <param name="offset">
|
/// Offset to move the file pointer by, relative to the position indicated by
|
||||||
/// Offset to move the file pointer by, relative to the position indicated by
|
/// the <paramref name="origin" /> parameter.
|
||||||
/// the <paramref name="origin" /> parameter.
|
/// </param>
|
||||||
/// </param>
|
/// <param name="origin">
|
||||||
/// <param name="origin">
|
/// Reference point relative to which the file pointer is placed
|
||||||
/// Reference point relative to which the file pointer is placed
|
/// </param>
|
||||||
/// </param>
|
/// <returns>The new absolute position within the stream</returns>
|
||||||
/// <returns>The new absolute position within the stream</returns>
|
public override long Seek(long offset, SeekOrigin origin) {
|
||||||
public override long Seek(long offset, SeekOrigin origin) {
|
switch(origin) {
|
||||||
switch(origin) {
|
case SeekOrigin.Begin: {
|
||||||
case SeekOrigin.Begin: {
|
return Position = offset;
|
||||||
return Position = offset;
|
}
|
||||||
}
|
case SeekOrigin.Current: {
|
||||||
case SeekOrigin.Current: {
|
return Position += offset;
|
||||||
return Position += offset;
|
}
|
||||||
}
|
case SeekOrigin.End: {
|
||||||
case SeekOrigin.End: {
|
return Position = (Length + offset);
|
||||||
return Position = (Length + offset);
|
}
|
||||||
}
|
default: {
|
||||||
default: {
|
throw new ArgumentException("Invalid seek origin", "origin");
|
||||||
throw new ArgumentException("Invalid seek origin", "origin");
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Changes the length of the stream</summary>
|
||||||
/// <summary>Changes the length of the stream</summary>
|
/// <param name="value">New length the stream shall have</param>
|
||||||
/// <param name="value">New length the stream shall have</param>
|
/// <exception cref="NotSupportedException">
|
||||||
/// <exception cref="NotSupportedException">
|
/// Always, the stream chainer does not support the SetLength() operation
|
||||||
/// Always, the stream chainer does not support the SetLength() operation
|
/// </exception>
|
||||||
/// </exception>
|
public override void SetLength(long value) {
|
||||||
public override void SetLength(long value) {
|
throw new NotSupportedException("Resizing chained streams is not supported");
|
||||||
throw new NotSupportedException("Resizing chained streams is not supported");
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Writes a sequence of bytes to the stream and advances the position of
|
||||||
/// Writes a sequence of bytes to the stream and advances the position of
|
/// the file pointer by the number of bytes written.
|
||||||
/// the file pointer by the number of bytes written.
|
/// </summary>
|
||||||
/// </summary>
|
/// <param name="buffer">
|
||||||
/// <param name="buffer">
|
/// Buffer containing the data that will be written to the stream
|
||||||
/// Buffer containing the data that will be written to the stream
|
/// </param>
|
||||||
/// </param>
|
/// <param name="offset">
|
||||||
/// <param name="offset">
|
/// Offset in the buffer at which the data to be written starts
|
||||||
/// Offset in the buffer at which the data to be written starts
|
/// </param>
|
||||||
/// </param>
|
/// <param name="count">Number of bytes that will be written into the stream</param>
|
||||||
/// <param name="count">Number of bytes that will be written into the stream</param>
|
/// <remarks>
|
||||||
/// <remarks>
|
/// The behavior of this method is as follows: If one or more chained streams
|
||||||
/// The behavior of this method is as follows: If one or more chained streams
|
/// do not support seeking, all data is appended to the final stream in the
|
||||||
/// do not support seeking, all data is appended to the final stream in the
|
/// chain. Otherwise, writing will begin with the stream the current file pointer
|
||||||
/// chain. Otherwise, writing will begin with the stream the current file pointer
|
/// offset falls into. If the end of that stream is reached, writing continues
|
||||||
/// offset falls into. If the end of that stream is reached, writing continues
|
/// in the next stream. On the last stream, writing more data into the stream
|
||||||
/// in the next stream. On the last stream, writing more data into the stream
|
/// that it current size allows will enlarge the stream.
|
||||||
/// that it current size allows will enlarge the stream.
|
/// </remarks>
|
||||||
/// </remarks>
|
public override void Write(byte[] buffer, int offset, int count) {
|
||||||
public override void Write(byte[] buffer, int offset, int count) {
|
if(!this.allStreamsCanWrite) {
|
||||||
if(!this.allStreamsCanWrite) {
|
throw new NotSupportedException(
|
||||||
throw new NotSupportedException(
|
"Can't write: at least one of the chained streams doesn't support writing"
|
||||||
"Can't write: at least one of the chained streams doesn't support writing"
|
);
|
||||||
);
|
}
|
||||||
}
|
|
||||||
|
int remaining = count;
|
||||||
int remaining = count;
|
|
||||||
|
// If seeking is supported, we can write into the mid of the stream,
|
||||||
// If seeking is supported, we can write into the mid of the stream,
|
// if the user so desires
|
||||||
// if the user so desires
|
if(this.allStreamsCanSeek) {
|
||||||
if(this.allStreamsCanSeek) {
|
|
||||||
|
// Find out in which stream and at which position we need to begin writing
|
||||||
// Find out in which stream and at which position we need to begin writing
|
int streamIndex;
|
||||||
int streamIndex;
|
long streamOffset;
|
||||||
long streamOffset;
|
findStreamIndexAndOffset(this.position, out streamIndex, out streamOffset);
|
||||||
findStreamIndexAndOffset(this.position, out streamIndex, out streamOffset);
|
|
||||||
|
// Write data into the streams, switching over to the next stream if data is
|
||||||
// Write data into the streams, switching over to the next stream if data is
|
// too large to fit into the current stream, until all data is spent.
|
||||||
// too large to fit into the current stream, until all data is spent.
|
int lastStreamIndex = this.streams.Length - 1;
|
||||||
int lastStreamIndex = this.streams.Length - 1;
|
while(remaining > 0) {
|
||||||
while(remaining > 0) {
|
Stream currentStream = this.streams[streamIndex];
|
||||||
Stream currentStream = this.streams[streamIndex];
|
|
||||||
|
// If this is the last stream, just write. If the data is larger than the last
|
||||||
// If this is the last stream, just write. If the data is larger than the last
|
// stream's remaining bytes, it will append to that stream, enlarging it.
|
||||||
// stream's remaining bytes, it will append to that stream, enlarging it.
|
if(streamIndex == lastStreamIndex) {
|
||||||
if(streamIndex == lastStreamIndex) {
|
|
||||||
|
// Write all remaining data into the last stream
|
||||||
// Write all remaining data into the last stream
|
currentStream.Position = streamOffset;
|
||||||
currentStream.Position = streamOffset;
|
currentStream.Write(buffer, offset, remaining);
|
||||||
currentStream.Write(buffer, offset, remaining);
|
remaining = 0;
|
||||||
remaining = 0;
|
|
||||||
|
} else { // We're writing into a stream that's followed by another stream
|
||||||
} else { // We're writing into a stream that's followed by another stream
|
|
||||||
|
// Find out how much data we can put into the current stream without
|
||||||
// Find out how much data we can put into the current stream without
|
// enlarging it (if seeking is supported, so is the Length property)
|
||||||
// enlarging it (if seeking is supported, so is the Length property)
|
long currentStreamRemaining = currentStream.Length - streamOffset;
|
||||||
long currentStreamRemaining = currentStream.Length - streamOffset;
|
int bytesToWrite = (int)Math.Min((long)remaining, currentStreamRemaining);
|
||||||
int bytesToWrite = (int)Math.Min((long)remaining, currentStreamRemaining);
|
|
||||||
|
// Write all data that can fit into the current stream
|
||||||
// Write all data that can fit into the current stream
|
currentStream.Position = streamOffset;
|
||||||
currentStream.Position = streamOffset;
|
currentStream.Write(buffer, offset, bytesToWrite);
|
||||||
currentStream.Write(buffer, offset, bytesToWrite);
|
|
||||||
|
// Adjust the offsets and count for the next stream
|
||||||
// Adjust the offsets and count for the next stream
|
offset += bytesToWrite;
|
||||||
offset += bytesToWrite;
|
remaining -= bytesToWrite;
|
||||||
remaining -= bytesToWrite;
|
streamOffset = 0;
|
||||||
streamOffset = 0;
|
++streamIndex;
|
||||||
++streamIndex;
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
} else { // Seeking not supported, append everything to the last stream
|
||||||
} else { // Seeking not supported, append everything to the last stream
|
Stream lastStream = this.streams[this.streams.Length - 1];
|
||||||
Stream lastStream = this.streams[this.streams.Length - 1];
|
if(lastStream.CanSeek) {
|
||||||
if(lastStream.CanSeek) {
|
lastStream.Seek(0, SeekOrigin.End);
|
||||||
lastStream.Seek(0, SeekOrigin.End);
|
}
|
||||||
}
|
lastStream.Write(buffer, offset, remaining);
|
||||||
lastStream.Write(buffer, offset, remaining);
|
}
|
||||||
}
|
|
||||||
|
this.position += count;
|
||||||
this.position += count;
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Streams being combined by the stream chainer</summary>
|
||||||
/// <summary>Streams being combined by the stream chainer</summary>
|
public Stream[] ChainedStreams {
|
||||||
public Stream[] ChainedStreams {
|
get { return this.streams; }
|
||||||
get { return this.streams; }
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Moves the file pointer</summary>
|
||||||
/// <summary>Moves the file pointer</summary>
|
/// <param name="position">New position the file pointer will be moved to</param>
|
||||||
/// <param name="position">New position the file pointer will be moved to</param>
|
private void moveFilePointer(long position) {
|
||||||
private void moveFilePointer(long position) {
|
if(!this.allStreamsCanSeek) {
|
||||||
if(!this.allStreamsCanSeek) {
|
throw makeSeekNotSupportedException("seek");
|
||||||
throw makeSeekNotSupportedException("seek");
|
}
|
||||||
}
|
|
||||||
|
// Seemingly, it is okay to move the file pointer beyond the end of
|
||||||
// Seemingly, it is okay to move the file pointer beyond the end of
|
// the stream until you try to Read() or Write()
|
||||||
// the stream until you try to Read() or Write()
|
this.position = position;
|
||||||
this.position = position;
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Finds the stream index and local offset for an absolute position within
|
||||||
/// Finds the stream index and local offset for an absolute position within
|
/// the combined streams.
|
||||||
/// the combined streams.
|
/// </summary>
|
||||||
/// </summary>
|
/// <param name="overallPosition">Absolute position within the combined streams</param>
|
||||||
/// <param name="overallPosition">Absolute position within the combined streams</param>
|
/// <param name="streamIndex">
|
||||||
/// <param name="streamIndex">
|
/// Index of the stream the overall position falls into
|
||||||
/// Index of the stream the overall position falls into
|
/// </param>
|
||||||
/// </param>
|
/// <param name="streamPosition">
|
||||||
/// <param name="streamPosition">
|
/// Local position within the stream indicated by <paramref name="streamIndex" />
|
||||||
/// Local position within the stream indicated by <paramref name="streamIndex" />
|
/// </param>
|
||||||
/// </param>
|
private void findStreamIndexAndOffset(
|
||||||
private void findStreamIndexAndOffset(
|
long overallPosition, out int streamIndex, out long streamPosition
|
||||||
long overallPosition, out int streamIndex, out long streamPosition
|
) {
|
||||||
) {
|
Debug.Assert(
|
||||||
Debug.Assert(
|
this.allStreamsCanSeek, "Call to findStreamIndexAndOffset() but no seek support"
|
||||||
this.allStreamsCanSeek, "Call to findStreamIndexAndOffset() but no seek support"
|
);
|
||||||
);
|
|
||||||
|
// In case the position is beyond the stream's end, this is what we will
|
||||||
// In case the position is beyond the stream's end, this is what we will
|
// return to the caller
|
||||||
// return to the caller
|
streamIndex = (this.streams.Length - 1);
|
||||||
streamIndex = (this.streams.Length - 1);
|
|
||||||
|
// Search until we have found the stream the position must lie in
|
||||||
// Search until we have found the stream the position must lie in
|
for(int index = 0; index < this.streams.Length; ++index) {
|
||||||
for(int index = 0; index < this.streams.Length; ++index) {
|
long streamLength = this.streams[index].Length;
|
||||||
long streamLength = this.streams[index].Length;
|
|
||||||
|
if(overallPosition < streamLength) {
|
||||||
if(overallPosition < streamLength) {
|
streamIndex = index;
|
||||||
streamIndex = index;
|
break;
|
||||||
break;
|
}
|
||||||
}
|
|
||||||
|
overallPosition -= streamLength;
|
||||||
overallPosition -= streamLength;
|
}
|
||||||
}
|
|
||||||
|
// The overall position will have been decreased by each skipped stream's length,
|
||||||
// The overall position will have been decreased by each skipped stream's length,
|
// so it should now contain the local position for the final stream we checked.
|
||||||
// so it should now contain the local position for the final stream we checked.
|
streamPosition = overallPosition;
|
||||||
streamPosition = overallPosition;
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Determines the capabilities of the chained streams</summary>
|
||||||
/// <summary>Determines the capabilities of the chained streams</summary>
|
/// <remarks>
|
||||||
/// <remarks>
|
/// <para>
|
||||||
/// <para>
|
/// Theoretically, it would be possible to create a stream chainer that supported
|
||||||
/// Theoretically, it would be possible to create a stream chainer that supported
|
/// writing only when the file pointer was on a chained stream with write support,
|
||||||
/// writing only when the file pointer was on a chained stream with write support,
|
/// that could seek within the beginning of the stream until the first chained
|
||||||
/// that could seek within the beginning of the stream until the first chained
|
/// stream with no seek capability was encountered and so on.
|
||||||
/// stream with no seek capability was encountered and so on.
|
/// </para>
|
||||||
/// </para>
|
/// <para>
|
||||||
/// <para>
|
/// However, the interface of the Stream class requires us to make a definitive
|
||||||
/// However, the interface of the Stream class requires us to make a definitive
|
/// statement as to whether the Stream supports seeking, reading and writing.
|
||||||
/// statement as to whether the Stream supports seeking, reading and writing.
|
/// We can't return "maybe" or "mostly" in CanSeek, so the only sane choice that
|
||||||
/// We can't return "maybe" or "mostly" in CanSeek, so the only sane choice that
|
/// doesn't violate the Stream interface is to implement these capabilities as
|
||||||
/// doesn't violate the Stream interface is to implement these capabilities as
|
/// all or nothing - either all streams support a feature, or the stream chainer
|
||||||
/// all or nothing - either all streams support a feature, or the stream chainer
|
/// will report the feature as unsupported.
|
||||||
/// will report the feature as unsupported.
|
/// </para>
|
||||||
/// </para>
|
/// </remarks>
|
||||||
/// </remarks>
|
private void determineCapabilities() {
|
||||||
private void determineCapabilities() {
|
this.allStreamsCanSeek = true;
|
||||||
this.allStreamsCanSeek = true;
|
this.allStreamsCanRead = true;
|
||||||
this.allStreamsCanRead = true;
|
this.allStreamsCanWrite = true;
|
||||||
this.allStreamsCanWrite = true;
|
|
||||||
|
for(int index = 0; index < this.streams.Length; ++index) {
|
||||||
for(int index = 0; index < this.streams.Length; ++index) {
|
this.allStreamsCanSeek &= this.streams[index].CanSeek;
|
||||||
this.allStreamsCanSeek &= this.streams[index].CanSeek;
|
this.allStreamsCanRead &= this.streams[index].CanRead;
|
||||||
this.allStreamsCanRead &= this.streams[index].CanRead;
|
this.allStreamsCanWrite &= this.streams[index].CanWrite;
|
||||||
this.allStreamsCanWrite &= this.streams[index].CanWrite;
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Constructs a NotSupportException for an error caused by one of the chained
|
||||||
/// Constructs a NotSupportException for an error caused by one of the chained
|
/// streams having no seek support
|
||||||
/// streams having no seek support
|
/// </summary>
|
||||||
/// </summary>
|
/// <param name="action">Action that was tried to perform</param>
|
||||||
/// <param name="action">Action that was tried to perform</param>
|
/// <returns>The newly constructed NotSupportedException</returns>
|
||||||
/// <returns>The newly constructed NotSupportedException</returns>
|
private static NotSupportedException makeSeekNotSupportedException(string action) {
|
||||||
private static NotSupportedException makeSeekNotSupportedException(string action) {
|
return new NotSupportedException(
|
||||||
return new NotSupportedException(
|
string.Format(
|
||||||
string.Format(
|
"Can't {0}: at least one of the chained streams does not support seeking",
|
||||||
"Can't {0}: at least one of the chained streams does not support seeking",
|
action
|
||||||
action
|
)
|
||||||
)
|
);
|
||||||
);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Streams that have been chained together</summary>
|
||||||
/// <summary>Streams that have been chained together</summary>
|
private Stream[] streams;
|
||||||
private Stream[] streams;
|
/// <summary>Current position of the overall file pointer</summary>
|
||||||
/// <summary>Current position of the overall file pointer</summary>
|
private long position;
|
||||||
private long position;
|
/// <summary>Stream we're currently reading from if seeking is not supported</summary>
|
||||||
/// <summary>Stream we're currently reading from if seeking is not supported</summary>
|
/// <remarks>
|
||||||
/// <remarks>
|
/// If seeking is not supported, the stream chainer will read from each stream
|
||||||
/// If seeking is not supported, the stream chainer will read from each stream
|
/// until the end was reached
|
||||||
/// until the end was reached
|
/// sequentially
|
||||||
/// sequentially
|
/// </remarks>
|
||||||
/// </remarks>
|
private int activeReadStreamIndex;
|
||||||
private int activeReadStreamIndex;
|
/// <summary>Position in the current read stream if seeking is not supported</summary>
|
||||||
/// <summary>Position in the current read stream if seeking is not supported</summary>
|
/// <remarks>
|
||||||
/// <remarks>
|
/// If there is a mix of streams supporting seeking and not supporting seeking, we
|
||||||
/// If there is a mix of streams supporting seeking and not supporting seeking, we
|
/// need to keep track of the read index for those streams that do. If, for example,
|
||||||
/// need to keep track of the read index for those streams that do. If, for example,
|
/// the last stream is written to and read from in succession, the file pointer
|
||||||
/// the last stream is written to and read from in succession, the file pointer
|
/// of that stream would have been moved to the end by the write attempt, skipping
|
||||||
/// of that stream would have been moved to the end by the write attempt, skipping
|
/// data that should have been read in the following read attempt.
|
||||||
/// data that should have been read in the following read attempt.
|
/// </remarks>
|
||||||
/// </remarks>
|
private long activeReadStreamPosition;
|
||||||
private long activeReadStreamPosition;
|
|
||||||
|
/// <summary>Whether all of the chained streams support seeking</summary>
|
||||||
/// <summary>Whether all of the chained streams support seeking</summary>
|
private bool allStreamsCanSeek;
|
||||||
private bool allStreamsCanSeek;
|
/// <summary>Whether all of the chained streams support reading</summary>
|
||||||
/// <summary>Whether all of the chained streams support reading</summary>
|
private bool allStreamsCanRead;
|
||||||
private bool allStreamsCanRead;
|
/// <summary>Whether all of the chained streams support writing</summary>
|
||||||
/// <summary>Whether all of the chained streams support writing</summary>
|
private bool allStreamsCanWrite;
|
||||||
private bool allStreamsCanWrite;
|
|
||||||
|
}
|
||||||
}
|
|
||||||
|
} // namespace Nuclex.Support.IO
|
||||||
} // namespace Nuclex.Support.IO
|
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,261 +1,260 @@
|
||||||
#region CPL License
|
#region Apache License 2.0
|
||||||
/*
|
/*
|
||||||
Nuclex Framework
|
Nuclex .NET Framework
|
||||||
Copyright (C) 2002-2017 Nuclex Development Labs
|
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
|
||||||
|
|
||||||
This library is free software; you can redistribute it and/or
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
modify it under the terms of the IBM Common Public License as
|
you may not use this file except in compliance with the License.
|
||||||
published by the IBM Corporation; either version 1.0 of the
|
You may obtain a copy of the License at
|
||||||
License, or (at your option) any later version.
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
This library is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
Unless required by applicable law or agreed to in writing, software
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
IBM Common Public License for more details.
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
You should have received a copy of the IBM Common Public
|
limitations under the License.
|
||||||
License along with this library
|
*/
|
||||||
*/
|
#endregion // Apache License 2.0
|
||||||
#endregion
|
|
||||||
|
using System;
|
||||||
using System;
|
using System.IO;
|
||||||
using System.IO;
|
|
||||||
|
namespace Nuclex.Support.IO {
|
||||||
namespace Nuclex.Support.IO {
|
|
||||||
|
/// <summary>Wraps a stream and exposes only a limited region of its data</summary>
|
||||||
/// <summary>Wraps a stream and exposes only a limited region of its data</summary>
|
public class PartialStream : Stream {
|
||||||
public class PartialStream : Stream {
|
|
||||||
|
/// <summary>Initializes a new partial stream</summary>
|
||||||
/// <summary>Initializes a new partial stream</summary>
|
/// <param name="stream">
|
||||||
/// <param name="stream">
|
/// Stream the wrapper will make a limited region accessible of
|
||||||
/// Stream the wrapper will make a limited region accessible of
|
/// </param>
|
||||||
/// </param>
|
/// <param name="start">
|
||||||
/// <param name="start">
|
/// Start index in the stream which becomes the beginning for the wrapper
|
||||||
/// Start index in the stream which becomes the beginning for the wrapper
|
/// </param>
|
||||||
/// </param>
|
/// <param name="length">
|
||||||
/// <param name="length">
|
/// Length the wrapped stream should report and allow access to
|
||||||
/// Length the wrapped stream should report and allow access to
|
/// </param>
|
||||||
/// </param>
|
public PartialStream(Stream stream, long start, long length) {
|
||||||
public PartialStream(Stream stream, long start, long length) {
|
if(start < 0) {
|
||||||
if(start < 0) {
|
throw new ArgumentException("Start index must not be less than 0", "start");
|
||||||
throw new ArgumentException("Start index must not be less than 0", "start");
|
}
|
||||||
}
|
|
||||||
|
if(stream.CanSeek) {
|
||||||
if(stream.CanSeek) {
|
if(start + length > stream.Length) {
|
||||||
if(start + length > stream.Length) {
|
throw new ArgumentException(
|
||||||
throw new ArgumentException(
|
"Partial stream exceeds end of full stream", "length"
|
||||||
"Partial stream exceeds end of full stream", "length"
|
);
|
||||||
);
|
}
|
||||||
}
|
} else {
|
||||||
} else {
|
if(start != 0) {
|
||||||
if(start != 0) {
|
throw new ArgumentException(
|
||||||
throw new ArgumentException(
|
"The only valid start for unseekable streams is 0", "start"
|
||||||
"The only valid start for unseekable streams is 0", "start"
|
);
|
||||||
);
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
this.stream = stream;
|
||||||
this.stream = stream;
|
this.start = start;
|
||||||
this.start = start;
|
this.length = length;
|
||||||
this.length = length;
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Whether data can be read from the stream</summary>
|
||||||
/// <summary>Whether data can be read from the stream</summary>
|
public override bool CanRead {
|
||||||
public override bool CanRead {
|
get { return this.stream.CanRead; }
|
||||||
get { return this.stream.CanRead; }
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Whether the stream supports seeking</summary>
|
||||||
/// <summary>Whether the stream supports seeking</summary>
|
public override bool CanSeek {
|
||||||
public override bool CanSeek {
|
get { return this.stream.CanSeek; }
|
||||||
get { return this.stream.CanSeek; }
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Whether data can be written into the stream</summary>
|
||||||
/// <summary>Whether data can be written into the stream</summary>
|
public override bool CanWrite {
|
||||||
public override bool CanWrite {
|
get { return this.stream.CanWrite; }
|
||||||
get { return this.stream.CanWrite; }
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Clears all buffers for this stream and causes any buffered data to be written
|
||||||
/// Clears all buffers for this stream and causes any buffered data to be written
|
/// to the underlying device.
|
||||||
/// to the underlying device.
|
/// </summary>
|
||||||
/// </summary>
|
public override void Flush() {
|
||||||
public override void Flush() {
|
this.stream.Flush();
|
||||||
this.stream.Flush();
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Length of the stream in bytes</summary>
|
||||||
/// <summary>Length of the stream in bytes</summary>
|
/// <exception cref="NotSupportedException">
|
||||||
/// <exception cref="NotSupportedException">
|
/// The wrapped stream does not support seeking
|
||||||
/// The wrapped stream does not support seeking
|
/// </exception>
|
||||||
/// </exception>
|
public override long Length {
|
||||||
public override long Length {
|
get { return this.length; }
|
||||||
get { return this.length; }
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Absolute position of the file pointer within the stream</summary>
|
||||||
/// <summary>Absolute position of the file pointer within the stream</summary>
|
/// <exception cref="NotSupportedException">
|
||||||
/// <exception cref="NotSupportedException">
|
/// The wrapped stream does not support seeking
|
||||||
/// The wrapped stream does not support seeking
|
/// </exception>
|
||||||
/// </exception>
|
public override long Position {
|
||||||
public override long Position {
|
get {
|
||||||
get {
|
if(!this.stream.CanSeek) {
|
||||||
if(!this.stream.CanSeek) {
|
throw makeSeekNotSupportedException("seek");
|
||||||
throw makeSeekNotSupportedException("seek");
|
}
|
||||||
}
|
|
||||||
|
return this.position;
|
||||||
return this.position;
|
}
|
||||||
}
|
set { moveFilePointer(value); }
|
||||||
set { moveFilePointer(value); }
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Reads a sequence of bytes from the stream and advances the position of
|
||||||
/// Reads a sequence of bytes from the stream and advances the position of
|
/// the file pointer by the number of bytes read.
|
||||||
/// the file pointer by the number of bytes read.
|
/// </summary>
|
||||||
/// </summary>
|
/// <param name="buffer">Buffer that will receive the data read from the stream</param>
|
||||||
/// <param name="buffer">Buffer that will receive the data read from the stream</param>
|
/// <param name="offset">
|
||||||
/// <param name="offset">
|
/// Offset in the buffer at which the stream will place the data read
|
||||||
/// Offset in the buffer at which the stream will place the data read
|
/// </param>
|
||||||
/// </param>
|
/// <param name="count">Maximum number of bytes that will be read</param>
|
||||||
/// <param name="count">Maximum number of bytes that will be read</param>
|
/// <returns>
|
||||||
/// <returns>
|
/// The number of bytes that were actually read from the stream and written into
|
||||||
/// The number of bytes that were actually read from the stream and written into
|
/// the provided buffer
|
||||||
/// the provided buffer
|
/// </returns>
|
||||||
/// </returns>
|
/// <exception cref="NotSupportedException">
|
||||||
/// <exception cref="NotSupportedException">
|
/// The wrapped stream does not support reading
|
||||||
/// The wrapped stream does not support reading
|
/// </exception>
|
||||||
/// </exception>
|
public override int Read(byte[] buffer, int offset, int count) {
|
||||||
public override int Read(byte[] buffer, int offset, int count) {
|
if(!this.stream.CanRead) {
|
||||||
if(!this.stream.CanRead) {
|
throw new NotSupportedException(
|
||||||
throw new NotSupportedException(
|
"Can't read: the wrapped stream doesn't support reading"
|
||||||
"Can't read: the wrapped stream doesn't support reading"
|
);
|
||||||
);
|
}
|
||||||
}
|
|
||||||
|
long remaining = this.length - this.position;
|
||||||
long remaining = this.length - this.position;
|
int bytesToRead = (int)Math.Min(count, remaining);
|
||||||
int bytesToRead = (int)Math.Min(count, remaining);
|
|
||||||
|
if(this.stream.CanSeek) {
|
||||||
if(this.stream.CanSeek) {
|
this.stream.Position = this.position + this.start;
|
||||||
this.stream.Position = this.position + this.start;
|
}
|
||||||
}
|
int bytesRead = this.stream.Read(buffer, offset, bytesToRead);
|
||||||
int bytesRead = this.stream.Read(buffer, offset, bytesToRead);
|
this.position += bytesRead;
|
||||||
this.position += bytesRead;
|
|
||||||
|
return bytesRead;
|
||||||
return bytesRead;
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Changes the position of the file pointer</summary>
|
||||||
/// <summary>Changes the position of the file pointer</summary>
|
/// <param name="offset">
|
||||||
/// <param name="offset">
|
/// Offset to move the file pointer by, relative to the position indicated by
|
||||||
/// Offset to move the file pointer by, relative to the position indicated by
|
/// the <paramref name="origin" /> parameter.
|
||||||
/// the <paramref name="origin" /> parameter.
|
/// </param>
|
||||||
/// </param>
|
/// <param name="origin">
|
||||||
/// <param name="origin">
|
/// Reference point relative to which the file pointer is placed
|
||||||
/// Reference point relative to which the file pointer is placed
|
/// </param>
|
||||||
/// </param>
|
/// <returns>The new absolute position within the stream</returns>
|
||||||
/// <returns>The new absolute position within the stream</returns>
|
public override long Seek(long offset, SeekOrigin origin) {
|
||||||
public override long Seek(long offset, SeekOrigin origin) {
|
switch(origin) {
|
||||||
switch(origin) {
|
case SeekOrigin.Begin: {
|
||||||
case SeekOrigin.Begin: {
|
return Position = offset;
|
||||||
return Position = offset;
|
}
|
||||||
}
|
case SeekOrigin.Current: {
|
||||||
case SeekOrigin.Current: {
|
return Position += offset;
|
||||||
return Position += offset;
|
}
|
||||||
}
|
case SeekOrigin.End: {
|
||||||
case SeekOrigin.End: {
|
return Position = (Length + offset);
|
||||||
return Position = (Length + offset);
|
}
|
||||||
}
|
default: {
|
||||||
default: {
|
throw new ArgumentException("Invalid seek origin", "origin");
|
||||||
throw new ArgumentException("Invalid seek origin", "origin");
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Changes the length of the stream</summary>
|
||||||
/// <summary>Changes the length of the stream</summary>
|
/// <param name="value">New length the stream shall have</param>
|
||||||
/// <param name="value">New length the stream shall have</param>
|
/// <exception cref="NotSupportedException">
|
||||||
/// <exception cref="NotSupportedException">
|
/// Always, the stream chainer does not support the SetLength() operation
|
||||||
/// Always, the stream chainer does not support the SetLength() operation
|
/// </exception>
|
||||||
/// </exception>
|
public override void SetLength(long value) {
|
||||||
public override void SetLength(long value) {
|
throw new NotSupportedException("Resizing partial streams is not supported");
|
||||||
throw new NotSupportedException("Resizing partial streams is not supported");
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Writes a sequence of bytes to the stream and advances the position of
|
||||||
/// Writes a sequence of bytes to the stream and advances the position of
|
/// the file pointer by the number of bytes written.
|
||||||
/// the file pointer by the number of bytes written.
|
/// </summary>
|
||||||
/// </summary>
|
/// <param name="buffer">
|
||||||
/// <param name="buffer">
|
/// Buffer containing the data that will be written to the stream
|
||||||
/// Buffer containing the data that will be written to the stream
|
/// </param>
|
||||||
/// </param>
|
/// <param name="offset">
|
||||||
/// <param name="offset">
|
/// Offset in the buffer at which the data to be written starts
|
||||||
/// Offset in the buffer at which the data to be written starts
|
/// </param>
|
||||||
/// </param>
|
/// <param name="count">Number of bytes that will be written into the stream</param>
|
||||||
/// <param name="count">Number of bytes that will be written into the stream</param>
|
/// <remarks>
|
||||||
/// <remarks>
|
/// The behavior of this method is as follows: If one or more chained streams
|
||||||
/// The behavior of this method is as follows: If one or more chained streams
|
/// do not support seeking, all data is appended to the final stream in the
|
||||||
/// do not support seeking, all data is appended to the final stream in the
|
/// chain. Otherwise, writing will begin with the stream the current file pointer
|
||||||
/// chain. Otherwise, writing will begin with the stream the current file pointer
|
/// offset falls into. If the end of that stream is reached, writing continues
|
||||||
/// offset falls into. If the end of that stream is reached, writing continues
|
/// in the next stream. On the last stream, writing more data into the stream
|
||||||
/// in the next stream. On the last stream, writing more data into the stream
|
/// that it current size allows will enlarge the stream.
|
||||||
/// that it current size allows will enlarge the stream.
|
/// </remarks>
|
||||||
/// </remarks>
|
public override void Write(byte[] buffer, int offset, int count) {
|
||||||
public override void Write(byte[] buffer, int offset, int count) {
|
long remaining = this.length - this.position;
|
||||||
long remaining = this.length - this.position;
|
if(count > remaining) {
|
||||||
if(count > remaining) {
|
throw new NotSupportedException(
|
||||||
throw new NotSupportedException(
|
"Cannot extend the length of the partial stream"
|
||||||
"Cannot extend the length of the partial stream"
|
);
|
||||||
);
|
}
|
||||||
}
|
|
||||||
|
if(this.stream.CanSeek) {
|
||||||
if(this.stream.CanSeek) {
|
this.stream.Position = this.position + this.start;
|
||||||
this.stream.Position = this.position + this.start;
|
}
|
||||||
}
|
this.stream.Write(buffer, offset, count);
|
||||||
this.stream.Write(buffer, offset, count);
|
|
||||||
|
this.position += count;
|
||||||
this.position += count;
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Stream being wrapped by the partial stream wrapper</summary>
|
||||||
/// <summary>Stream being wrapped by the partial stream wrapper</summary>
|
public Stream CompleteStream {
|
||||||
public Stream CompleteStream {
|
get { return this.stream; }
|
||||||
get { return this.stream; }
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Moves the file pointer</summary>
|
||||||
/// <summary>Moves the file pointer</summary>
|
/// <param name="position">New position the file pointer will be moved to</param>
|
||||||
/// <param name="position">New position the file pointer will be moved to</param>
|
private void moveFilePointer(long position) {
|
||||||
private void moveFilePointer(long position) {
|
if(!this.stream.CanSeek) {
|
||||||
if(!this.stream.CanSeek) {
|
throw makeSeekNotSupportedException("seek");
|
||||||
throw makeSeekNotSupportedException("seek");
|
}
|
||||||
}
|
|
||||||
|
// Seemingly, it is okay to move the file pointer beyond the end of
|
||||||
// Seemingly, it is okay to move the file pointer beyond the end of
|
// the stream until you try to Read() or Write()
|
||||||
// the stream until you try to Read() or Write()
|
this.position = position;
|
||||||
this.position = position;
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Constructs a NotSupportException for an error caused by the wrapped
|
||||||
/// Constructs a NotSupportException for an error caused by the wrapped
|
/// stream having no seek support
|
||||||
/// stream having no seek support
|
/// </summary>
|
||||||
/// </summary>
|
/// <param name="action">Action that was tried to perform</param>
|
||||||
/// <param name="action">Action that was tried to perform</param>
|
/// <returns>The newly constructed NotSupportedException</returns>
|
||||||
/// <returns>The newly constructed NotSupportedException</returns>
|
private static NotSupportedException makeSeekNotSupportedException(string action) {
|
||||||
private static NotSupportedException makeSeekNotSupportedException(string action) {
|
return new NotSupportedException(
|
||||||
return new NotSupportedException(
|
string.Format(
|
||||||
string.Format(
|
"Can't {0}: the wrapped stream does not support seeking",
|
||||||
"Can't {0}: the wrapped stream does not support seeking",
|
action
|
||||||
action
|
)
|
||||||
)
|
);
|
||||||
);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Streams that have been chained together</summary>
|
||||||
/// <summary>Streams that have been chained together</summary>
|
private Stream stream;
|
||||||
private Stream stream;
|
/// <summary>Start index of the partial stream in the wrapped stream</summary>
|
||||||
/// <summary>Start index of the partial stream in the wrapped stream</summary>
|
private long start;
|
||||||
private long start;
|
/// <summary>Zero-based position of the partial stream's file pointer</summary>
|
||||||
/// <summary>Zero-based position of the partial stream's file pointer</summary>
|
/// <remarks>
|
||||||
/// <remarks>
|
/// If the stream does not support seeking, the position will simply be counted
|
||||||
/// If the stream does not support seeking, the position will simply be counted
|
/// up until it reaches <see cref="PartialStream.length" />.
|
||||||
/// up until it reaches <see cref="PartialStream.length" />.
|
/// </remarks>
|
||||||
/// </remarks>
|
private long position;
|
||||||
private long position;
|
/// <summary>Length of the partial stream</summary>
|
||||||
/// <summary>Length of the partial stream</summary>
|
private long length;
|
||||||
private long length;
|
|
||||||
|
}
|
||||||
}
|
|
||||||
|
} // namespace Nuclex.Support.IO
|
||||||
} // namespace Nuclex.Support.IO
|
|
||||||
|
|
|
@ -1,330 +1,329 @@
|
||||||
#region CPL License
|
#region Apache License 2.0
|
||||||
/*
|
/*
|
||||||
Nuclex Framework
|
Nuclex .NET Framework
|
||||||
Copyright (C) 2002-2017 Nuclex Development Labs
|
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
|
||||||
|
|
||||||
This library is free software; you can redistribute it and/or
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
modify it under the terms of the IBM Common Public License as
|
you may not use this file except in compliance with the License.
|
||||||
published by the IBM Corporation; either version 1.0 of the
|
You may obtain a copy of the License at
|
||||||
License, or (at your option) any later version.
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
This library is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
Unless required by applicable law or agreed to in writing, software
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
IBM Common Public License for more details.
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
You should have received a copy of the IBM Common Public
|
limitations under the License.
|
||||||
License along with this library
|
*/
|
||||||
*/
|
#endregion // Apache License 2.0
|
||||||
#endregion
|
|
||||||
|
using System;
|
||||||
using System;
|
using System.IO;
|
||||||
using System.IO;
|
|
||||||
|
#if UNITTEST
|
||||||
#if UNITTEST
|
|
||||||
|
using NUnit.Framework;
|
||||||
using NUnit.Framework;
|
|
||||||
|
namespace Nuclex.Support.IO {
|
||||||
namespace Nuclex.Support.IO {
|
|
||||||
|
/// <summary>Unit Test for the ring buffer class</summary>
|
||||||
/// <summary>Unit Test for the ring buffer class</summary>
|
[TestFixture]
|
||||||
[TestFixture]
|
internal class RingMemoryStreamTest {
|
||||||
internal class RingMemoryStreamTest {
|
|
||||||
|
/// <summary>Prepares some test data for the units test methods</summary>
|
||||||
/// <summary>Prepares some test data for the units test methods</summary>
|
[TestFixtureSetUp]
|
||||||
[TestFixtureSetUp]
|
public void Setup() {
|
||||||
public void Setup() {
|
this.testBytes = new byte[20];
|
||||||
this.testBytes = new byte[20];
|
for(int i = 0; i < 20; ++i)
|
||||||
for(int i = 0; i < 20; ++i)
|
this.testBytes[i] = (byte)i;
|
||||||
this.testBytes[i] = (byte)i;
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Ensures that the ring buffer blocks write attempts that would exceed its capacity
|
||||||
/// Ensures that the ring buffer blocks write attempts that would exceed its capacity
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void TestWriteTooLargeChunk() {
|
||||||
public void TestWriteTooLargeChunk() {
|
Assert.Throws<OverflowException>(
|
||||||
Assert.Throws<OverflowException>(
|
delegate() { new RingMemoryStream(10).Write(this.testBytes, 0, 11); }
|
||||||
delegate() { new RingMemoryStream(10).Write(this.testBytes, 0, 11); }
|
);
|
||||||
);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Ensures that the ring buffer still accepts write attempts that would fill the
|
||||||
/// Ensures that the ring buffer still accepts write attempts that would fill the
|
/// entire buffer in one go.
|
||||||
/// entire buffer in one go.
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void TestWriteBarelyFittingChunk() {
|
||||||
public void TestWriteBarelyFittingChunk() {
|
new RingMemoryStream(10).Write(this.testBytes, 0, 10);
|
||||||
new RingMemoryStream(10).Write(this.testBytes, 0, 10);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Ensures that the ring buffer correctly manages write attempts that have to
|
||||||
/// Ensures that the ring buffer correctly manages write attempts that have to
|
/// be split at the end of the ring buffer
|
||||||
/// be split at the end of the ring buffer
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void TestWriteSplitBlock() {
|
||||||
public void TestWriteSplitBlock() {
|
RingMemoryStream testRing = new RingMemoryStream(10);
|
||||||
RingMemoryStream testRing = new RingMemoryStream(10);
|
testRing.Write(this.testBytes, 0, 8);
|
||||||
testRing.Write(this.testBytes, 0, 8);
|
testRing.Read(this.testBytes, 0, 5);
|
||||||
testRing.Read(this.testBytes, 0, 5);
|
testRing.Write(this.testBytes, 0, 7);
|
||||||
testRing.Write(this.testBytes, 0, 7);
|
|
||||||
|
byte[] actual = new byte[10];
|
||||||
byte[] actual = new byte[10];
|
testRing.Read(actual, 0, 10);
|
||||||
testRing.Read(actual, 0, 10);
|
Assert.AreEqual(new byte[] { 5, 6, 7, 0, 1, 2, 3, 4, 5, 6 }, actual);
|
||||||
Assert.AreEqual(new byte[] { 5, 6, 7, 0, 1, 2, 3, 4, 5, 6 }, actual);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Ensures that the ring buffer correctly manages write attempts that write into
|
||||||
/// Ensures that the ring buffer correctly manages write attempts that write into
|
/// the gap after the ring buffer's data has become split
|
||||||
/// the gap after the ring buffer's data has become split
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void TestWriteSplitAndLinearBlock() {
|
||||||
public void TestWriteSplitAndLinearBlock() {
|
RingMemoryStream testRing = new RingMemoryStream(10);
|
||||||
RingMemoryStream testRing = new RingMemoryStream(10);
|
testRing.Write(this.testBytes, 0, 8);
|
||||||
testRing.Write(this.testBytes, 0, 8);
|
testRing.Read(this.testBytes, 0, 5);
|
||||||
testRing.Read(this.testBytes, 0, 5);
|
testRing.Write(this.testBytes, 0, 5);
|
||||||
testRing.Write(this.testBytes, 0, 5);
|
testRing.Write(this.testBytes, 0, 2);
|
||||||
testRing.Write(this.testBytes, 0, 2);
|
|
||||||
|
byte[] actual = new byte[10];
|
||||||
byte[] actual = new byte[10];
|
testRing.Read(actual, 0, 10);
|
||||||
testRing.Read(actual, 0, 10);
|
Assert.AreEqual(new byte[] { 5, 6, 7, 0, 1, 2, 3, 4, 0, 1 }, actual);
|
||||||
Assert.AreEqual(new byte[] { 5, 6, 7, 0, 1, 2, 3, 4, 0, 1 }, actual);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Ensures that the ring buffer still detects write that would exceed its capacity
|
||||||
/// Ensures that the ring buffer still detects write that would exceed its capacity
|
/// if they write into the gap after the ring buffer's data has become split
|
||||||
/// if they write into the gap after the ring buffer's data has become split
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void TestWriteSplitAndLinearTooLargeBlock() {
|
||||||
public void TestWriteSplitAndLinearTooLargeBlock() {
|
RingMemoryStream testRing = new RingMemoryStream(10);
|
||||||
RingMemoryStream testRing = new RingMemoryStream(10);
|
testRing.Write(this.testBytes, 0, 8);
|
||||||
testRing.Write(this.testBytes, 0, 8);
|
testRing.Read(this.testBytes, 0, 5);
|
||||||
testRing.Read(this.testBytes, 0, 5);
|
testRing.Write(this.testBytes, 0, 5);
|
||||||
testRing.Write(this.testBytes, 0, 5);
|
Assert.Throws<OverflowException>(
|
||||||
Assert.Throws<OverflowException>(
|
delegate() { testRing.Write(this.testBytes, 0, 3); }
|
||||||
delegate() { testRing.Write(this.testBytes, 0, 3); }
|
);
|
||||||
);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Tests whether the ring buffer correctly handles fragmentation</summary>
|
||||||
/// <summary>Tests whether the ring buffer correctly handles fragmentation</summary>
|
[Test]
|
||||||
[Test]
|
public void TestSplitBlockWrappedRead() {
|
||||||
public void TestSplitBlockWrappedRead() {
|
RingMemoryStream testRing = new RingMemoryStream(10);
|
||||||
RingMemoryStream testRing = new RingMemoryStream(10);
|
testRing.Write(this.testBytes, 0, 10);
|
||||||
testRing.Write(this.testBytes, 0, 10);
|
testRing.Read(this.testBytes, 0, 5);
|
||||||
testRing.Read(this.testBytes, 0, 5);
|
testRing.Write(this.testBytes, 0, 5);
|
||||||
testRing.Write(this.testBytes, 0, 5);
|
|
||||||
|
byte[] actual = new byte[10];
|
||||||
byte[] actual = new byte[10];
|
testRing.Read(actual, 0, 10);
|
||||||
testRing.Read(actual, 0, 10);
|
Assert.AreEqual(new byte[] { 5, 6, 7, 8, 9, 0, 1, 2, 3, 4 }, actual);
|
||||||
Assert.AreEqual(new byte[] { 5, 6, 7, 8, 9, 0, 1, 2, 3, 4 }, actual);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Tests whether the ring buffer correctly handles fragmentation</summary>
|
||||||
/// <summary>Tests whether the ring buffer correctly handles fragmentation</summary>
|
[Test]
|
||||||
[Test]
|
public void TestSplitBlockLinearRead() {
|
||||||
public void TestSplitBlockLinearRead() {
|
RingMemoryStream testRing = new RingMemoryStream(10);
|
||||||
RingMemoryStream testRing = new RingMemoryStream(10);
|
testRing.Write(this.testBytes, 0, 10);
|
||||||
testRing.Write(this.testBytes, 0, 10);
|
testRing.Read(this.testBytes, 0, 5);
|
||||||
testRing.Read(this.testBytes, 0, 5);
|
testRing.Write(this.testBytes, 0, 5);
|
||||||
testRing.Write(this.testBytes, 0, 5);
|
|
||||||
|
byte[] actual = new byte[5];
|
||||||
byte[] actual = new byte[5];
|
testRing.Read(actual, 0, 5);
|
||||||
testRing.Read(actual, 0, 5);
|
Assert.AreEqual(new byte[] { 5, 6, 7, 8, 9 }, actual);
|
||||||
Assert.AreEqual(new byte[] { 5, 6, 7, 8, 9 }, actual);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Tests whether the ring buffer correctly returns partial data if more
|
||||||
/// Tests whether the ring buffer correctly returns partial data if more
|
/// data is requested than is contained in it.
|
||||||
/// data is requested than is contained in it.
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void TestEndOfStream() {
|
||||||
public void TestEndOfStream() {
|
byte[] tempBytes = new byte[10];
|
||||||
byte[] tempBytes = new byte[10];
|
|
||||||
|
RingMemoryStream testRing = new RingMemoryStream(10);
|
||||||
RingMemoryStream testRing = new RingMemoryStream(10);
|
Assert.AreEqual(0, testRing.Read(tempBytes, 0, 5));
|
||||||
Assert.AreEqual(0, testRing.Read(tempBytes, 0, 5));
|
|
||||||
|
testRing.Write(this.testBytes, 0, 5);
|
||||||
testRing.Write(this.testBytes, 0, 5);
|
Assert.AreEqual(5, testRing.Read(tempBytes, 0, 10));
|
||||||
Assert.AreEqual(5, testRing.Read(tempBytes, 0, 10));
|
|
||||||
|
testRing.Write(this.testBytes, 0, 6);
|
||||||
testRing.Write(this.testBytes, 0, 6);
|
testRing.Read(tempBytes, 0, 5);
|
||||||
testRing.Read(tempBytes, 0, 5);
|
testRing.Write(this.testBytes, 0, 9);
|
||||||
testRing.Write(this.testBytes, 0, 9);
|
Assert.AreEqual(10, testRing.Read(tempBytes, 0, 20));
|
||||||
Assert.AreEqual(10, testRing.Read(tempBytes, 0, 20));
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Validates that the ring buffer can extend its capacity without loosing data
|
||||||
/// Validates that the ring buffer can extend its capacity without loosing data
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void TestCapacityIncrease() {
|
||||||
public void TestCapacityIncrease() {
|
RingMemoryStream testRing = new RingMemoryStream(10);
|
||||||
RingMemoryStream testRing = new RingMemoryStream(10);
|
testRing.Write(this.testBytes, 0, 10);
|
||||||
testRing.Write(this.testBytes, 0, 10);
|
|
||||||
|
testRing.Capacity = 20;
|
||||||
testRing.Capacity = 20;
|
byte[] actual = new byte[10];
|
||||||
byte[] actual = new byte[10];
|
testRing.Read(actual, 0, 10);
|
||||||
testRing.Read(actual, 0, 10);
|
|
||||||
|
Assert.AreEqual(new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }, actual);
|
||||||
Assert.AreEqual(new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }, actual);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Validates that the ring buffer can reduce its capacity without loosing data
|
||||||
/// Validates that the ring buffer can reduce its capacity without loosing data
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void TestCapacityDecrease() {
|
||||||
public void TestCapacityDecrease() {
|
RingMemoryStream testRing = new RingMemoryStream(20);
|
||||||
RingMemoryStream testRing = new RingMemoryStream(20);
|
testRing.Write(this.testBytes, 0, 10);
|
||||||
testRing.Write(this.testBytes, 0, 10);
|
|
||||||
|
testRing.Capacity = 10;
|
||||||
testRing.Capacity = 10;
|
byte[] actual = new byte[10];
|
||||||
byte[] actual = new byte[10];
|
testRing.Read(actual, 0, 10);
|
||||||
testRing.Read(actual, 0, 10);
|
|
||||||
|
Assert.AreEqual(new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }, actual);
|
||||||
Assert.AreEqual(new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }, actual);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Checks that an exception is thrown when the ring buffer's capacity is
|
||||||
/// Checks that an exception is thrown when the ring buffer's capacity is
|
/// reduced so much it would have to give up some of its contained data
|
||||||
/// reduced so much it would have to give up some of its contained data
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void TestCapacityDecreaseException() {
|
||||||
public void TestCapacityDecreaseException() {
|
RingMemoryStream testRing = new RingMemoryStream(20);
|
||||||
RingMemoryStream testRing = new RingMemoryStream(20);
|
testRing.Write(this.testBytes, 0, 20);
|
||||||
testRing.Write(this.testBytes, 0, 20);
|
|
||||||
|
Assert.Throws<ArgumentOutOfRangeException>(
|
||||||
Assert.Throws<ArgumentOutOfRangeException>(
|
delegate() { testRing.Capacity = 10; }
|
||||||
delegate() { testRing.Capacity = 10; }
|
);
|
||||||
);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Tests whether the Capacity property returns the current capacity</summary>
|
||||||
/// <summary>Tests whether the Capacity property returns the current capacity</summary>
|
[Test]
|
||||||
[Test]
|
public void TestCapacity() {
|
||||||
public void TestCapacity() {
|
RingMemoryStream testRing = new RingMemoryStream(123);
|
||||||
RingMemoryStream testRing = new RingMemoryStream(123);
|
|
||||||
|
Assert.AreEqual(123, testRing.Capacity);
|
||||||
Assert.AreEqual(123, testRing.Capacity);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Ensures that the CanRead property returns true</summary>
|
||||||
/// <summary>Ensures that the CanRead property returns true</summary>
|
[Test]
|
||||||
[Test]
|
public void TestCanRead() {
|
||||||
public void TestCanRead() {
|
Assert.IsTrue(new RingMemoryStream(10).CanRead);
|
||||||
Assert.IsTrue(new RingMemoryStream(10).CanRead);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Ensures that the CanSeek property returns false</summary>
|
||||||
/// <summary>Ensures that the CanSeek property returns false</summary>
|
[Test]
|
||||||
[Test]
|
public void TestCanSeek() {
|
||||||
public void TestCanSeek() {
|
Assert.IsFalse(new RingMemoryStream(10).CanSeek);
|
||||||
Assert.IsFalse(new RingMemoryStream(10).CanSeek);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Ensures that the CanWrite property returns true</summary>
|
||||||
/// <summary>Ensures that the CanWrite property returns true</summary>
|
[Test]
|
||||||
[Test]
|
public void TestCanWrite() {
|
||||||
public void TestCanWrite() {
|
Assert.IsTrue(new RingMemoryStream(10).CanWrite);
|
||||||
Assert.IsTrue(new RingMemoryStream(10).CanWrite);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Tests whether the auto reset feature works (resets the buffer pointer to the
|
||||||
/// Tests whether the auto reset feature works (resets the buffer pointer to the
|
/// left end of the buffer when it gets empty; mainly a performance feature).
|
||||||
/// left end of the buffer when it gets empty; mainly a performance feature).
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void TestAutoReset() {
|
||||||
public void TestAutoReset() {
|
byte[] tempBytes = new byte[10];
|
||||||
byte[] tempBytes = new byte[10];
|
RingMemoryStream testRing = new RingMemoryStream(10);
|
||||||
RingMemoryStream testRing = new RingMemoryStream(10);
|
|
||||||
|
testRing.Write(this.testBytes, 0, 8);
|
||||||
testRing.Write(this.testBytes, 0, 8);
|
testRing.Read(tempBytes, 0, 2);
|
||||||
testRing.Read(tempBytes, 0, 2);
|
testRing.Read(tempBytes, 0, 2);
|
||||||
testRing.Read(tempBytes, 0, 2);
|
testRing.Read(tempBytes, 0, 1);
|
||||||
testRing.Read(tempBytes, 0, 1);
|
testRing.Read(tempBytes, 0, 1);
|
||||||
testRing.Read(tempBytes, 0, 1);
|
|
||||||
|
Assert.AreEqual(2, testRing.Length);
|
||||||
Assert.AreEqual(2, testRing.Length);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Verifies that an exception is thrown when the Position property of the ring
|
||||||
/// Verifies that an exception is thrown when the Position property of the ring
|
/// memory stream is used to retrieve the current file pointer position
|
||||||
/// memory stream is used to retrieve the current file pointer position
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void TestThrowOnRetrievePosition() {
|
||||||
public void TestThrowOnRetrievePosition() {
|
Assert.Throws<NotSupportedException>(
|
||||||
Assert.Throws<NotSupportedException>(
|
delegate() { Console.WriteLine(new RingMemoryStream(10).Position); }
|
||||||
delegate() { Console.WriteLine(new RingMemoryStream(10).Position); }
|
);
|
||||||
);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Verifies that an exception is thrown when the Position property of the ring
|
||||||
/// Verifies that an exception is thrown when the Position property of the ring
|
/// memory stream is used to modify the current file pointer position
|
||||||
/// memory stream is used to modify the current file pointer position
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void TestThrowOnAssignPosition() {
|
||||||
public void TestThrowOnAssignPosition() {
|
Assert.Throws<NotSupportedException>(
|
||||||
Assert.Throws<NotSupportedException>(
|
delegate() { new RingMemoryStream(10).Position = 0; }
|
||||||
delegate() { new RingMemoryStream(10).Position = 0; }
|
);
|
||||||
);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Verifies that an exception is thrown when the Seek() method of the ring memory
|
||||||
/// Verifies that an exception is thrown when the Seek() method of the ring memory
|
/// stream is attempted to be used
|
||||||
/// stream is attempted to be used
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void TestThrowOnSeek() {
|
||||||
public void TestThrowOnSeek() {
|
Assert.Throws<NotSupportedException>(
|
||||||
Assert.Throws<NotSupportedException>(
|
delegate() { new RingMemoryStream(10).Seek(0, SeekOrigin.Begin); }
|
||||||
delegate() { new RingMemoryStream(10).Seek(0, SeekOrigin.Begin); }
|
);
|
||||||
);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Verifies that an exception is thrown when the SetLength() method of the ring
|
||||||
/// Verifies that an exception is thrown when the SetLength() method of the ring
|
/// memory stream is attempted to be used
|
||||||
/// memory stream is attempted to be used
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void TestThrowOnSetLength() {
|
||||||
public void TestThrowOnSetLength() {
|
Assert.Throws<NotSupportedException>(
|
||||||
Assert.Throws<NotSupportedException>(
|
delegate() { new RingMemoryStream(10).SetLength(10); }
|
||||||
delegate() { new RingMemoryStream(10).SetLength(10); }
|
);
|
||||||
);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Tests the Flush() method of the ring memory stream, which is either a dummy
|
||||||
/// Tests the Flush() method of the ring memory stream, which is either a dummy
|
/// implementation or has no side effects
|
||||||
/// implementation or has no side effects
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void TestFlush() {
|
||||||
public void TestFlush() {
|
new RingMemoryStream(10).Flush();
|
||||||
new RingMemoryStream(10).Flush();
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Tests whether the length property is updated in accordance to the data written
|
||||||
/// Tests whether the length property is updated in accordance to the data written
|
/// into the ring memory stream
|
||||||
/// into the ring memory stream
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void TestLengthOnLinearBlock() {
|
||||||
public void TestLengthOnLinearBlock() {
|
RingMemoryStream testRing = new RingMemoryStream(10);
|
||||||
RingMemoryStream testRing = new RingMemoryStream(10);
|
testRing.Write(new byte[10], 0, 10);
|
||||||
testRing.Write(new byte[10], 0, 10);
|
|
||||||
|
Assert.AreEqual(10, testRing.Length);
|
||||||
Assert.AreEqual(10, testRing.Length);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Tests whether the length property is updated in accordance to the data written
|
||||||
/// Tests whether the length property is updated in accordance to the data written
|
/// into the ring memory stream when the data is split within the stream
|
||||||
/// into the ring memory stream when the data is split within the stream
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void TestLengthOnSplitBlock() {
|
||||||
public void TestLengthOnSplitBlock() {
|
RingMemoryStream testRing = new RingMemoryStream(10);
|
||||||
RingMemoryStream testRing = new RingMemoryStream(10);
|
|
||||||
|
testRing.Write(new byte[10], 0, 10);
|
||||||
testRing.Write(new byte[10], 0, 10);
|
testRing.Read(new byte[5], 0, 5);
|
||||||
testRing.Read(new byte[5], 0, 5);
|
testRing.Write(new byte[5], 0, 5);
|
||||||
testRing.Write(new byte[5], 0, 5);
|
|
||||||
|
Assert.AreEqual(10, testRing.Length);
|
||||||
Assert.AreEqual(10, testRing.Length);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Test data for the ring buffer unit tests</summary>
|
||||||
/// <summary>Test data for the ring buffer unit tests</summary>
|
private byte[] testBytes;
|
||||||
private byte[] testBytes;
|
|
||||||
|
}
|
||||||
}
|
|
||||||
|
} // namespace Nuclex.Support.IO
|
||||||
} // namespace Nuclex.Support.IO
|
|
||||||
|
#endif // UNITTEST
|
||||||
#endif // UNITTEST
|
|
||||||
|
|
|
@ -1,256 +1,255 @@
|
||||||
#region CPL License
|
#region Apache License 2.0
|
||||||
/*
|
/*
|
||||||
Nuclex Framework
|
Nuclex .NET Framework
|
||||||
Copyright (C) 2002-2017 Nuclex Development Labs
|
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
|
||||||
|
|
||||||
This library is free software; you can redistribute it and/or
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
modify it under the terms of the IBM Common Public License as
|
you may not use this file except in compliance with the License.
|
||||||
published by the IBM Corporation; either version 1.0 of the
|
You may obtain a copy of the License at
|
||||||
License, or (at your option) any later version.
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
This library is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
Unless required by applicable law or agreed to in writing, software
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
IBM Common Public License for more details.
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
You should have received a copy of the IBM Common Public
|
limitations under the License.
|
||||||
License along with this library
|
*/
|
||||||
*/
|
#endregion // Apache License 2.0
|
||||||
#endregion
|
|
||||||
|
using System;
|
||||||
using System;
|
using System.IO;
|
||||||
using System.IO;
|
|
||||||
|
namespace Nuclex.Support.IO {
|
||||||
namespace Nuclex.Support.IO {
|
|
||||||
|
/// <summary>Specialized memory stream for ring buffers</summary>
|
||||||
/// <summary>Specialized memory stream for ring buffers</summary>
|
/// <remarks>
|
||||||
/// <remarks>
|
/// This ring buffer class is specialized for binary data and tries to achieve
|
||||||
/// This ring buffer class is specialized for binary data and tries to achieve
|
/// optimal efficiency when storing and retrieving chunks of several bytes
|
||||||
/// optimal efficiency when storing and retrieving chunks of several bytes
|
/// at once. Typical use cases include audio and network buffers where one party
|
||||||
/// at once. Typical use cases include audio and network buffers where one party
|
/// is responsible for refilling the buffer at regular intervals while the other
|
||||||
/// is responsible for refilling the buffer at regular intervals while the other
|
/// constantly streams data out of it.
|
||||||
/// constantly streams data out of it.
|
/// </remarks>
|
||||||
/// </remarks>
|
public class RingMemoryStream : Stream {
|
||||||
public class RingMemoryStream : Stream {
|
|
||||||
|
/// <summary>Initializes a new ring memory stream</summary>
|
||||||
/// <summary>Initializes a new ring memory stream</summary>
|
/// <param name="capacity">Maximum capacity of the stream</param>
|
||||||
/// <param name="capacity">Maximum capacity of the stream</param>
|
public RingMemoryStream(int capacity) {
|
||||||
public RingMemoryStream(int capacity) {
|
this.ringBuffer = new MemoryStream(capacity);
|
||||||
this.ringBuffer = new MemoryStream(capacity);
|
this.ringBuffer.SetLength(capacity);
|
||||||
this.ringBuffer.SetLength(capacity);
|
this.empty = true;
|
||||||
this.empty = true;
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Maximum amount of data that will fit into the ring memory stream</summary>
|
||||||
/// <summary>Maximum amount of data that will fit into the ring memory stream</summary>
|
/// <exception cref="ArgumentOutOfRangeException">
|
||||||
/// <exception cref="ArgumentOutOfRangeException">
|
/// Thrown if the new capacity is too small for the data already contained
|
||||||
/// Thrown if the new capacity is too small for the data already contained
|
/// in the ring buffer.
|
||||||
/// in the ring buffer.
|
/// </exception>
|
||||||
/// </exception>
|
public long Capacity {
|
||||||
public long Capacity {
|
get { return this.ringBuffer.Length; }
|
||||||
get { return this.ringBuffer.Length; }
|
set {
|
||||||
set {
|
int length = (int)Length;
|
||||||
int length = (int)Length;
|
if(value < length) {
|
||||||
if(value < length) {
|
throw new ArgumentOutOfRangeException(
|
||||||
throw new ArgumentOutOfRangeException(
|
"New capacity is less than the stream's current length"
|
||||||
"New capacity is less than the stream's current length"
|
);
|
||||||
);
|
}
|
||||||
}
|
|
||||||
|
// This could be done in a more efficient manner than just replacing
|
||||||
// This could be done in a more efficient manner than just replacing
|
// the entire buffer, but since this operation will probably be only called
|
||||||
// the entire buffer, but since this operation will probably be only called
|
// once during the lifetime of the application, if at all, I don't see
|
||||||
// once during the lifetime of the application, if at all, I don't see
|
// the need to optimize it...
|
||||||
// the need to optimize it...
|
MemoryStream newBuffer = new MemoryStream((int)value);
|
||||||
MemoryStream newBuffer = new MemoryStream((int)value);
|
|
||||||
|
newBuffer.SetLength(value);
|
||||||
newBuffer.SetLength(value);
|
if(length > 0) {
|
||||||
if(length > 0) {
|
Read(newBuffer.GetBuffer(), 0, length);
|
||||||
Read(newBuffer.GetBuffer(), 0, length);
|
}
|
||||||
}
|
|
||||||
|
this.ringBuffer.Close(); // Equals dispose of the old buffer
|
||||||
this.ringBuffer.Close(); // Equals dispose of the old buffer
|
this.ringBuffer = newBuffer;
|
||||||
this.ringBuffer = newBuffer;
|
this.startIndex = 0;
|
||||||
this.startIndex = 0;
|
this.endIndex = length;
|
||||||
this.endIndex = length;
|
}
|
||||||
}
|
|
||||||
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Whether it's possible to read from this stream</summary>
|
||||||
/// <summary>Whether it's possible to read from this stream</summary>
|
public override bool CanRead { get { return true; } }
|
||||||
public override bool CanRead { get { return true; } }
|
/// <summary>Whether this stream supports random access</summary>
|
||||||
/// <summary>Whether this stream supports random access</summary>
|
public override bool CanSeek { get { return false; } }
|
||||||
public override bool CanSeek { get { return false; } }
|
/// <summary>Whether it's possible to write into this stream</summary>
|
||||||
/// <summary>Whether it's possible to write into this stream</summary>
|
public override bool CanWrite { get { return true; } }
|
||||||
public override bool CanWrite { get { return true; } }
|
/// <summary>Flushes the buffers and writes down unsaved data</summary>
|
||||||
/// <summary>Flushes the buffers and writes down unsaved data</summary>
|
public override void Flush() { }
|
||||||
public override void Flush() { }
|
|
||||||
|
/// <summary>Current length of the stream</summary>
|
||||||
/// <summary>Current length of the stream</summary>
|
public override long Length {
|
||||||
public override long Length {
|
get {
|
||||||
get {
|
if((this.endIndex > this.startIndex) || this.empty) {
|
||||||
if((this.endIndex > this.startIndex) || this.empty) {
|
return this.endIndex - this.startIndex;
|
||||||
return this.endIndex - this.startIndex;
|
} else {
|
||||||
} else {
|
return this.ringBuffer.Length - this.startIndex + this.endIndex;
|
||||||
return this.ringBuffer.Length - this.startIndex + this.endIndex;
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Current cursor position within the stream</summary>
|
||||||
/// <summary>Current cursor position within the stream</summary>
|
/// <exception cref="NotSupportedException">Always</exception>
|
||||||
/// <exception cref="NotSupportedException">Always</exception>
|
public override long Position {
|
||||||
public override long Position {
|
get { throw new NotSupportedException("The ring buffer does not support seeking"); }
|
||||||
get { throw new NotSupportedException("The ring buffer does not support seeking"); }
|
set { throw new NotSupportedException("The ring buffer does not support seeking"); }
|
||||||
set { throw new NotSupportedException("The ring buffer does not support seeking"); }
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Reads data from the beginning of the stream</summary>
|
||||||
/// <summary>Reads data from the beginning of the stream</summary>
|
/// <param name="buffer">Buffer in which to store the data</param>
|
||||||
/// <param name="buffer">Buffer in which to store the data</param>
|
/// <param name="offset">Starting index at which to begin writing the buffer</param>
|
||||||
/// <param name="offset">Starting index at which to begin writing the buffer</param>
|
/// <param name="count">Number of bytes to read from the stream</param>
|
||||||
/// <param name="count">Number of bytes to read from the stream</param>
|
/// <returns>Die Number of bytes actually read</returns>
|
||||||
/// <returns>Die Number of bytes actually read</returns>
|
public override int Read(byte[] buffer, int offset, int count) {
|
||||||
public override int Read(byte[] buffer, int offset, int count) {
|
|
||||||
|
// The end index lies behind the start index (usual case), so the
|
||||||
// The end index lies behind the start index (usual case), so the
|
// ring memory is not fragmented. Example: |-----<#######>-----|
|
||||||
// ring memory is not fragmented. Example: |-----<#######>-----|
|
if((this.startIndex < this.endIndex) || this.empty) {
|
||||||
if((this.startIndex < this.endIndex) || this.empty) {
|
|
||||||
|
// The Stream interface requires us to return less than the requested
|
||||||
// The Stream interface requires us to return less than the requested
|
// number of bytes if we don't have enough data
|
||||||
// number of bytes if we don't have enough data
|
count = Math.Min(count, this.endIndex - this.startIndex);
|
||||||
count = Math.Min(count, this.endIndex - this.startIndex);
|
if(count > 0) {
|
||||||
if(count > 0) {
|
this.ringBuffer.Position = this.startIndex;
|
||||||
this.ringBuffer.Position = this.startIndex;
|
this.ringBuffer.Read(buffer, offset, count);
|
||||||
this.ringBuffer.Read(buffer, offset, count);
|
this.startIndex += count;
|
||||||
this.startIndex += count;
|
|
||||||
|
if(this.startIndex == this.endIndex) {
|
||||||
if(this.startIndex == this.endIndex) {
|
setEmpty();
|
||||||
setEmpty();
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
} else { // The end index lies in front of the start index
|
||||||
} else { // The end index lies in front of the start index
|
|
||||||
|
// With the end before the start index, the data in the ring memory
|
||||||
// With the end before the start index, the data in the ring memory
|
// stream is fragmented. Example: |#####>-------<#####|
|
||||||
// stream is fragmented. Example: |#####>-------<#####|
|
int linearAvailable = (int)this.ringBuffer.Length - this.startIndex;
|
||||||
int linearAvailable = (int)this.ringBuffer.Length - this.startIndex;
|
|
||||||
|
// Will this read process cross the end of the ring buffer, requiring us to
|
||||||
// Will this read process cross the end of the ring buffer, requiring us to
|
// read the data in 2 steps?
|
||||||
// read the data in 2 steps?
|
if(count > linearAvailable) {
|
||||||
if(count > linearAvailable) {
|
|
||||||
|
// The Stream interface requires us to return less than the requested
|
||||||
// The Stream interface requires us to return less than the requested
|
// number of bytes if we don't have enough data
|
||||||
// number of bytes if we don't have enough data
|
count = Math.Min(count, linearAvailable + this.endIndex);
|
||||||
count = Math.Min(count, linearAvailable + this.endIndex);
|
|
||||||
|
this.ringBuffer.Position = this.startIndex;
|
||||||
this.ringBuffer.Position = this.startIndex;
|
this.ringBuffer.Read(buffer, offset, linearAvailable);
|
||||||
this.ringBuffer.Read(buffer, offset, linearAvailable);
|
this.ringBuffer.Position = 0;
|
||||||
this.ringBuffer.Position = 0;
|
this.startIndex = count - linearAvailable;
|
||||||
this.startIndex = count - linearAvailable;
|
this.ringBuffer.Read(buffer, offset + linearAvailable, this.startIndex);
|
||||||
this.ringBuffer.Read(buffer, offset + linearAvailable, this.startIndex);
|
|
||||||
|
} else { // Nope, the amount of requested data can be read in one piece
|
||||||
} else { // Nope, the amount of requested data can be read in one piece
|
this.ringBuffer.Position = this.startIndex;
|
||||||
this.ringBuffer.Position = this.startIndex;
|
this.ringBuffer.Read(buffer, offset, count);
|
||||||
this.ringBuffer.Read(buffer, offset, count);
|
this.startIndex += count;
|
||||||
this.startIndex += count;
|
|
||||||
|
}
|
||||||
}
|
|
||||||
|
// If we consumed the entire ring buffer, set the empty flag and move
|
||||||
// If we consumed the entire ring buffer, set the empty flag and move
|
// the indexes back to zero for better performance
|
||||||
// the indexes back to zero for better performance
|
if(this.startIndex == this.endIndex) {
|
||||||
if(this.startIndex == this.endIndex) {
|
setEmpty();
|
||||||
setEmpty();
|
}
|
||||||
}
|
|
||||||
|
}
|
||||||
}
|
|
||||||
|
return count;
|
||||||
return count;
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Appends data to the end of the stream</summary>
|
||||||
/// <summary>Appends data to the end of the stream</summary>
|
/// <param name="buffer">Buffer containing the data to append</param>
|
||||||
/// <param name="buffer">Buffer containing the data to append</param>
|
/// <param name="offset">Starting index of the data in the buffer</param>
|
||||||
/// <param name="offset">Starting index of the data in the buffer</param>
|
/// <param name="count">Number of bytes to write to the stream</param>
|
||||||
/// <param name="count">Number of bytes to write to the stream</param>
|
/// <exception cref="OverflowException">When the ring buffer is full</exception>
|
||||||
/// <exception cref="OverflowException">When the ring buffer is full</exception>
|
public override void Write(byte[] buffer, int offset, int count) {
|
||||||
public override void Write(byte[] buffer, int offset, int count) {
|
|
||||||
|
// The end index lies behind the start index (usual case), so the
|
||||||
// The end index lies behind the start index (usual case), so the
|
// unused buffer space is fragmented. Example: |-----<#######>-----|
|
||||||
// unused buffer space is fragmented. Example: |-----<#######>-----|
|
if((this.startIndex < this.endIndex) || this.empty) {
|
||||||
if((this.startIndex < this.endIndex) || this.empty) {
|
int linearAvailable = (int)(this.ringBuffer.Length - this.endIndex);
|
||||||
int linearAvailable = (int)(this.ringBuffer.Length - this.endIndex);
|
|
||||||
|
// If the data to be written would cross the ring memory stream's end,
|
||||||
// If the data to be written would cross the ring memory stream's end,
|
// we have to check that there's enough space at the beginning of the
|
||||||
// we have to check that there's enough space at the beginning of the
|
// stream to contain the remainder of the data.
|
||||||
// stream to contain the remainder of the data.
|
if(count > linearAvailable) {
|
||||||
if(count > linearAvailable) {
|
if(count > (linearAvailable + this.startIndex))
|
||||||
if(count > (linearAvailable + this.startIndex))
|
throw new OverflowException("Data does not fit in buffer");
|
||||||
throw new OverflowException("Data does not fit in buffer");
|
|
||||||
|
this.ringBuffer.Position = this.endIndex;
|
||||||
this.ringBuffer.Position = this.endIndex;
|
this.ringBuffer.Write(buffer, offset, linearAvailable);
|
||||||
this.ringBuffer.Write(buffer, offset, linearAvailable);
|
this.ringBuffer.Position = 0;
|
||||||
this.ringBuffer.Position = 0;
|
this.endIndex = count - linearAvailable;
|
||||||
this.endIndex = count - linearAvailable;
|
this.ringBuffer.Write(buffer, offset + linearAvailable, this.endIndex);
|
||||||
this.ringBuffer.Write(buffer, offset + linearAvailable, this.endIndex);
|
|
||||||
|
} else { // All data can be appended at the current stream position
|
||||||
} else { // All data can be appended at the current stream position
|
this.ringBuffer.Position = this.endIndex;
|
||||||
this.ringBuffer.Position = this.endIndex;
|
this.ringBuffer.Write(buffer, offset, count);
|
||||||
this.ringBuffer.Write(buffer, offset, count);
|
this.endIndex += count;
|
||||||
this.endIndex += count;
|
}
|
||||||
}
|
|
||||||
|
this.empty = false;
|
||||||
this.empty = false;
|
|
||||||
|
} else { // The end index lies before the start index
|
||||||
} else { // The end index lies before the start index
|
|
||||||
|
// The ring memory stream has been fragmented. This means the gap into which
|
||||||
// The ring memory stream has been fragmented. This means the gap into which
|
// we are about to write is not fragmented. Example: |#####>-------<#####|
|
||||||
// we are about to write is not fragmented. Example: |#####>-------<#####|
|
if(count > (this.startIndex - this.endIndex))
|
||||||
if(count > (this.startIndex - this.endIndex))
|
throw new OverflowException("Data does not fit in buffer");
|
||||||
throw new OverflowException("Data does not fit in buffer");
|
|
||||||
|
// Because the gap isn't fragmented, we can be sure that a single
|
||||||
// Because the gap isn't fragmented, we can be sure that a single
|
// write call will suffice.
|
||||||
// write call will suffice.
|
this.ringBuffer.Position = this.endIndex;
|
||||||
this.ringBuffer.Position = this.endIndex;
|
this.ringBuffer.Write(buffer, offset, count);
|
||||||
this.ringBuffer.Write(buffer, offset, count);
|
this.endIndex += count;
|
||||||
this.endIndex += count;
|
|
||||||
|
}
|
||||||
}
|
|
||||||
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Jumps to the specified location within the stream</summary>
|
||||||
/// <summary>Jumps to the specified location within the stream</summary>
|
/// <param name="offset">Position to jump to</param>
|
||||||
/// <param name="offset">Position to jump to</param>
|
/// <param name="origin">Origin towards which to interpret the offset</param>
|
||||||
/// <param name="origin">Origin towards which to interpret the offset</param>
|
/// <returns>The new offset within the stream</returns>
|
||||||
/// <returns>The new offset within the stream</returns>
|
/// <exception cref="NotSupportedException">Always</exception>
|
||||||
/// <exception cref="NotSupportedException">Always</exception>
|
public override long Seek(long offset, SeekOrigin origin) {
|
||||||
public override long Seek(long offset, SeekOrigin origin) {
|
throw new NotSupportedException("The ring buffer does not support seeking");
|
||||||
throw new NotSupportedException("The ring buffer does not support seeking");
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Changes the length of the stream</summary>
|
||||||
/// <summary>Changes the length of the stream</summary>
|
/// <param name="value">New length to resize the stream to</param>
|
||||||
/// <param name="value">New length to resize the stream to</param>
|
/// <exception cref="NotSupportedException">Always</exception>
|
||||||
/// <exception cref="NotSupportedException">Always</exception>
|
public override void SetLength(long value) {
|
||||||
public override void SetLength(long value) {
|
throw new NotSupportedException("This operation is not supported");
|
||||||
throw new NotSupportedException("This operation is not supported");
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Resets the stream to its empty state</summary>
|
||||||
/// <summary>Resets the stream to its empty state</summary>
|
private void setEmpty() {
|
||||||
private void setEmpty() {
|
this.empty = true;
|
||||||
this.empty = true;
|
this.startIndex = 0;
|
||||||
this.startIndex = 0;
|
this.endIndex = 0;
|
||||||
this.endIndex = 0;
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Internal stream containing the ring buffer data</summary>
|
||||||
/// <summary>Internal stream containing the ring buffer data</summary>
|
private MemoryStream ringBuffer;
|
||||||
private MemoryStream ringBuffer;
|
/// <summary>Start index of the data within the ring buffer</summary>
|
||||||
/// <summary>Start index of the data within the ring buffer</summary>
|
private int startIndex;
|
||||||
private int startIndex;
|
/// <summary>End index of the data within the ring buffer</summary>
|
||||||
/// <summary>End index of the data within the ring buffer</summary>
|
private int endIndex;
|
||||||
private int endIndex;
|
/// <summary>Whether the ring buffer is empty</summary>
|
||||||
/// <summary>Whether the ring buffer is empty</summary>
|
/// <remarks>
|
||||||
/// <remarks>
|
/// This field is required to differentiate between the ring buffer being
|
||||||
/// This field is required to differentiate between the ring buffer being
|
/// filled to the limit and being totally empty, because in both cases,
|
||||||
/// filled to the limit and being totally empty, because in both cases,
|
/// the start index and the end index will be the same.
|
||||||
/// the start index and the end index will be the same.
|
/// </remarks>
|
||||||
/// </remarks>
|
private bool empty;
|
||||||
private bool empty;
|
|
||||||
|
}
|
||||||
}
|
|
||||||
|
} // namespace Nuclex.Support.IO
|
||||||
} // namespace Nuclex.Support.IO
|
|
||||||
|
|
|
@ -1,130 +1,129 @@
|
||||||
#region CPL License
|
#region Apache License 2.0
|
||||||
/*
|
/*
|
||||||
Nuclex Framework
|
Nuclex .NET Framework
|
||||||
Copyright (C) 2002-2017 Nuclex Development Labs
|
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
|
||||||
|
|
||||||
This library is free software; you can redistribute it and/or
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
modify it under the terms of the IBM Common Public License as
|
you may not use this file except in compliance with the License.
|
||||||
published by the IBM Corporation; either version 1.0 of the
|
You may obtain a copy of the License at
|
||||||
License, or (at your option) any later version.
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
This library is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
Unless required by applicable law or agreed to in writing, software
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
IBM Common Public License for more details.
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
You should have received a copy of the IBM Common Public
|
limitations under the License.
|
||||||
License along with this library
|
*/
|
||||||
*/
|
#endregion // Apache License 2.0
|
||||||
#endregion
|
|
||||||
|
#if UNITTEST
|
||||||
#if UNITTEST
|
|
||||||
|
using System;
|
||||||
using System;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Generic;
|
|
||||||
|
using NUnit.Framework;
|
||||||
using NUnit.Framework;
|
|
||||||
|
namespace Nuclex.Support {
|
||||||
namespace Nuclex.Support {
|
|
||||||
|
/// <summary>Contains unit tests for the integer helper class</summary>
|
||||||
/// <summary>Contains unit tests for the integer helper class</summary>
|
[TestFixture]
|
||||||
[TestFixture]
|
internal class IntegerHelperTest {
|
||||||
internal class IntegerHelperTest {
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Verifies that the next power of 2 calculation works for long integers
|
||||||
/// Verifies that the next power of 2 calculation works for long integers
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void TestNextPowerOf2ULong() {
|
||||||
public void TestNextPowerOf2ULong() {
|
Assert.AreEqual(1UL, IntegerHelper.NextPowerOf2(0UL));
|
||||||
Assert.AreEqual(1UL, IntegerHelper.NextPowerOf2(0UL));
|
Assert.AreEqual(1UL, IntegerHelper.NextPowerOf2(1UL));
|
||||||
Assert.AreEqual(1UL, IntegerHelper.NextPowerOf2(1UL));
|
Assert.AreEqual(2UL, IntegerHelper.NextPowerOf2(2UL));
|
||||||
Assert.AreEqual(2UL, IntegerHelper.NextPowerOf2(2UL));
|
Assert.AreEqual(4UL, IntegerHelper.NextPowerOf2(3UL));
|
||||||
Assert.AreEqual(4UL, IntegerHelper.NextPowerOf2(3UL));
|
Assert.AreEqual(4UL, IntegerHelper.NextPowerOf2(4UL));
|
||||||
Assert.AreEqual(4UL, IntegerHelper.NextPowerOf2(4UL));
|
Assert.AreEqual(
|
||||||
Assert.AreEqual(
|
9223372036854775808UL, IntegerHelper.NextPowerOf2(4611686018427387905UL)
|
||||||
9223372036854775808UL, IntegerHelper.NextPowerOf2(4611686018427387905UL)
|
);
|
||||||
);
|
Assert.AreEqual(
|
||||||
Assert.AreEqual(
|
9223372036854775808UL, IntegerHelper.NextPowerOf2(9223372036854775807UL)
|
||||||
9223372036854775808UL, IntegerHelper.NextPowerOf2(9223372036854775807UL)
|
);
|
||||||
);
|
Assert.AreEqual(
|
||||||
Assert.AreEqual(
|
9223372036854775808UL, IntegerHelper.NextPowerOf2(9223372036854775808UL)
|
||||||
9223372036854775808UL, IntegerHelper.NextPowerOf2(9223372036854775808UL)
|
);
|
||||||
);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Verifies that the next power of 2 calculation works for long integers
|
||||||
/// Verifies that the next power of 2 calculation works for long integers
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void TestNextPowerOf2Long() {
|
||||||
public void TestNextPowerOf2Long() {
|
Assert.AreEqual(1L, IntegerHelper.NextPowerOf2(0L));
|
||||||
Assert.AreEqual(1L, IntegerHelper.NextPowerOf2(0L));
|
Assert.AreEqual(1L, IntegerHelper.NextPowerOf2(1L));
|
||||||
Assert.AreEqual(1L, IntegerHelper.NextPowerOf2(1L));
|
Assert.AreEqual(2L, IntegerHelper.NextPowerOf2(2L));
|
||||||
Assert.AreEqual(2L, IntegerHelper.NextPowerOf2(2L));
|
Assert.AreEqual(4L, IntegerHelper.NextPowerOf2(3L));
|
||||||
Assert.AreEqual(4L, IntegerHelper.NextPowerOf2(3L));
|
Assert.AreEqual(4L, IntegerHelper.NextPowerOf2(4L));
|
||||||
Assert.AreEqual(4L, IntegerHelper.NextPowerOf2(4L));
|
Assert.AreEqual(4611686018427387904L, IntegerHelper.NextPowerOf2(2305843009213693953L));
|
||||||
Assert.AreEqual(4611686018427387904L, IntegerHelper.NextPowerOf2(2305843009213693953L));
|
Assert.AreEqual(4611686018427387904L, IntegerHelper.NextPowerOf2(4611686018427387903L));
|
||||||
Assert.AreEqual(4611686018427387904L, IntegerHelper.NextPowerOf2(4611686018427387903L));
|
Assert.AreEqual(4611686018427387904L, IntegerHelper.NextPowerOf2(4611686018427387904L));
|
||||||
Assert.AreEqual(4611686018427387904L, IntegerHelper.NextPowerOf2(4611686018427387904L));
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Verifies that the next power of 2 calculation works for integers
|
||||||
/// Verifies that the next power of 2 calculation works for integers
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void TestNextPowerOf2UInt() {
|
||||||
public void TestNextPowerOf2UInt() {
|
Assert.AreEqual(1U, IntegerHelper.NextPowerOf2(0U));
|
||||||
Assert.AreEqual(1U, IntegerHelper.NextPowerOf2(0U));
|
Assert.AreEqual(1U, IntegerHelper.NextPowerOf2(1U));
|
||||||
Assert.AreEqual(1U, IntegerHelper.NextPowerOf2(1U));
|
Assert.AreEqual(2U, IntegerHelper.NextPowerOf2(2U));
|
||||||
Assert.AreEqual(2U, IntegerHelper.NextPowerOf2(2U));
|
Assert.AreEqual(4U, IntegerHelper.NextPowerOf2(3U));
|
||||||
Assert.AreEqual(4U, IntegerHelper.NextPowerOf2(3U));
|
Assert.AreEqual(4U, IntegerHelper.NextPowerOf2(4U));
|
||||||
Assert.AreEqual(4U, IntegerHelper.NextPowerOf2(4U));
|
Assert.AreEqual(2147483648U, IntegerHelper.NextPowerOf2(1073741825U));
|
||||||
Assert.AreEqual(2147483648U, IntegerHelper.NextPowerOf2(1073741825U));
|
Assert.AreEqual(2147483648U, IntegerHelper.NextPowerOf2(2147483647U));
|
||||||
Assert.AreEqual(2147483648U, IntegerHelper.NextPowerOf2(2147483647U));
|
Assert.AreEqual(2147483648U, IntegerHelper.NextPowerOf2(2147483648U));
|
||||||
Assert.AreEqual(2147483648U, IntegerHelper.NextPowerOf2(2147483648U));
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Verifies that the next power of 2 calculation works for integers
|
||||||
/// Verifies that the next power of 2 calculation works for integers
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void TestNextPowerOf2Int() {
|
||||||
public void TestNextPowerOf2Int() {
|
Assert.AreEqual(1, IntegerHelper.NextPowerOf2(0));
|
||||||
Assert.AreEqual(1, IntegerHelper.NextPowerOf2(0));
|
Assert.AreEqual(1, IntegerHelper.NextPowerOf2(1));
|
||||||
Assert.AreEqual(1, IntegerHelper.NextPowerOf2(1));
|
Assert.AreEqual(2, IntegerHelper.NextPowerOf2(2));
|
||||||
Assert.AreEqual(2, IntegerHelper.NextPowerOf2(2));
|
Assert.AreEqual(4, IntegerHelper.NextPowerOf2(3));
|
||||||
Assert.AreEqual(4, IntegerHelper.NextPowerOf2(3));
|
Assert.AreEqual(4, IntegerHelper.NextPowerOf2(4));
|
||||||
Assert.AreEqual(4, IntegerHelper.NextPowerOf2(4));
|
Assert.AreEqual(1073741824, IntegerHelper.NextPowerOf2(536870913));
|
||||||
Assert.AreEqual(1073741824, IntegerHelper.NextPowerOf2(536870913));
|
Assert.AreEqual(1073741824, IntegerHelper.NextPowerOf2(1073741823));
|
||||||
Assert.AreEqual(1073741824, IntegerHelper.NextPowerOf2(1073741823));
|
Assert.AreEqual(1073741824, IntegerHelper.NextPowerOf2(1073741824));
|
||||||
Assert.AreEqual(1073741824, IntegerHelper.NextPowerOf2(1073741824));
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Verifies that the bit counting method for integers works</summary>
|
||||||
/// <summary>Verifies that the bit counting method for integers works</summary>
|
[Test]
|
||||||
[Test]
|
public void TestCountBitsInInteger() {
|
||||||
public void TestCountBitsInInteger() {
|
Assert.AreEqual(0, IntegerHelper.CountBits(0));
|
||||||
Assert.AreEqual(0, IntegerHelper.CountBits(0));
|
Assert.AreEqual(32, IntegerHelper.CountBits(-1));
|
||||||
Assert.AreEqual(32, IntegerHelper.CountBits(-1));
|
Assert.AreEqual(16, IntegerHelper.CountBits(0x55555555));
|
||||||
Assert.AreEqual(16, IntegerHelper.CountBits(0x55555555));
|
Assert.AreEqual(16, IntegerHelper.CountBits(0xAAAAAAAA));
|
||||||
Assert.AreEqual(16, IntegerHelper.CountBits(0xAAAAAAAA));
|
|
||||||
|
for (int bitIndex = 0; bitIndex < 32; ++bitIndex) {
|
||||||
for (int bitIndex = 0; bitIndex < 32; ++bitIndex) {
|
Assert.AreEqual(1, IntegerHelper.CountBits(1 << bitIndex));
|
||||||
Assert.AreEqual(1, IntegerHelper.CountBits(1 << bitIndex));
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Verifies that the bit counting method for long integers works</summary>
|
||||||
/// <summary>Verifies that the bit counting method for long integers works</summary>
|
[Test]
|
||||||
[Test]
|
public void TestCountBitsInLongInteger() {
|
||||||
public void TestCountBitsInLongInteger() {
|
Assert.AreEqual(0, IntegerHelper.CountBits(0L));
|
||||||
Assert.AreEqual(0, IntegerHelper.CountBits(0L));
|
Assert.AreEqual(64, IntegerHelper.CountBits(-1L));
|
||||||
Assert.AreEqual(64, IntegerHelper.CountBits(-1L));
|
Assert.AreEqual(32, IntegerHelper.CountBits(0x5555555555555555));
|
||||||
Assert.AreEqual(32, IntegerHelper.CountBits(0x5555555555555555));
|
Assert.AreEqual(32, IntegerHelper.CountBits(0xAAAAAAAAAAAAAAAA));
|
||||||
Assert.AreEqual(32, IntegerHelper.CountBits(0xAAAAAAAAAAAAAAAA));
|
|
||||||
|
for (int bitIndex = 0; bitIndex < 64; ++bitIndex) {
|
||||||
for (int bitIndex = 0; bitIndex < 64; ++bitIndex) {
|
Assert.AreEqual(1, IntegerHelper.CountBits(1 << bitIndex));
|
||||||
Assert.AreEqual(1, IntegerHelper.CountBits(1 << bitIndex));
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
}
|
||||||
}
|
|
||||||
|
} // namespace Nuclex.Support
|
||||||
} // namespace Nuclex.Support
|
|
||||||
|
#endif // UNITTEST
|
||||||
#endif // UNITTEST
|
|
||||||
|
|
|
@ -1,127 +1,126 @@
|
||||||
#region CPL License
|
#region Apache License 2.0
|
||||||
/*
|
/*
|
||||||
Nuclex Framework
|
Nuclex .NET Framework
|
||||||
Copyright (C) 2002-2017 Nuclex Development Labs
|
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
|
||||||
|
|
||||||
This library is free software; you can redistribute it and/or
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
modify it under the terms of the IBM Common Public License as
|
you may not use this file except in compliance with the License.
|
||||||
published by the IBM Corporation; either version 1.0 of the
|
You may obtain a copy of the License at
|
||||||
License, or (at your option) any later version.
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
This library is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
Unless required by applicable law or agreed to in writing, software
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
IBM Common Public License for more details.
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
You should have received a copy of the IBM Common Public
|
limitations under the License.
|
||||||
License along with this library
|
*/
|
||||||
*/
|
#endregion // Apache License 2.0
|
||||||
#endregion
|
|
||||||
|
using System;
|
||||||
using System;
|
|
||||||
|
namespace Nuclex.Support {
|
||||||
namespace Nuclex.Support {
|
|
||||||
|
/// <summary>Helper methods for working with integer types</summary>
|
||||||
/// <summary>Helper methods for working with integer types</summary>
|
public static class IntegerHelper {
|
||||||
public static class IntegerHelper {
|
|
||||||
|
/// <summary>Returns the next highest power of 2 from the specified value</summary>
|
||||||
/// <summary>Returns the next highest power of 2 from the specified value</summary>
|
/// <param name="value">Value of which to return the next highest power of 2</param>
|
||||||
/// <param name="value">Value of which to return the next highest power of 2</param>
|
/// <returns>The next highest power of 2 to the value</returns>
|
||||||
/// <returns>The next highest power of 2 to the value</returns>
|
public static long NextPowerOf2(this long value) {
|
||||||
public static long NextPowerOf2(this long value) {
|
return (long)NextPowerOf2((ulong)value);
|
||||||
return (long)NextPowerOf2((ulong)value);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Returns the next highest power of 2 from the specified value</summary>
|
||||||
/// <summary>Returns the next highest power of 2 from the specified value</summary>
|
/// <param name="value">Value of which to return the next highest power of 2</param>
|
||||||
/// <param name="value">Value of which to return the next highest power of 2</param>
|
/// <returns>The next highest power of 2 to the value</returns>
|
||||||
/// <returns>The next highest power of 2 to the value</returns>
|
public static ulong NextPowerOf2(this ulong value) {
|
||||||
public static ulong NextPowerOf2(this ulong value) {
|
if (value == 0)
|
||||||
if (value == 0)
|
return 1;
|
||||||
return 1;
|
|
||||||
|
--value;
|
||||||
--value;
|
value |= value >> 1;
|
||||||
value |= value >> 1;
|
value |= value >> 2;
|
||||||
value |= value >> 2;
|
value |= value >> 4;
|
||||||
value |= value >> 4;
|
value |= value >> 8;
|
||||||
value |= value >> 8;
|
value |= value >> 16;
|
||||||
value |= value >> 16;
|
value |= value >> 32;
|
||||||
value |= value >> 32;
|
++value;
|
||||||
++value;
|
|
||||||
|
return value;
|
||||||
return value;
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Returns the next highest power of 2 from the specified value</summary>
|
||||||
/// <summary>Returns the next highest power of 2 from the specified value</summary>
|
/// <param name="value">Value of which to return the next highest power of 2</param>
|
||||||
/// <param name="value">Value of which to return the next highest power of 2</param>
|
/// <returns>The next highest power of 2 to the value</returns>
|
||||||
/// <returns>The next highest power of 2 to the value</returns>
|
public static int NextPowerOf2(this int value) {
|
||||||
public static int NextPowerOf2(this int value) {
|
return (int)NextPowerOf2((uint)value);
|
||||||
return (int)NextPowerOf2((uint)value);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Returns the next highest power of 2 from the specified value</summary>
|
||||||
/// <summary>Returns the next highest power of 2 from the specified value</summary>
|
/// <param name="value">Value of which to return the next highest power of 2</param>
|
||||||
/// <param name="value">Value of which to return the next highest power of 2</param>
|
/// <returns>The next highest power of 2 to the value</returns>
|
||||||
/// <returns>The next highest power of 2 to the value</returns>
|
public static uint NextPowerOf2(this uint value) {
|
||||||
public static uint NextPowerOf2(this uint value) {
|
if (value == 0)
|
||||||
if (value == 0)
|
return 1;
|
||||||
return 1;
|
|
||||||
|
--value;
|
||||||
--value;
|
value |= value >> 1;
|
||||||
value |= value >> 1;
|
value |= value >> 2;
|
||||||
value |= value >> 2;
|
value |= value >> 4;
|
||||||
value |= value >> 4;
|
value |= value >> 8;
|
||||||
value |= value >> 8;
|
value |= value >> 16;
|
||||||
value |= value >> 16;
|
++value;
|
||||||
++value;
|
|
||||||
|
return value;
|
||||||
return value;
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Returns the number of bits set in an integer</summary>
|
||||||
/// <summary>Returns the number of bits set in an integer</summary>
|
/// <param name="value">Value whose bits will be counted</param>
|
||||||
/// <param name="value">Value whose bits will be counted</param>
|
/// <returns>The number of bits set in the integer</returns>
|
||||||
/// <returns>The number of bits set in the integer</returns>
|
public static int CountBits(this int value) {
|
||||||
public static int CountBits(this int value) {
|
return CountBits((uint)value);
|
||||||
return CountBits((uint)value);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Returns the number of bits set in an unsigned integer</summary>
|
||||||
/// <summary>Returns the number of bits set in an unsigned integer</summary>
|
/// <param name="value">Value whose bits will be counted</param>
|
||||||
/// <param name="value">Value whose bits will be counted</param>
|
/// <returns>The number of bits set in the unsigned integer</returns>
|
||||||
/// <returns>The number of bits set in the unsigned integer</returns>
|
/// <remarks>
|
||||||
/// <remarks>
|
/// Based on a trick revealed here:
|
||||||
/// Based on a trick revealed here:
|
/// http://stackoverflow.com/questions/109023
|
||||||
/// http://stackoverflow.com/questions/109023
|
/// </remarks>
|
||||||
/// </remarks>
|
public static int CountBits(this uint value) {
|
||||||
public static int CountBits(this uint value) {
|
value = value - ((value >> 1) & 0x55555555);
|
||||||
value = value - ((value >> 1) & 0x55555555);
|
value = (value & 0x33333333) + ((value >> 2) & 0x33333333);
|
||||||
value = (value & 0x33333333) + ((value >> 2) & 0x33333333);
|
|
||||||
|
return (int)unchecked(
|
||||||
return (int)unchecked(
|
((value + (value >> 4) & 0xF0F0F0F) * 0x1010101) >> 24
|
||||||
((value + (value >> 4) & 0xF0F0F0F) * 0x1010101) >> 24
|
);
|
||||||
);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Returns the number of bits set in a long integer</summary>
|
||||||
/// <summary>Returns the number of bits set in a long integer</summary>
|
/// <param name="value">Value whose bits will be counted</param>
|
||||||
/// <param name="value">Value whose bits will be counted</param>
|
/// <returns>The number of bits set in the long integer</returns>
|
||||||
/// <returns>The number of bits set in the long integer</returns>
|
public static int CountBits(this long value) {
|
||||||
public static int CountBits(this long value) {
|
return CountBits((ulong)value);
|
||||||
return CountBits((ulong)value);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Returns the number of bits set in an unsigned long integer</summary>
|
||||||
/// <summary>Returns the number of bits set in an unsigned long integer</summary>
|
/// <param name="value">Value whose bits will be counted</param>
|
||||||
/// <param name="value">Value whose bits will be counted</param>
|
/// <returns>The number of bits set in the unsigned long integer</returns>
|
||||||
/// <returns>The number of bits set in the unsigned long integer</returns>
|
/// <remarks>
|
||||||
/// <remarks>
|
/// Based on a trick revealed here:
|
||||||
/// Based on a trick revealed here:
|
/// http://stackoverflow.com/questions/2709430
|
||||||
/// http://stackoverflow.com/questions/2709430
|
/// </remarks>
|
||||||
/// </remarks>
|
public static int CountBits(this ulong value) {
|
||||||
public static int CountBits(this ulong value) {
|
value = value - ((value >> 1) & 0x5555555555555555UL);
|
||||||
value = value - ((value >> 1) & 0x5555555555555555UL);
|
value = (value & 0x3333333333333333UL) + ((value >> 2) & 0x3333333333333333UL);
|
||||||
value = (value & 0x3333333333333333UL) + ((value >> 2) & 0x3333333333333333UL);
|
|
||||||
|
return (int)unchecked(
|
||||||
return (int)unchecked(
|
(((value + (value >> 4)) & 0xF0F0F0F0F0F0F0FUL) * 0x101010101010101UL) >> 56
|
||||||
(((value + (value >> 4)) & 0xF0F0F0F0F0F0F0FUL) * 0x101010101010101UL) >> 56
|
);
|
||||||
);
|
}
|
||||||
}
|
|
||||||
|
}
|
||||||
}
|
|
||||||
|
} // namespace Nuclex.Support
|
||||||
} // namespace Nuclex.Support
|
|
||||||
|
|
|
@ -1,142 +1,141 @@
|
||||||
#region CPL License
|
#region Apache License 2.0
|
||||||
/*
|
/*
|
||||||
Nuclex Framework
|
Nuclex .NET Framework
|
||||||
Copyright (C) 2002-2017 Nuclex Development Labs
|
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
|
||||||
|
|
||||||
This library is free software; you can redistribute it and/or
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
modify it under the terms of the IBM Common Public License as
|
you may not use this file except in compliance with the License.
|
||||||
published by the IBM Corporation; either version 1.0 of the
|
You may obtain a copy of the License at
|
||||||
License, or (at your option) any later version.
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
This library is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
Unless required by applicable law or agreed to in writing, software
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
IBM Common Public License for more details.
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
You should have received a copy of the IBM Common Public
|
limitations under the License.
|
||||||
License along with this library
|
*/
|
||||||
*/
|
#endregion // Apache License 2.0
|
||||||
#endregion
|
|
||||||
|
using System;
|
||||||
using System;
|
using System.Collections;
|
||||||
using System.Collections;
|
|
||||||
|
#if UNITTEST
|
||||||
#if UNITTEST
|
|
||||||
|
using NUnit.Framework;
|
||||||
using NUnit.Framework;
|
|
||||||
|
namespace Nuclex.Support.Licensing {
|
||||||
namespace Nuclex.Support.Licensing {
|
|
||||||
|
/// <summary>Unit test for the license key class</summary>
|
||||||
/// <summary>Unit test for the license key class</summary>
|
[TestFixture]
|
||||||
[TestFixture]
|
internal class LicenseKeyTest {
|
||||||
internal class LicenseKeyTest {
|
|
||||||
|
/// <summary>Tests the default constructor of the license key class</summary>
|
||||||
/// <summary>Tests the default constructor of the license key class</summary>
|
[Test]
|
||||||
[Test]
|
public void DefaultConstructorCanBeUsed() {
|
||||||
public void DefaultConstructorCanBeUsed() {
|
Assert.IsNotNull(new LicenseKey()); // Nonsense, prevents compiler warning
|
||||||
Assert.IsNotNull(new LicenseKey()); // Nonsense, prevents compiler warning
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Validates the correct translation of keys to GUIDs and back</summary>
|
||||||
/// <summary>Validates the correct translation of keys to GUIDs and back</summary>
|
[Test]
|
||||||
[Test]
|
public void LicenseKeysCanBeConvertedToGuidsAndBack() {
|
||||||
public void LicenseKeysCanBeConvertedToGuidsAndBack() {
|
for(int i = 0; i < 128; ++i) {
|
||||||
for(int i = 0; i < 128; ++i) {
|
|
||||||
|
// Create a new BitArray with the n.th bit set
|
||||||
// Create a new BitArray with the n.th bit set
|
BitArray guidBits = new BitArray(128);
|
||||||
BitArray guidBits = new BitArray(128);
|
guidBits[i] = true;
|
||||||
guidBits[i] = true;
|
|
||||||
|
// Create a GUID from this Bitarray
|
||||||
// Create a GUID from this Bitarray
|
byte[] guidBytes = new byte[16];
|
||||||
byte[] guidBytes = new byte[16];
|
guidBits.CopyTo(guidBytes, 0);
|
||||||
guidBits.CopyTo(guidBytes, 0);
|
Guid originalGuid = new Guid(guidBytes);
|
||||||
Guid originalGuid = new Guid(guidBytes);
|
|
||||||
|
// Convert the GUID into a license key and back to a GUID
|
||||||
// Convert the GUID into a license key and back to a GUID
|
string licenseKey = new LicenseKey(originalGuid).ToString();
|
||||||
string licenseKey = new LicenseKey(originalGuid).ToString();
|
Guid rebuiltGuid = LicenseKey.Parse(licenseKey).ToGuid();
|
||||||
Guid rebuiltGuid = LicenseKey.Parse(licenseKey).ToGuid();
|
|
||||||
|
// Verify that the original GUID matches the fore-and-back converted one
|
||||||
// Verify that the original GUID matches the fore-and-back converted one
|
Assert.AreEqual(originalGuid, rebuiltGuid, "Test for GUID bit " + i);
|
||||||
Assert.AreEqual(originalGuid, rebuiltGuid, "Test for GUID bit " + i);
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Tests whether license keys can be modified without destroying them</summary>
|
||||||
/// <summary>Tests whether license keys can be modified without destroying them</summary>
|
[Test]
|
||||||
[Test]
|
public void LicenseKeysCanBeModified() {
|
||||||
public void LicenseKeysCanBeModified() {
|
|
||||||
|
for(int i = 0; i < 4; ++i) {
|
||||||
for(int i = 0; i < 4; ++i) {
|
for(int j = 0; j < 8; ++j) {
|
||||||
for(int j = 0; j < 8; ++j) {
|
|
||||||
|
LicenseKey testKey = new LicenseKey(
|
||||||
LicenseKey testKey = new LicenseKey(
|
new Guid(-1, -1, -1, 255, 255, 255, 255, 255, 255, 255, 255)
|
||||||
new Guid(-1, -1, -1, 255, 255, 255, 255, 255, 255, 255, 255)
|
);
|
||||||
);
|
|
||||||
|
string originalString = testKey.ToString();
|
||||||
string originalString = testKey.ToString();
|
testKey[i] &= ~(1 << j);
|
||||||
testKey[i] &= ~(1 << j);
|
string modifiedString = testKey.ToString();
|
||||||
string modifiedString = testKey.ToString();
|
|
||||||
|
Assert.IsTrue(
|
||||||
Assert.IsTrue(
|
originalString != modifiedString, "Modified string differs from original"
|
||||||
originalString != modifiedString, "Modified string differs from original"
|
);
|
||||||
);
|
|
||||||
|
testKey[i] |= (1 << j);
|
||||||
testKey[i] |= (1 << j);
|
string revertedString = testKey.ToString();
|
||||||
string revertedString = testKey.ToString();
|
|
||||||
|
Assert.AreEqual(
|
||||||
Assert.AreEqual(
|
originalString, revertedString, "Original state restorable"
|
||||||
originalString, revertedString, "Original state restorable"
|
);
|
||||||
);
|
|
||||||
|
} // for j
|
||||||
} // for j
|
} // for i
|
||||||
} // for i
|
|
||||||
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Tests whether license keys can be modified without destroying them</summary>
|
||||||
/// <summary>Tests whether license keys can be modified without destroying them</summary>
|
[Test]
|
||||||
[Test]
|
public void ParsingInvalidLicenseKeyThrowsArgumentException() {
|
||||||
public void ParsingInvalidLicenseKeyThrowsArgumentException() {
|
Assert.Throws<ArgumentException>(
|
||||||
Assert.Throws<ArgumentException>(
|
delegate() { LicenseKey.Parse("hello world"); }
|
||||||
delegate() { LicenseKey.Parse("hello world"); }
|
);
|
||||||
);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Tests whether an exception is thrown if the indexer of a license key is used
|
||||||
/// Tests whether an exception is thrown if the indexer of a license key is used
|
/// with an invalid index to retrieve a component of the key
|
||||||
/// with an invalid index to retrieve a component of the key
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void ReadingInvalidIndexThrowsIndexOutOfRangeException() {
|
||||||
public void ReadingInvalidIndexThrowsIndexOutOfRangeException() {
|
LicenseKey key = new LicenseKey();
|
||||||
LicenseKey key = new LicenseKey();
|
Assert.Throws<IndexOutOfRangeException>(
|
||||||
Assert.Throws<IndexOutOfRangeException>(
|
delegate() { Console.WriteLine(key[-1]); }
|
||||||
delegate() { Console.WriteLine(key[-1]); }
|
);
|
||||||
);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Tests whether an exception is thrown if the indexer of a license key is used
|
||||||
/// Tests whether an exception is thrown if the indexer of a license key is used
|
/// with an invalid index to set a component of the key
|
||||||
/// with an invalid index to set a component of the key
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void WritingInvalidIndexThrowsIndexOutOfRangeException() {
|
||||||
public void WritingInvalidIndexThrowsIndexOutOfRangeException() {
|
LicenseKey key = new LicenseKey();
|
||||||
LicenseKey key = new LicenseKey();
|
Assert.Throws<IndexOutOfRangeException>(
|
||||||
Assert.Throws<IndexOutOfRangeException>(
|
delegate() { key[-1] = 0; }
|
||||||
delegate() { key[-1] = 0; }
|
);
|
||||||
);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Verifies that a license key can be converted into a byte array
|
||||||
/// Verifies that a license key can be converted into a byte array
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void LicenseKeyCanBeConvertedToByteArray() {
|
||||||
public void LicenseKeyCanBeConvertedToByteArray() {
|
Guid someGuid = Guid.NewGuid();
|
||||||
Guid someGuid = Guid.NewGuid();
|
LicenseKey someKey = new LicenseKey(someGuid);
|
||||||
LicenseKey someKey = new LicenseKey(someGuid);
|
|
||||||
|
CollectionAssert.AreEqual(someGuid.ToByteArray(), someKey.ToByteArray());
|
||||||
CollectionAssert.AreEqual(someGuid.ToByteArray(), someKey.ToByteArray());
|
}
|
||||||
}
|
|
||||||
|
}
|
||||||
}
|
|
||||||
|
} // namespace Nuclex.Support.Licensing
|
||||||
} // namespace Nuclex.Support.Licensing
|
|
||||||
|
#endif // UNITTEST
|
||||||
#endif // UNITTEST
|
|
||||||
|
|
|
@ -1,257 +1,256 @@
|
||||||
#region CPL License
|
#region Apache License 2.0
|
||||||
/*
|
/*
|
||||||
Nuclex Framework
|
Nuclex .NET Framework
|
||||||
Copyright (C) 2002-2017 Nuclex Development Labs
|
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
|
||||||
|
|
||||||
This library is free software; you can redistribute it and/or
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
modify it under the terms of the IBM Common Public License as
|
you may not use this file except in compliance with the License.
|
||||||
published by the IBM Corporation; either version 1.0 of the
|
You may obtain a copy of the License at
|
||||||
License, or (at your option) any later version.
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
This library is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
Unless required by applicable law or agreed to in writing, software
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
IBM Common Public License for more details.
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
You should have received a copy of the IBM Common Public
|
limitations under the License.
|
||||||
License along with this library
|
*/
|
||||||
*/
|
#endregion // Apache License 2.0
|
||||||
#endregion
|
|
||||||
|
using System;
|
||||||
using System;
|
using System.Collections;
|
||||||
using System.Collections;
|
using System.Text;
|
||||||
using System.Text;
|
|
||||||
|
namespace Nuclex.Support.Licensing {
|
||||||
namespace Nuclex.Support.Licensing {
|
|
||||||
|
/// <summary>Typical license key with 5x5 alphanumerical characters</summary>
|
||||||
/// <summary>Typical license key with 5x5 alphanumerical characters</summary>
|
/// <remarks>
|
||||||
/// <remarks>
|
/// <para>
|
||||||
/// <para>
|
/// This class manages a license key like it is used in Microsoft products.
|
||||||
/// This class manages a license key like it is used in Microsoft products.
|
/// Althought it is probably not the exact same technique used by Microsoft,
|
||||||
/// Althought it is probably not the exact same technique used by Microsoft,
|
/// the textual representation of the license keys looks identical,
|
||||||
/// the textual representation of the license keys looks identical,
|
/// eg. <code>O809J-RN5TD-IM3CU-4IG1O-O90X9</code>.
|
||||||
/// eg. <code>O809J-RN5TD-IM3CU-4IG1O-O90X9</code>.
|
/// </para>
|
||||||
/// </para>
|
/// <para>
|
||||||
/// <para>
|
/// Available storage space is used efficiently and allows for up to four
|
||||||
/// Available storage space is used efficiently and allows for up to four
|
/// 32 bit integers to be stored within the key, that's enough for a full GUID.
|
||||||
/// 32 bit integers to be stored within the key, that's enough for a full GUID.
|
/// The four integers can be modified directly, for example to store feature
|
||||||
/// The four integers can be modified directly, for example to store feature
|
/// lists, checksums or other data within the key.
|
||||||
/// lists, checksums or other data within the key.
|
/// </para>
|
||||||
/// </para>
|
/// </remarks>
|
||||||
/// </remarks>
|
public class LicenseKey {
|
||||||
public class LicenseKey {
|
|
||||||
|
/// <summary>Parses the license key contained in a string</summary>
|
||||||
/// <summary>Parses the license key contained in a string</summary>
|
/// <param name="key">String containing a license key that is to be parsed</param>
|
||||||
/// <param name="key">String containing a license key that is to be parsed</param>
|
/// <returns>The license key parsed from provided string</returns>
|
||||||
/// <returns>The license key parsed from provided string</returns>
|
/// <exception cref="ArgumentException">
|
||||||
/// <exception cref="ArgumentException">
|
/// When the provided string is not a license key
|
||||||
/// When the provided string is not a license key
|
/// </exception>
|
||||||
/// </exception>
|
public static LicenseKey Parse(string key) {
|
||||||
public static LicenseKey Parse(string key) {
|
key = key.Replace(" ", string.Empty).Replace("-", string.Empty).ToUpper();
|
||||||
key = key.Replace(" ", string.Empty).Replace("-", string.Empty).ToUpper();
|
if(key.Length != 25)
|
||||||
if(key.Length != 25)
|
throw new ArgumentException("This is not a license key");
|
||||||
throw new ArgumentException("This is not a license key");
|
|
||||||
|
BitArray bits = new BitArray(128);
|
||||||
BitArray bits = new BitArray(128);
|
uint sequence;
|
||||||
uint sequence;
|
|
||||||
|
// Convert the first 4 sequences of 6 chars into 124 bits
|
||||||
// Convert the first 4 sequences of 6 chars into 124 bits
|
for(int j = 0; j < 4; j++) {
|
||||||
for(int j = 0; j < 4; j++) {
|
|
||||||
|
sequence =
|
||||||
sequence =
|
(uint)codeTable.IndexOf(key[j * 6 + 5]) * 60466176 +
|
||||||
(uint)codeTable.IndexOf(key[j * 6 + 5]) * 60466176 +
|
(uint)codeTable.IndexOf(key[j * 6 + 4]) * 1679616 +
|
||||||
(uint)codeTable.IndexOf(key[j * 6 + 4]) * 1679616 +
|
(uint)codeTable.IndexOf(key[j * 6 + 3]) * 46656 +
|
||||||
(uint)codeTable.IndexOf(key[j * 6 + 3]) * 46656 +
|
(uint)codeTable.IndexOf(key[j * 6 + 2]) * 1296 +
|
||||||
(uint)codeTable.IndexOf(key[j * 6 + 2]) * 1296 +
|
(uint)codeTable.IndexOf(key[j * 6 + 1]) * 36 +
|
||||||
(uint)codeTable.IndexOf(key[j * 6 + 1]) * 36 +
|
(uint)codeTable.IndexOf(key[j * 6 + 0]);
|
||||||
(uint)codeTable.IndexOf(key[j * 6 + 0]);
|
|
||||||
|
for(int i = 0; i < 31; i++)
|
||||||
for(int i = 0; i < 31; i++)
|
bits[j * 31 + i] = (sequence & powersOfTwo[i, 1]) != 0;
|
||||||
bits[j * 31 + i] = (sequence & powersOfTwo[i, 1]) != 0;
|
|
||||||
|
}
|
||||||
}
|
|
||||||
|
// Append the remaining character's 4 bits
|
||||||
// Append the remaining character's 4 bits
|
sequence = (uint)codeTable.IndexOf(key[24]);
|
||||||
sequence = (uint)codeTable.IndexOf(key[24]);
|
bits[124] = (sequence & powersOfTwo[4, 1]) != 0;
|
||||||
bits[124] = (sequence & powersOfTwo[4, 1]) != 0;
|
bits[125] = (sequence & powersOfTwo[3, 1]) != 0;
|
||||||
bits[125] = (sequence & powersOfTwo[3, 1]) != 0;
|
bits[126] = (sequence & powersOfTwo[2, 1]) != 0;
|
||||||
bits[126] = (sequence & powersOfTwo[2, 1]) != 0;
|
bits[127] = (sequence & powersOfTwo[1, 1]) != 0;
|
||||||
bits[127] = (sequence & powersOfTwo[1, 1]) != 0;
|
|
||||||
|
// Revert the mangling that was applied to the key when encoding...
|
||||||
// Revert the mangling that was applied to the key when encoding...
|
unmangle(bits);
|
||||||
unmangle(bits);
|
|
||||||
|
// ...and we've got our GUID back!
|
||||||
// ...and we've got our GUID back!
|
byte[] guidBytes = new byte[16];
|
||||||
byte[] guidBytes = new byte[16];
|
bits.CopyTo(guidBytes, 0);
|
||||||
bits.CopyTo(guidBytes, 0);
|
|
||||||
|
return new LicenseKey(new Guid(guidBytes));
|
||||||
return new LicenseKey(new Guid(guidBytes));
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Initializes a new, empty license key</summary>
|
||||||
/// <summary>Initializes a new, empty license key</summary>
|
public LicenseKey() : this(Guid.Empty) { }
|
||||||
public LicenseKey() : this(Guid.Empty) { }
|
|
||||||
|
/// <summary>Initializes the license key from a GUID</summary>
|
||||||
/// <summary>Initializes the license key from a GUID</summary>
|
/// <param name="source">GUID that is used to create the license key</param>
|
||||||
/// <param name="source">GUID that is used to create the license key</param>
|
public LicenseKey(Guid source) {
|
||||||
public LicenseKey(Guid source) {
|
this.guid = source;
|
||||||
this.guid = source;
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Accesses the four integer values within a license key</summary>
|
||||||
/// <summary>Accesses the four integer values within a license key</summary>
|
/// <exception cref="IndexOutOfRangeException">
|
||||||
/// <exception cref="IndexOutOfRangeException">
|
/// When the index lies outside of the key's fields
|
||||||
/// When the index lies outside of the key's fields
|
/// </exception>
|
||||||
/// </exception>
|
public int this[int index] {
|
||||||
public int this[int index] {
|
get {
|
||||||
get {
|
if((index < 0) || (index > 3))
|
||||||
if((index < 0) || (index > 3))
|
throw new IndexOutOfRangeException("Index out of range");
|
||||||
throw new IndexOutOfRangeException("Index out of range");
|
|
||||||
|
return BitConverter.ToInt32(this.guid.ToByteArray(), index * 4);
|
||||||
return BitConverter.ToInt32(this.guid.ToByteArray(), index * 4);
|
}
|
||||||
}
|
set {
|
||||||
set {
|
if((index < 0) || (index > 3))
|
||||||
if((index < 0) || (index > 3))
|
throw new IndexOutOfRangeException("Index out of range");
|
||||||
throw new IndexOutOfRangeException("Index out of range");
|
|
||||||
|
// Convert the GUID into binary data so we can replace one of its values
|
||||||
// Convert the GUID into binary data so we can replace one of its values
|
byte[] guidBytes = this.guid.ToByteArray();
|
||||||
byte[] guidBytes = this.guid.ToByteArray();
|
|
||||||
|
// Overwrite the section at the index specified by the user with the new value
|
||||||
// Overwrite the section at the index specified by the user with the new value
|
Array.Copy(
|
||||||
Array.Copy(
|
BitConverter.GetBytes(value), 0, // source and start index
|
||||||
BitConverter.GetBytes(value), 0, // source and start index
|
guidBytes, index * 4, // destination and start index
|
||||||
guidBytes, index * 4, // destination and start index
|
4 // length
|
||||||
4 // length
|
);
|
||||||
);
|
|
||||||
|
// Replacement finished, now we can reconstruct our guid
|
||||||
// Replacement finished, now we can reconstruct our guid
|
this.guid = new Guid(guidBytes);
|
||||||
this.guid = new Guid(guidBytes);
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Converts the license key into a GUID</summary>
|
||||||
/// <summary>Converts the license key into a GUID</summary>
|
/// <returns>The GUID created from the license key</returns>
|
||||||
/// <returns>The GUID created from the license key</returns>
|
public Guid ToGuid() {
|
||||||
public Guid ToGuid() {
|
return this.guid;
|
||||||
return this.guid;
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Converts the license key into a byte array</summary>
|
||||||
/// <summary>Converts the license key into a byte array</summary>
|
/// <returns>A byte array containing the converted license key</returns>
|
||||||
/// <returns>A byte array containing the converted license key</returns>
|
public byte[] ToByteArray() {
|
||||||
public byte[] ToByteArray() {
|
return this.guid.ToByteArray();
|
||||||
return this.guid.ToByteArray();
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Converts the license key to a string</summary>
|
||||||
/// <summary>Converts the license key to a string</summary>
|
/// <returns>A string containing the converted license key</returns>
|
||||||
/// <returns>A string containing the converted license key</returns>
|
public override string ToString() {
|
||||||
public override string ToString() {
|
StringBuilder resultBuilder = new StringBuilder();
|
||||||
StringBuilder resultBuilder = new StringBuilder();
|
|
||||||
|
// Build a bit array from the input data
|
||||||
// Build a bit array from the input data
|
BitArray bits = new BitArray(this.guid.ToByteArray());
|
||||||
BitArray bits = new BitArray(this.guid.ToByteArray());
|
mangle(bits);
|
||||||
mangle(bits);
|
|
||||||
|
int sequence = 0;
|
||||||
int sequence = 0;
|
|
||||||
|
// Build 4 sequences of 6 characters from the first 124 bits
|
||||||
// Build 4 sequences of 6 characters from the first 124 bits
|
for(int i = 0; i < 4; ++i) {
|
||||||
for(int i = 0; i < 4; ++i) {
|
|
||||||
|
// We take the next 31 bits from the buffer
|
||||||
// We take the next 31 bits from the buffer
|
for(int j = 0; j < 31; ++j)
|
||||||
for(int j = 0; j < 31; ++j)
|
sequence |= (int)powersOfTwo[j, bits[i * 31 + j] ? 1 : 0];
|
||||||
sequence |= (int)powersOfTwo[j, bits[i * 31 + j] ? 1 : 0];
|
|
||||||
|
// Using 31 bits, a number up to 2.147.483.648 can be represented,
|
||||||
// Using 31 bits, a number up to 2.147.483.648 can be represented,
|
// while 6 alpha-numerical characters allow for 2.176.782.336 possible values,
|
||||||
// while 6 alpha-numerical characters allow for 2.176.782.336 possible values,
|
// which means we can fit 31 bits into every 6 alpha-numerical characters.
|
||||||
// which means we can fit 31 bits into every 6 alpha-numerical characters.
|
for(int j = 0; j < 6; ++j) {
|
||||||
for(int j = 0; j < 6; ++j) {
|
resultBuilder.Append(codeTable[sequence % 36]);
|
||||||
resultBuilder.Append(codeTable[sequence % 36]);
|
sequence /= 36;
|
||||||
sequence /= 36;
|
}
|
||||||
}
|
|
||||||
|
}
|
||||||
}
|
|
||||||
|
// Use the remaining 4 bits to build the final character
|
||||||
// Use the remaining 4 bits to build the final character
|
resultBuilder.Append(
|
||||||
resultBuilder.Append(
|
codeTable[
|
||||||
codeTable[
|
(int)(
|
||||||
(int)(
|
powersOfTwo[4, bits[124] ? 1 : 0] |
|
||||||
powersOfTwo[4, bits[124] ? 1 : 0] |
|
powersOfTwo[3, bits[125] ? 1 : 0] |
|
||||||
powersOfTwo[3, bits[125] ? 1 : 0] |
|
powersOfTwo[2, bits[126] ? 1 : 0] |
|
||||||
powersOfTwo[2, bits[126] ? 1 : 0] |
|
powersOfTwo[1, bits[127] ? 1 : 0] |
|
||||||
powersOfTwo[1, bits[127] ? 1 : 0] |
|
powersOfTwo[0, 1] // One bit remains unused :)
|
||||||
powersOfTwo[0, 1] // One bit remains unused :)
|
)
|
||||||
)
|
]
|
||||||
]
|
);
|
||||||
);
|
|
||||||
|
// Now build a nice, readable string from the decoded characters
|
||||||
// Now build a nice, readable string from the decoded characters
|
resultBuilder.Insert(5, keyDelimiter, 0, 1);
|
||||||
resultBuilder.Insert(5, keyDelimiter, 0, 1);
|
resultBuilder.Insert(11, keyDelimiter, 0, 1);
|
||||||
resultBuilder.Insert(11, keyDelimiter, 0, 1);
|
resultBuilder.Insert(17, keyDelimiter, 0, 1);
|
||||||
resultBuilder.Insert(17, keyDelimiter, 0, 1);
|
resultBuilder.Insert(23, keyDelimiter, 0, 1);
|
||||||
resultBuilder.Insert(23, keyDelimiter, 0, 1);
|
return resultBuilder.ToString();
|
||||||
return resultBuilder.ToString();
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Mangles a bit array</summary>
|
||||||
/// <summary>Mangles a bit array</summary>
|
/// <param name="bits">Bit array that will be mangled</param>
|
||||||
/// <param name="bits">Bit array that will be mangled</param>
|
private static void mangle(BitArray bits) {
|
||||||
private static void mangle(BitArray bits) {
|
BitArray temp = new BitArray(bits);
|
||||||
BitArray temp = new BitArray(bits);
|
|
||||||
|
for(int i = 0; i < temp.Length; ++i) {
|
||||||
for(int i = 0; i < temp.Length; ++i) {
|
bits[i] = temp[shuffle[i]];
|
||||||
bits[i] = temp[shuffle[i]];
|
|
||||||
|
if((i & 1) != 0)
|
||||||
if((i & 1) != 0)
|
bits[i] = !bits[i];
|
||||||
bits[i] = !bits[i];
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Unmangles a bit array</summary>
|
||||||
/// <summary>Unmangles a bit array</summary>
|
/// <param name="bits">Bit array that will be unmangled</param>
|
||||||
/// <param name="bits">Bit array that will be unmangled</param>
|
private static void unmangle(BitArray bits) {
|
||||||
private static void unmangle(BitArray bits) {
|
BitArray temp = new BitArray(bits);
|
||||||
BitArray temp = new BitArray(bits);
|
|
||||||
|
for(int i = 0; i < temp.Length; ++i) {
|
||||||
for(int i = 0; i < temp.Length; ++i) {
|
if((i & 1) != 0)
|
||||||
if((i & 1) != 0)
|
temp[i] = !temp[i];
|
||||||
temp[i] = !temp[i];
|
|
||||||
|
bits[shuffle[i]] = temp[i];
|
||||||
bits[shuffle[i]] = temp[i];
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Character used to delimit each 5 digit group in a license key</summary>
|
||||||
/// <summary>Character used to delimit each 5 digit group in a license key</summary>
|
/// <remarks>
|
||||||
/// <remarks>
|
/// Required to be a char array because the .NET Compact Framework only provides
|
||||||
/// Required to be a char array because the .NET Compact Framework only provides
|
/// an overload for char[] in the StringBuilder.Insert() method.
|
||||||
/// an overload for char[] in the StringBuilder.Insert() method.
|
/// </remarks>
|
||||||
/// </remarks>
|
private static char[] keyDelimiter = new char[] { '-' };
|
||||||
private static char[] keyDelimiter = new char[] { '-' };
|
|
||||||
|
/// <summary>Table with the individual characters in a key</summary>
|
||||||
/// <summary>Table with the individual characters in a key</summary>
|
private static readonly string codeTable =
|
||||||
private static readonly string codeTable =
|
"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
||||||
"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
|
||||||
|
/// <summary>Helper array containing the precalculated powers of two</summary>
|
||||||
/// <summary>Helper array containing the precalculated powers of two</summary>
|
private static readonly uint[,] powersOfTwo = new uint[32, 2] {
|
||||||
private static readonly uint[,] powersOfTwo = new uint[32, 2] {
|
{ 0, 1 }, { 0, 2 }, { 0, 4 }, { 0, 8 },
|
||||||
{ 0, 1 }, { 0, 2 }, { 0, 4 }, { 0, 8 },
|
{ 0, 16 }, { 0, 32 }, { 0, 64 }, { 0, 128 },
|
||||||
{ 0, 16 }, { 0, 32 }, { 0, 64 }, { 0, 128 },
|
{ 0, 256 }, { 0, 512 }, { 0, 1024 }, { 0, 2048 },
|
||||||
{ 0, 256 }, { 0, 512 }, { 0, 1024 }, { 0, 2048 },
|
{ 0, 4096 }, { 0, 8192 }, { 0, 16384 }, { 0, 32768 },
|
||||||
{ 0, 4096 }, { 0, 8192 }, { 0, 16384 }, { 0, 32768 },
|
{ 0, 65536 }, { 0, 131072 }, { 0, 262144 }, { 0, 524288 },
|
||||||
{ 0, 65536 }, { 0, 131072 }, { 0, 262144 }, { 0, 524288 },
|
{ 0, 1048576 }, { 0, 2097152 }, { 0, 4194304 }, { 0, 8388608 },
|
||||||
{ 0, 1048576 }, { 0, 2097152 }, { 0, 4194304 }, { 0, 8388608 },
|
{ 0, 16777216 }, { 0, 33554432 }, { 0, 67108864 }, { 0, 134217728 },
|
||||||
{ 0, 16777216 }, { 0, 33554432 }, { 0, 67108864 }, { 0, 134217728 },
|
{ 0, 268435456 }, { 0, 536870912 }, { 0, 1073741824 }, { 0, 2147483648 }
|
||||||
{ 0, 268435456 }, { 0, 536870912 }, { 0, 1073741824 }, { 0, 2147483648 }
|
};
|
||||||
};
|
|
||||||
|
/// <summary>Index list for rotating the bit arrays</summary>
|
||||||
/// <summary>Index list for rotating the bit arrays</summary>
|
private static readonly byte[] shuffle = new byte[128] {
|
||||||
private static readonly byte[] shuffle = new byte[128] {
|
99, 47, 19, 104, 40, 71, 35, 82, 88, 2, 117, 118, 105, 42, 84, 48,
|
||||||
99, 47, 19, 104, 40, 71, 35, 82, 88, 2, 117, 118, 105, 42, 84, 48,
|
33, 54, 43, 27, 78, 53, 61, 50, 109, 87, 69, 66, 25, 76, 45, 14,
|
||||||
33, 54, 43, 27, 78, 53, 61, 50, 109, 87, 69, 66, 25, 76, 45, 14,
|
92, 16, 123, 98, 95, 37, 34, 8, 1, 49, 20, 90, 15, 97, 22, 108,
|
||||||
92, 16, 123, 98, 95, 37, 34, 8, 1, 49, 20, 90, 15, 97, 22, 108,
|
5, 32, 120, 106, 122, 70, 67, 55, 46, 89, 100, 0, 26, 94, 121, 7,
|
||||||
5, 32, 120, 106, 122, 70, 67, 55, 46, 89, 100, 0, 26, 94, 121, 7,
|
56, 59, 103, 79, 107, 36, 125, 119, 126, 44, 18, 93, 75, 116, 31, 9,
|
||||||
56, 59, 103, 79, 107, 36, 125, 119, 126, 44, 18, 93, 75, 116, 31, 9,
|
73, 113, 3, 41, 124, 60, 77, 91, 28, 114, 65, 12, 39, 127, 72, 17,
|
||||||
73, 113, 3, 41, 124, 60, 77, 91, 28, 114, 65, 12, 39, 127, 72, 17,
|
112, 21, 96, 111, 83, 101, 85, 80, 23, 68, 57, 13, 4, 10, 51, 63,
|
||||||
112, 21, 96, 111, 83, 101, 85, 80, 23, 68, 57, 13, 4, 10, 51, 63,
|
11, 30, 115, 102, 86, 81, 74, 110, 62, 38, 29, 64, 52, 6, 24, 58
|
||||||
11, 30, 115, 102, 86, 81, 74, 110, 62, 38, 29, 64, 52, 6, 24, 58
|
};
|
||||||
};
|
|
||||||
|
/// <summary>GUID in which the key is stored</summary>
|
||||||
/// <summary>GUID in which the key is stored</summary>
|
private Guid guid;
|
||||||
private Guid guid;
|
|
||||||
|
}
|
||||||
}
|
|
||||||
|
} // namespace Nuclex.Support.Licensing
|
||||||
} // namespace Nuclex.Support.Licensing
|
|
||||||
|
|
|
@ -1,172 +1,171 @@
|
||||||
#region CPL License
|
#region Apache License 2.0
|
||||||
/*
|
/*
|
||||||
Nuclex Framework
|
Nuclex .NET Framework
|
||||||
Copyright (C) 2002-2017 Nuclex Development Labs
|
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
|
||||||
|
|
||||||
This library is free software; you can redistribute it and/or
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
modify it under the terms of the IBM Common Public License as
|
you may not use this file except in compliance with the License.
|
||||||
published by the IBM Corporation; either version 1.0 of the
|
You may obtain a copy of the License at
|
||||||
License, or (at your option) any later version.
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
This library is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
Unless required by applicable law or agreed to in writing, software
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
IBM Common Public License for more details.
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
You should have received a copy of the IBM Common Public
|
limitations under the License.
|
||||||
License along with this library
|
*/
|
||||||
*/
|
#endregion // Apache License 2.0
|
||||||
#endregion
|
|
||||||
|
#if UNITTEST
|
||||||
#if UNITTEST
|
|
||||||
|
using System;
|
||||||
using System;
|
using System.ComponentModel;
|
||||||
using System.ComponentModel;
|
|
||||||
|
using NUnit.Framework;
|
||||||
using NUnit.Framework;
|
using NMock;
|
||||||
using NMock;
|
|
||||||
|
namespace Nuclex.Support {
|
||||||
namespace Nuclex.Support {
|
|
||||||
|
/// <summary>Unit tests for observable class</summary>
|
||||||
/// <summary>Unit tests for observable class</summary>
|
[TestFixture]
|
||||||
[TestFixture]
|
internal class ObservableTest {
|
||||||
internal class ObservableTest {
|
|
||||||
|
#region class TestObservable
|
||||||
#region class TestObservable
|
|
||||||
|
/// <summary>Example class on which unit test generates change notifications</summary>
|
||||||
/// <summary>Example class on which unit test generates change notifications</summary>
|
public class TestObservable : Observable {
|
||||||
public class TestObservable : Observable {
|
|
||||||
|
/// <summary>Triggers the property changed event for the specified property</summary>
|
||||||
/// <summary>Triggers the property changed event for the specified property</summary>
|
/// <param name="propertyName">
|
||||||
/// <param name="propertyName">
|
/// Name of the property that will be reported as changed
|
||||||
/// Name of the property that will be reported as changed
|
/// </param>
|
||||||
/// </param>
|
public void FirePropertyChanged(string propertyName) {
|
||||||
public void FirePropertyChanged(string propertyName) {
|
OnPropertyChanged(propertyName);
|
||||||
OnPropertyChanged(propertyName);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Fires the property changed event for the 'SomePropety' property</summary>
|
||||||
/// <summary>Fires the property changed event for the 'SomePropety' property</summary>
|
public void FireSomePropertyChanged() {
|
||||||
public void FireSomePropertyChanged() {
|
#pragma warning disable 0618
|
||||||
#pragma warning disable 0618
|
OnPropertyChanged(() => SomeProperty);
|
||||||
OnPropertyChanged(() => SomeProperty);
|
#pragma warning restore 0618
|
||||||
#pragma warning restore 0618
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Example property that will be reported to have changed</summary>
|
||||||
/// <summary>Example property that will be reported to have changed</summary>
|
public int SomeProperty { get; set; }
|
||||||
public int SomeProperty { get; set; }
|
|
||||||
|
}
|
||||||
}
|
|
||||||
|
#endregion // class TestObservable
|
||||||
#endregion // class TestObservable
|
|
||||||
|
#region class MockedSubscriber
|
||||||
#region class MockedSubscriber
|
|
||||||
|
/// <summary>Mocked change notification subscriber</summary>
|
||||||
/// <summary>Mocked change notification subscriber</summary>
|
public class MockedSubscriber {
|
||||||
public class MockedSubscriber {
|
|
||||||
|
/// <summary>Called when the value of a property has changed</summary>
|
||||||
/// <summary>Called when the value of a property has changed</summary>
|
/// <param name="sender">Object of which a property has changed</param>
|
||||||
/// <param name="sender">Object of which a property has changed</param>
|
/// <param name="arguments">Contains the name of the changed property</param>
|
||||||
/// <param name="arguments">Contains the name of the changed property</param>
|
public void PropertyChanged(object sender, PropertyChangedEventArgs arguments) {
|
||||||
public void PropertyChanged(object sender, PropertyChangedEventArgs arguments) {
|
this.wasNotified = true;
|
||||||
this.wasNotified = true;
|
this.changedPropertyName = arguments.PropertyName;
|
||||||
this.changedPropertyName = arguments.PropertyName;
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Whether the subscriber was notified of a property change</summary>
|
||||||
/// <summary>Whether the subscriber was notified of a property change</summary>
|
public bool WasNotified {
|
||||||
public bool WasNotified {
|
get { return this.wasNotified; }
|
||||||
get { return this.wasNotified; }
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Checks whether a change notification for the specified property was received
|
||||||
/// Checks whether a change notification for the specified property was received
|
/// </summary>
|
||||||
/// </summary>
|
/// <param name="propertyName">Name of the property that will be checked for</param>
|
||||||
/// <param name="propertyName">Name of the property that will be checked for</param>
|
/// <returns>
|
||||||
/// <returns>
|
/// True if a change notification for the specified property was received
|
||||||
/// True if a change notification for the specified property was received
|
/// </returns>
|
||||||
/// </returns>
|
public bool WasNotifiedOfChangeTo(string propertyName) {
|
||||||
public bool WasNotifiedOfChangeTo(string propertyName) {
|
if(!this.wasNotified) {
|
||||||
if(!this.wasNotified) {
|
return false;
|
||||||
return false;
|
}
|
||||||
}
|
|
||||||
|
if(string.IsNullOrEmpty(propertyName)) {
|
||||||
if(string.IsNullOrEmpty(propertyName)) {
|
return string.IsNullOrEmpty(this.changedPropertyName);
|
||||||
return string.IsNullOrEmpty(this.changedPropertyName);
|
}
|
||||||
}
|
|
||||||
|
return (propertyName == this.changedPropertyName);
|
||||||
return (propertyName == this.changedPropertyName);
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Whether a change notification was received</summary>
|
||||||
/// <summary>Whether a change notification was received</summary>
|
private bool wasNotified;
|
||||||
private bool wasNotified;
|
/// <summary>Name of the property for which a change notification was received</summary>
|
||||||
/// <summary>Name of the property for which a change notification was received</summary>
|
private string changedPropertyName;
|
||||||
private string changedPropertyName;
|
|
||||||
|
}
|
||||||
}
|
|
||||||
|
#endregion // class MockedSubscriber
|
||||||
#endregion // class MockedSubscriber
|
|
||||||
|
/// <summary>Called before each unit test is run</summary>
|
||||||
/// <summary>Called before each unit test is run</summary>
|
[SetUp]
|
||||||
[SetUp]
|
public void Setup() {
|
||||||
public void Setup() {
|
this.testObservable = new TestObservable();
|
||||||
this.testObservable = new TestObservable();
|
this.subscriber = new MockedSubscriber();
|
||||||
this.subscriber = new MockedSubscriber();
|
|
||||||
|
this.testObservable.PropertyChanged += this.subscriber.PropertyChanged;
|
||||||
this.testObservable.PropertyChanged += this.subscriber.PropertyChanged;
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Verifies that the name of the changed property can be specified manually
|
||||||
/// Verifies that the name of the changed property can be specified manually
|
/// when triggering the PropertyChanged event
|
||||||
/// when triggering the PropertyChanged event
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void PropertyNameCanBeSpecifiedManually() {
|
||||||
public void PropertyNameCanBeSpecifiedManually() {
|
this.testObservable.FirePropertyChanged("SomeProperty");
|
||||||
this.testObservable.FirePropertyChanged("SomeProperty");
|
Assert.IsTrue(this.subscriber.WasNotifiedOfChangeTo("SomeProperty"));
|
||||||
Assert.IsTrue(this.subscriber.WasNotifiedOfChangeTo("SomeProperty"));
|
}
|
||||||
}
|
|
||||||
|
#if DEBUG // The check is conditionally performed only in debug mode
|
||||||
#if DEBUG // The check is conditionally performed only in debug mode
|
/// <summary>
|
||||||
/// <summary>
|
/// Verifies that specifying the name of a property that doesn't exist
|
||||||
/// Verifies that specifying the name of a property that doesn't exist
|
/// causes an ArgumentException to be thrown
|
||||||
/// causes an ArgumentException to be thrown
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void SpecifyingInvalidPropertyNameThrowsArgumentException() {
|
||||||
public void SpecifyingInvalidPropertyNameThrowsArgumentException() {
|
Assert.Throws<ArgumentException>(
|
||||||
Assert.Throws<ArgumentException>(
|
delegate() { this.testObservable.FirePropertyChanged("DoesntExist"); }
|
||||||
delegate() { this.testObservable.FirePropertyChanged("DoesntExist"); }
|
);
|
||||||
);
|
}
|
||||||
}
|
#endif
|
||||||
#endif
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Verifies that the observable is capable of deducing the name of the property
|
||||||
/// Verifies that the observable is capable of deducing the name of the property
|
/// from a lambda expression
|
||||||
/// from a lambda expression
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void PropertyNameCanBeDeducedFromLambdaExpression() {
|
||||||
public void PropertyNameCanBeDeducedFromLambdaExpression() {
|
this.testObservable.FireSomePropertyChanged();
|
||||||
this.testObservable.FireSomePropertyChanged();
|
Assert.IsTrue(this.subscriber.WasNotifiedOfChangeTo("SomeProperty"));
|
||||||
Assert.IsTrue(this.subscriber.WasNotifiedOfChangeTo("SomeProperty"));
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
/// <summary>
|
/// Verifies that change notifications for all properties of a type can
|
||||||
/// Verifies that change notifications for all properties of a type can
|
/// be generated
|
||||||
/// be generated
|
/// </summary>
|
||||||
/// </summary>
|
[Test]
|
||||||
[Test]
|
public void WildcardChangeNotificationsCanBeSent() {
|
||||||
public void WildcardChangeNotificationsCanBeSent() {
|
this.testObservable.FirePropertyChanged(string.Empty);
|
||||||
this.testObservable.FirePropertyChanged(string.Empty);
|
Assert.IsTrue(this.subscriber.WasNotifiedOfChangeTo(null));
|
||||||
Assert.IsTrue(this.subscriber.WasNotifiedOfChangeTo(null));
|
|
||||||
|
this.testObservable.FirePropertyChanged(null);
|
||||||
this.testObservable.FirePropertyChanged(null);
|
Assert.IsTrue(this.subscriber.WasNotifiedOfChangeTo(string.Empty));
|
||||||
Assert.IsTrue(this.subscriber.WasNotifiedOfChangeTo(string.Empty));
|
}
|
||||||
}
|
|
||||||
|
/// <summary>Observable object being tested</summary>
|
||||||
/// <summary>Observable object being tested</summary>
|
private TestObservable testObservable;
|
||||||
private TestObservable testObservable;
|
/// <summary>Subscriber to the observable object being tested</summary>
|
||||||
/// <summary>Subscriber to the observable object being tested</summary>
|
private MockedSubscriber subscriber;
|
||||||
private MockedSubscriber subscriber;
|
|
||||||
|
}
|
||||||
}
|
|
||||||
|
} // namespace Nuclex.Support
|
||||||
} // namespace Nuclex.Support
|
|
||||||
|
#endif // UNITTEST
|
||||||
#endif // UNITTEST
|
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user