Changed license to Apache License 2.0
This commit is contained in:
parent
d3bf0be9d7
commit
9f36d71529
144 changed files with 32422 additions and 32544 deletions
|
|
@ -1,53 +1,52 @@
|
|||
#region CPL License
|
||||
/*
|
||||
Nuclex Framework
|
||||
Copyright (C) 2002-2017 Nuclex Development Labs
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the IBM Common Public License as
|
||||
published by the IBM Corporation; either version 1.0 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
IBM Common Public License for more details.
|
||||
|
||||
You should have received a copy of the IBM Common Public
|
||||
License along with this library
|
||||
*/
|
||||
#endregion
|
||||
|
||||
#if UNITTEST
|
||||
|
||||
using System;
|
||||
using System.Collections.Specialized;
|
||||
|
||||
using NUnit.Framework;
|
||||
|
||||
namespace Nuclex.Support.Collections {
|
||||
|
||||
/// <summary>Unit Test for the collection constants</summary>
|
||||
[TestFixture]
|
||||
internal class ConstantsTest {
|
||||
|
||||
#if !NO_SPECIALIZED_COLLECTIONS
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that the collection reset event arguments have 'reset' specified as
|
||||
/// their action
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void CollectionResetEventArgsHaveResetActionSet() {
|
||||
Assert.AreEqual(
|
||||
NotifyCollectionChangedAction.Reset, Constants.NotifyCollectionResetEventArgs.Action
|
||||
);
|
||||
}
|
||||
|
||||
#endif // !NO_SPECIALIZED_COLLECTIONS
|
||||
|
||||
}
|
||||
|
||||
} // namespace Nuclex.Support.Collections
|
||||
|
||||
#endif // UNITTEST
|
||||
#region Apache License 2.0
|
||||
/*
|
||||
Nuclex .NET Framework
|
||||
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
#endregion // Apache License 2.0
|
||||
|
||||
#if UNITTEST
|
||||
|
||||
using System;
|
||||
using System.Collections.Specialized;
|
||||
|
||||
using NUnit.Framework;
|
||||
|
||||
namespace Nuclex.Support.Collections {
|
||||
|
||||
/// <summary>Unit Test for the collection constants</summary>
|
||||
[TestFixture]
|
||||
internal class ConstantsTest {
|
||||
|
||||
#if !NO_SPECIALIZED_COLLECTIONS
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that the collection reset event arguments have 'reset' specified as
|
||||
/// their action
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void CollectionResetEventArgsHaveResetActionSet() {
|
||||
Assert.AreEqual(
|
||||
NotifyCollectionChangedAction.Reset, Constants.NotifyCollectionResetEventArgs.Action
|
||||
);
|
||||
}
|
||||
|
||||
#endif // !NO_SPECIALIZED_COLLECTIONS
|
||||
|
||||
}
|
||||
|
||||
} // namespace Nuclex.Support.Collections
|
||||
|
||||
#endif // UNITTEST
|
||||
|
|
|
|||
|
|
@ -1,39 +1,38 @@
|
|||
#region CPL License
|
||||
/*
|
||||
Nuclex Framework
|
||||
Copyright (C) 2002-2017 Nuclex Development Labs
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the IBM Common Public License as
|
||||
published by the IBM Corporation; either version 1.0 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
IBM Common Public License for more details.
|
||||
|
||||
You should have received a copy of the IBM Common Public
|
||||
License along with this library
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
#if !NO_SPECIALIZED_COLLECTIONS
|
||||
using System.Collections.Specialized;
|
||||
#endif
|
||||
|
||||
namespace Nuclex.Support.Collections {
|
||||
|
||||
/// <summary>Contains fixed constants used by some collections</summary>
|
||||
public static class Constants {
|
||||
|
||||
#if !NO_SPECIALIZED_COLLECTIONS
|
||||
/// <summary>Fixed event args used to notify that the collection has reset</summary>
|
||||
public static readonly NotifyCollectionChangedEventArgs NotifyCollectionResetEventArgs =
|
||||
new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset);
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
} // namespace Nuclex.Support.Collections
|
||||
#region Apache License 2.0
|
||||
/*
|
||||
Nuclex .NET Framework
|
||||
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
#endregion // Apache License 2.0
|
||||
|
||||
using System;
|
||||
#if !NO_SPECIALIZED_COLLECTIONS
|
||||
using System.Collections.Specialized;
|
||||
#endif
|
||||
|
||||
namespace Nuclex.Support.Collections {
|
||||
|
||||
/// <summary>Contains fixed constants used by some collections</summary>
|
||||
public static class Constants {
|
||||
|
||||
#if !NO_SPECIALIZED_COLLECTIONS
|
||||
/// <summary>Fixed event args used to notify that the collection has reset</summary>
|
||||
public static readonly NotifyCollectionChangedEventArgs NotifyCollectionResetEventArgs =
|
||||
new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset);
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
} // namespace Nuclex.Support.Collections
|
||||
|
|
|
|||
|
|
@ -1,211 +1,210 @@
|
|||
#region CPL License
|
||||
/*
|
||||
Nuclex Framework
|
||||
Copyright (C) 2002-2017 Nuclex Development Labs
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the IBM Common Public License as
|
||||
published by the IBM Corporation; either version 1.0 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
IBM Common Public License for more details.
|
||||
|
||||
You should have received a copy of the IBM Common Public
|
||||
License along with this library
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
|
||||
namespace Nuclex.Support.Collections {
|
||||
|
||||
partial class Deque<TItem> {
|
||||
|
||||
/// <summary>Inserts an item at the beginning of the double-ended queue</summary>
|
||||
/// <param name="item">Item that will be inserted into the queue</param>
|
||||
public void AddFirst(TItem item) {
|
||||
if(this.firstBlockStartIndex > 0) {
|
||||
--this.firstBlockStartIndex;
|
||||
} else { // Need to allocate a new block
|
||||
this.blocks.Insert(0, new TItem[this.blockSize]);
|
||||
this.firstBlockStartIndex = this.blockSize - 1;
|
||||
}
|
||||
|
||||
this.blocks[0][this.firstBlockStartIndex] = item;
|
||||
++this.count;
|
||||
#if DEBUG
|
||||
++this.version;
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>Appends an item to the end of the double-ended queue</summary>
|
||||
/// <param name="item">Item that will be appended to the queue</param>
|
||||
public void AddLast(TItem item) {
|
||||
if(this.lastBlockEndIndex < this.blockSize) {
|
||||
++this.lastBlockEndIndex;
|
||||
} else { // Need to allocate a new block
|
||||
this.blocks.Add(new TItem[this.blockSize]);
|
||||
this.lastBlockEndIndex = 1;
|
||||
}
|
||||
|
||||
this.blocks[this.blocks.Count - 1][this.lastBlockEndIndex - 1] = item;
|
||||
++this.count;
|
||||
#if DEBUG
|
||||
++this.version;
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>Inserts the item at the specified index</summary>
|
||||
/// <param name="index">Index the item will be inserted at</param>
|
||||
/// <param name="item">Item that will be inserted</param>
|
||||
public void Insert(int index, TItem item) {
|
||||
int distanceToRightEnd = this.count - index;
|
||||
if(index < distanceToRightEnd) { // Are we closer to the left end?
|
||||
shiftLeftAndInsert(index, item);
|
||||
} else { // Nope, we're closer to the right end
|
||||
shiftRightAndInsert(index, item);
|
||||
}
|
||||
#if DEBUG
|
||||
++this.version;
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shifts all items before the insertion point to the left and inserts
|
||||
/// the item at the specified index
|
||||
/// </summary>
|
||||
/// <param name="index">Index the item will be inserted at</param>
|
||||
/// <param name="item">Item that will be inserted</param>
|
||||
private void shiftLeftAndInsert(int index, TItem item) {
|
||||
if(index == 0) {
|
||||
AddFirst(item);
|
||||
} else {
|
||||
int blockIndex, subIndex;
|
||||
findIndex(index, out blockIndex, out subIndex);
|
||||
|
||||
int firstBlock = 0;
|
||||
int blockStart;
|
||||
|
||||
// If the first block is full, we need to add another block
|
||||
if(this.firstBlockStartIndex == 0) {
|
||||
this.blocks.Insert(0, new TItem[this.blockSize]);
|
||||
this.blocks[0][this.blockSize - 1] = this.blocks[1][0];
|
||||
this.firstBlockStartIndex = this.blockSize - 1;
|
||||
|
||||
blockStart = 1;
|
||||
--subIndex;
|
||||
if(subIndex < 0) {
|
||||
subIndex = this.blockSize - 1;
|
||||
} else {
|
||||
++blockIndex;
|
||||
}
|
||||
++firstBlock;
|
||||
} else {
|
||||
blockStart = this.firstBlockStartIndex;
|
||||
--this.firstBlockStartIndex;
|
||||
|
||||
--subIndex;
|
||||
if(subIndex < 0) {
|
||||
subIndex = this.blockSize - 1;
|
||||
--blockIndex;
|
||||
}
|
||||
}
|
||||
|
||||
// If the insertion point is not in the first block
|
||||
if(blockIndex != firstBlock) {
|
||||
Array.Copy(
|
||||
this.blocks[firstBlock], blockStart,
|
||||
this.blocks[firstBlock], blockStart - 1,
|
||||
this.blockSize - blockStart
|
||||
);
|
||||
this.blocks[firstBlock][this.blockSize - 1] = this.blocks[firstBlock + 1][0];
|
||||
|
||||
// Move all the blocks following the insertion point to the right by one item.
|
||||
// If there are no blocks inbetween, this for loop will not run.
|
||||
for(int tempIndex = firstBlock + 1; tempIndex < blockIndex; ++tempIndex) {
|
||||
Array.Copy(
|
||||
this.blocks[tempIndex], 1, this.blocks[tempIndex], 0, this.blockSize - 1
|
||||
);
|
||||
this.blocks[tempIndex][this.blockSize - 1] = this.blocks[tempIndex + 1][0];
|
||||
}
|
||||
|
||||
blockStart = 1;
|
||||
}
|
||||
|
||||
// Finally, move the items in the block the insertion takes place in
|
||||
Array.Copy(
|
||||
this.blocks[blockIndex], blockStart,
|
||||
this.blocks[blockIndex], blockStart - 1,
|
||||
subIndex - blockStart + 1
|
||||
);
|
||||
|
||||
this.blocks[blockIndex][subIndex] = item;
|
||||
++this.count;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shifts all items after the insertion point to the right and inserts
|
||||
/// the item at the specified index
|
||||
/// </summary>
|
||||
/// <param name="index">Index the item will be inserted at</param>
|
||||
/// <param name="item">Item that will be inserted</param>
|
||||
private void shiftRightAndInsert(int index, TItem item) {
|
||||
if(index == this.count) {
|
||||
AddLast(item);
|
||||
} else {
|
||||
int blockIndex, subIndex;
|
||||
findIndex(index, out blockIndex, out subIndex);
|
||||
|
||||
int lastBlock = this.blocks.Count - 1;
|
||||
int blockLength;
|
||||
|
||||
// If the lastmost block is full, we need to add another block
|
||||
if(this.lastBlockEndIndex == this.blockSize) {
|
||||
this.blocks.Add(new TItem[this.blockSize]);
|
||||
this.blocks[lastBlock + 1][0] = this.blocks[lastBlock][this.blockSize - 1];
|
||||
this.lastBlockEndIndex = 1;
|
||||
|
||||
blockLength = this.blockSize - 1;
|
||||
} else {
|
||||
blockLength = this.lastBlockEndIndex;
|
||||
++this.lastBlockEndIndex;
|
||||
}
|
||||
|
||||
// If the insertion point is not in the lastmost block
|
||||
if(blockIndex != lastBlock) {
|
||||
Array.Copy(
|
||||
this.blocks[lastBlock], 0, this.blocks[lastBlock], 1, blockLength
|
||||
);
|
||||
this.blocks[lastBlock][0] = this.blocks[lastBlock - 1][this.blockSize - 1];
|
||||
|
||||
// Move all the blocks following the insertion point to the right by one item.
|
||||
// If there are no blocks inbetween, this for loop will not run.
|
||||
for(int tempIndex = lastBlock - 1; tempIndex > blockIndex; --tempIndex) {
|
||||
Array.Copy(
|
||||
this.blocks[tempIndex], 0, this.blocks[tempIndex], 1, this.blockSize - 1
|
||||
);
|
||||
this.blocks[tempIndex][0] = this.blocks[tempIndex - 1][this.blockSize - 1];
|
||||
}
|
||||
|
||||
blockLength = this.blockSize - 1;
|
||||
}
|
||||
|
||||
// Finally, move the items in the block the insertion takes place in
|
||||
Array.Copy(
|
||||
this.blocks[blockIndex], subIndex,
|
||||
this.blocks[blockIndex], subIndex + 1,
|
||||
blockLength - subIndex
|
||||
);
|
||||
|
||||
this.blocks[blockIndex][subIndex] = item;
|
||||
++this.count;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} // namespace Nuclex.Support.Collections
|
||||
#region Apache License 2.0
|
||||
/*
|
||||
Nuclex .NET Framework
|
||||
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
#endregion // Apache License 2.0
|
||||
|
||||
using System;
|
||||
|
||||
namespace Nuclex.Support.Collections {
|
||||
|
||||
partial class Deque<TItem> {
|
||||
|
||||
/// <summary>Inserts an item at the beginning of the double-ended queue</summary>
|
||||
/// <param name="item">Item that will be inserted into the queue</param>
|
||||
public void AddFirst(TItem item) {
|
||||
if(this.firstBlockStartIndex > 0) {
|
||||
--this.firstBlockStartIndex;
|
||||
} else { // Need to allocate a new block
|
||||
this.blocks.Insert(0, new TItem[this.blockSize]);
|
||||
this.firstBlockStartIndex = this.blockSize - 1;
|
||||
}
|
||||
|
||||
this.blocks[0][this.firstBlockStartIndex] = item;
|
||||
++this.count;
|
||||
#if DEBUG
|
||||
++this.version;
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>Appends an item to the end of the double-ended queue</summary>
|
||||
/// <param name="item">Item that will be appended to the queue</param>
|
||||
public void AddLast(TItem item) {
|
||||
if(this.lastBlockEndIndex < this.blockSize) {
|
||||
++this.lastBlockEndIndex;
|
||||
} else { // Need to allocate a new block
|
||||
this.blocks.Add(new TItem[this.blockSize]);
|
||||
this.lastBlockEndIndex = 1;
|
||||
}
|
||||
|
||||
this.blocks[this.blocks.Count - 1][this.lastBlockEndIndex - 1] = item;
|
||||
++this.count;
|
||||
#if DEBUG
|
||||
++this.version;
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>Inserts the item at the specified index</summary>
|
||||
/// <param name="index">Index the item will be inserted at</param>
|
||||
/// <param name="item">Item that will be inserted</param>
|
||||
public void Insert(int index, TItem item) {
|
||||
int distanceToRightEnd = this.count - index;
|
||||
if(index < distanceToRightEnd) { // Are we closer to the left end?
|
||||
shiftLeftAndInsert(index, item);
|
||||
} else { // Nope, we're closer to the right end
|
||||
shiftRightAndInsert(index, item);
|
||||
}
|
||||
#if DEBUG
|
||||
++this.version;
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shifts all items before the insertion point to the left and inserts
|
||||
/// the item at the specified index
|
||||
/// </summary>
|
||||
/// <param name="index">Index the item will be inserted at</param>
|
||||
/// <param name="item">Item that will be inserted</param>
|
||||
private void shiftLeftAndInsert(int index, TItem item) {
|
||||
if(index == 0) {
|
||||
AddFirst(item);
|
||||
} else {
|
||||
int blockIndex, subIndex;
|
||||
findIndex(index, out blockIndex, out subIndex);
|
||||
|
||||
int firstBlock = 0;
|
||||
int blockStart;
|
||||
|
||||
// If the first block is full, we need to add another block
|
||||
if(this.firstBlockStartIndex == 0) {
|
||||
this.blocks.Insert(0, new TItem[this.blockSize]);
|
||||
this.blocks[0][this.blockSize - 1] = this.blocks[1][0];
|
||||
this.firstBlockStartIndex = this.blockSize - 1;
|
||||
|
||||
blockStart = 1;
|
||||
--subIndex;
|
||||
if(subIndex < 0) {
|
||||
subIndex = this.blockSize - 1;
|
||||
} else {
|
||||
++blockIndex;
|
||||
}
|
||||
++firstBlock;
|
||||
} else {
|
||||
blockStart = this.firstBlockStartIndex;
|
||||
--this.firstBlockStartIndex;
|
||||
|
||||
--subIndex;
|
||||
if(subIndex < 0) {
|
||||
subIndex = this.blockSize - 1;
|
||||
--blockIndex;
|
||||
}
|
||||
}
|
||||
|
||||
// If the insertion point is not in the first block
|
||||
if(blockIndex != firstBlock) {
|
||||
Array.Copy(
|
||||
this.blocks[firstBlock], blockStart,
|
||||
this.blocks[firstBlock], blockStart - 1,
|
||||
this.blockSize - blockStart
|
||||
);
|
||||
this.blocks[firstBlock][this.blockSize - 1] = this.blocks[firstBlock + 1][0];
|
||||
|
||||
// Move all the blocks following the insertion point to the right by one item.
|
||||
// If there are no blocks inbetween, this for loop will not run.
|
||||
for(int tempIndex = firstBlock + 1; tempIndex < blockIndex; ++tempIndex) {
|
||||
Array.Copy(
|
||||
this.blocks[tempIndex], 1, this.blocks[tempIndex], 0, this.blockSize - 1
|
||||
);
|
||||
this.blocks[tempIndex][this.blockSize - 1] = this.blocks[tempIndex + 1][0];
|
||||
}
|
||||
|
||||
blockStart = 1;
|
||||
}
|
||||
|
||||
// Finally, move the items in the block the insertion takes place in
|
||||
Array.Copy(
|
||||
this.blocks[blockIndex], blockStart,
|
||||
this.blocks[blockIndex], blockStart - 1,
|
||||
subIndex - blockStart + 1
|
||||
);
|
||||
|
||||
this.blocks[blockIndex][subIndex] = item;
|
||||
++this.count;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shifts all items after the insertion point to the right and inserts
|
||||
/// the item at the specified index
|
||||
/// </summary>
|
||||
/// <param name="index">Index the item will be inserted at</param>
|
||||
/// <param name="item">Item that will be inserted</param>
|
||||
private void shiftRightAndInsert(int index, TItem item) {
|
||||
if(index == this.count) {
|
||||
AddLast(item);
|
||||
} else {
|
||||
int blockIndex, subIndex;
|
||||
findIndex(index, out blockIndex, out subIndex);
|
||||
|
||||
int lastBlock = this.blocks.Count - 1;
|
||||
int blockLength;
|
||||
|
||||
// If the lastmost block is full, we need to add another block
|
||||
if(this.lastBlockEndIndex == this.blockSize) {
|
||||
this.blocks.Add(new TItem[this.blockSize]);
|
||||
this.blocks[lastBlock + 1][0] = this.blocks[lastBlock][this.blockSize - 1];
|
||||
this.lastBlockEndIndex = 1;
|
||||
|
||||
blockLength = this.blockSize - 1;
|
||||
} else {
|
||||
blockLength = this.lastBlockEndIndex;
|
||||
++this.lastBlockEndIndex;
|
||||
}
|
||||
|
||||
// If the insertion point is not in the lastmost block
|
||||
if(blockIndex != lastBlock) {
|
||||
Array.Copy(
|
||||
this.blocks[lastBlock], 0, this.blocks[lastBlock], 1, blockLength
|
||||
);
|
||||
this.blocks[lastBlock][0] = this.blocks[lastBlock - 1][this.blockSize - 1];
|
||||
|
||||
// Move all the blocks following the insertion point to the right by one item.
|
||||
// If there are no blocks inbetween, this for loop will not run.
|
||||
for(int tempIndex = lastBlock - 1; tempIndex > blockIndex; --tempIndex) {
|
||||
Array.Copy(
|
||||
this.blocks[tempIndex], 0, this.blocks[tempIndex], 1, this.blockSize - 1
|
||||
);
|
||||
this.blocks[tempIndex][0] = this.blocks[tempIndex - 1][this.blockSize - 1];
|
||||
}
|
||||
|
||||
blockLength = this.blockSize - 1;
|
||||
}
|
||||
|
||||
// Finally, move the items in the block the insertion takes place in
|
||||
Array.Copy(
|
||||
this.blocks[blockIndex], subIndex,
|
||||
this.blocks[blockIndex], subIndex + 1,
|
||||
blockLength - subIndex
|
||||
);
|
||||
|
||||
this.blocks[blockIndex][subIndex] = item;
|
||||
++this.count;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} // namespace Nuclex.Support.Collections
|
||||
|
|
|
|||
|
|
@ -1,150 +1,149 @@
|
|||
#region CPL License
|
||||
/*
|
||||
Nuclex Framework
|
||||
Copyright (C) 2002-2017 Nuclex Development Labs
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the IBM Common Public License as
|
||||
published by the IBM Corporation; either version 1.0 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
IBM Common Public License for more details.
|
||||
|
||||
You should have received a copy of the IBM Common Public
|
||||
License along with this library
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections;
|
||||
|
||||
namespace Nuclex.Support.Collections {
|
||||
|
||||
partial class Deque<TItem> {
|
||||
|
||||
#region IEnumerable members
|
||||
|
||||
/// <summary>Obtains a new enumerator for the contents of the deque</summary>
|
||||
/// <returns>The new enumerator</returns>
|
||||
IEnumerator IEnumerable.GetEnumerator() {
|
||||
return new Enumerator(this);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IList Members
|
||||
|
||||
/// <summary>Adds an item to the deque</summary>
|
||||
/// <param name="value">Item that will be added to the deque</param>
|
||||
/// <returns>The index at which the new item was added</returns>
|
||||
int IList.Add(object value) {
|
||||
verifyCompatibleObject(value);
|
||||
|
||||
AddLast((TItem)value);
|
||||
return this.count - 1;
|
||||
}
|
||||
|
||||
/// <summary>Checks whether the deque contains the specified item</summary>
|
||||
/// <param name="value">Item the deque will be scanned for</param>
|
||||
/// <returns>True if the deque contained the specified item</returns>
|
||||
bool IList.Contains(object value) {
|
||||
return isCompatibleObject(value) && Contains((TItem)value);
|
||||
}
|
||||
|
||||
/// <summary>Determines the index of the item in the deque</summary>
|
||||
/// <param name="value">Item whose index will be determined</param>
|
||||
/// <returns>The index of the specified item in the deque</returns>
|
||||
int IList.IndexOf(object value) {
|
||||
if(isCompatibleObject(value)) {
|
||||
return IndexOf((TItem)value);
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Inserts an item into the deque at the specified location</summary>
|
||||
/// <param name="index">Index at which the item will be inserted</param>
|
||||
/// <param name="value">Item that will be inserted</param>
|
||||
void IList.Insert(int index, object value) {
|
||||
verifyCompatibleObject(value);
|
||||
Insert(index, (TItem)value);
|
||||
}
|
||||
|
||||
/// <summary>Whether the deque has a fixed size</summary>
|
||||
bool IList.IsFixedSize {
|
||||
get { return false; }
|
||||
}
|
||||
|
||||
/// <summary>Whether the deque is read-only</summary>
|
||||
bool IList.IsReadOnly {
|
||||
get { return false; }
|
||||
}
|
||||
|
||||
/// <summary>Removes the specified item from the deque</summary>
|
||||
/// <param name="value">Item that will be removed from the deque</param>
|
||||
void IList.Remove(object value) {
|
||||
if(isCompatibleObject(value)) {
|
||||
Remove((TItem)value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Accesses an item in the deque by its index</summary>
|
||||
/// <param name="index">Index of the item that will be accessed</param>
|
||||
/// <returns>The item at the specified index</returns>
|
||||
object IList.this[int index] {
|
||||
get { return this[index]; }
|
||||
set {
|
||||
verifyCompatibleObject(value);
|
||||
this[index] = (TItem)value;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region ICollection<ItemType> Members
|
||||
|
||||
/// <summary>Adds an item into the deque</summary>
|
||||
/// <param name="item">Item that will be added to the deque</param>
|
||||
void ICollection<TItem>.Add(TItem item) {
|
||||
AddLast(item);
|
||||
}
|
||||
|
||||
/// <summary>Whether the collection is read-only</summary>
|
||||
bool ICollection<TItem>.IsReadOnly {
|
||||
get { return false; }
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region ICollection Members
|
||||
|
||||
/// <summary>Copies the contents of the deque into an array</summary>
|
||||
/// <param name="array">Array the contents of the deque will be copied into</param>
|
||||
/// <param name="index">Index at which writing into the array will begin</param>
|
||||
void ICollection.CopyTo(Array array, int index) {
|
||||
if(!(array is TItem[])) {
|
||||
throw new ArgumentException("Incompatible array type", "array");
|
||||
}
|
||||
|
||||
CopyTo((TItem[])array, index);
|
||||
}
|
||||
|
||||
/// <summary>Whether the deque is thread-synchronized</summary>
|
||||
bool ICollection.IsSynchronized {
|
||||
get { return false; }
|
||||
}
|
||||
|
||||
/// <summary>Synchronization root of the instance</summary>
|
||||
object ICollection.SyncRoot {
|
||||
get { return this; }
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
||||
|
||||
} // namespace Nuclex.Support.Collections
|
||||
#region Apache License 2.0
|
||||
/*
|
||||
Nuclex .NET Framework
|
||||
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
#endregion // Apache License 2.0
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections;
|
||||
|
||||
namespace Nuclex.Support.Collections {
|
||||
|
||||
partial class Deque<TItem> {
|
||||
|
||||
#region IEnumerable members
|
||||
|
||||
/// <summary>Obtains a new enumerator for the contents of the deque</summary>
|
||||
/// <returns>The new enumerator</returns>
|
||||
IEnumerator IEnumerable.GetEnumerator() {
|
||||
return new Enumerator(this);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IList Members
|
||||
|
||||
/// <summary>Adds an item to the deque</summary>
|
||||
/// <param name="value">Item that will be added to the deque</param>
|
||||
/// <returns>The index at which the new item was added</returns>
|
||||
int IList.Add(object value) {
|
||||
verifyCompatibleObject(value);
|
||||
|
||||
AddLast((TItem)value);
|
||||
return this.count - 1;
|
||||
}
|
||||
|
||||
/// <summary>Checks whether the deque contains the specified item</summary>
|
||||
/// <param name="value">Item the deque will be scanned for</param>
|
||||
/// <returns>True if the deque contained the specified item</returns>
|
||||
bool IList.Contains(object value) {
|
||||
return isCompatibleObject(value) && Contains((TItem)value);
|
||||
}
|
||||
|
||||
/// <summary>Determines the index of the item in the deque</summary>
|
||||
/// <param name="value">Item whose index will be determined</param>
|
||||
/// <returns>The index of the specified item in the deque</returns>
|
||||
int IList.IndexOf(object value) {
|
||||
if(isCompatibleObject(value)) {
|
||||
return IndexOf((TItem)value);
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Inserts an item into the deque at the specified location</summary>
|
||||
/// <param name="index">Index at which the item will be inserted</param>
|
||||
/// <param name="value">Item that will be inserted</param>
|
||||
void IList.Insert(int index, object value) {
|
||||
verifyCompatibleObject(value);
|
||||
Insert(index, (TItem)value);
|
||||
}
|
||||
|
||||
/// <summary>Whether the deque has a fixed size</summary>
|
||||
bool IList.IsFixedSize {
|
||||
get { return false; }
|
||||
}
|
||||
|
||||
/// <summary>Whether the deque is read-only</summary>
|
||||
bool IList.IsReadOnly {
|
||||
get { return false; }
|
||||
}
|
||||
|
||||
/// <summary>Removes the specified item from the deque</summary>
|
||||
/// <param name="value">Item that will be removed from the deque</param>
|
||||
void IList.Remove(object value) {
|
||||
if(isCompatibleObject(value)) {
|
||||
Remove((TItem)value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Accesses an item in the deque by its index</summary>
|
||||
/// <param name="index">Index of the item that will be accessed</param>
|
||||
/// <returns>The item at the specified index</returns>
|
||||
object IList.this[int index] {
|
||||
get { return this[index]; }
|
||||
set {
|
||||
verifyCompatibleObject(value);
|
||||
this[index] = (TItem)value;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region ICollection<ItemType> Members
|
||||
|
||||
/// <summary>Adds an item into the deque</summary>
|
||||
/// <param name="item">Item that will be added to the deque</param>
|
||||
void ICollection<TItem>.Add(TItem item) {
|
||||
AddLast(item);
|
||||
}
|
||||
|
||||
/// <summary>Whether the collection is read-only</summary>
|
||||
bool ICollection<TItem>.IsReadOnly {
|
||||
get { return false; }
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region ICollection Members
|
||||
|
||||
/// <summary>Copies the contents of the deque into an array</summary>
|
||||
/// <param name="array">Array the contents of the deque will be copied into</param>
|
||||
/// <param name="index">Index at which writing into the array will begin</param>
|
||||
void ICollection.CopyTo(Array array, int index) {
|
||||
if(!(array is TItem[])) {
|
||||
throw new ArgumentException("Incompatible array type", "array");
|
||||
}
|
||||
|
||||
CopyTo((TItem[])array, index);
|
||||
}
|
||||
|
||||
/// <summary>Whether the deque is thread-synchronized</summary>
|
||||
bool ICollection.IsSynchronized {
|
||||
get { return false; }
|
||||
}
|
||||
|
||||
/// <summary>Synchronization root of the instance</summary>
|
||||
object ICollection.SyncRoot {
|
||||
get { return this; }
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
||||
|
||||
} // namespace Nuclex.Support.Collections
|
||||
|
|
|
|||
|
|
@ -1,259 +1,258 @@
|
|||
#region CPL License
|
||||
/*
|
||||
Nuclex Framework
|
||||
Copyright (C) 2002-2017 Nuclex Development Labs
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the IBM Common Public License as
|
||||
published by the IBM Corporation; either version 1.0 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
IBM Common Public License for more details.
|
||||
|
||||
You should have received a copy of the IBM Common Public
|
||||
License along with this library
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
|
||||
namespace Nuclex.Support.Collections {
|
||||
|
||||
partial class Deque<TItem> {
|
||||
|
||||
/// <summary>Removes all items from the deque</summary>
|
||||
public void Clear() {
|
||||
if(this.blocks.Count > 1) { // Are there multiple blocks?
|
||||
|
||||
// Clear the items in the first block to avoid holding on to references
|
||||
// in memory unreachable to the user
|
||||
for(int index = this.firstBlockStartIndex; index < this.blockSize; ++index) {
|
||||
this.blocks[0][index] = default(TItem);
|
||||
}
|
||||
|
||||
// Remove any other blocks
|
||||
this.blocks.RemoveRange(1, this.blocks.Count - 1);
|
||||
|
||||
} else { // Nope, only a single block exists
|
||||
|
||||
// Clear the items in the block to release any reference we may be keeping alive
|
||||
for(
|
||||
int index = this.firstBlockStartIndex; index < this.lastBlockEndIndex; ++index
|
||||
) {
|
||||
this.blocks[0][index] = default(TItem);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Reset the counters to restart the deque from scratch
|
||||
this.firstBlockStartIndex = 0;
|
||||
this.lastBlockEndIndex = 0;
|
||||
this.count = 0;
|
||||
#if DEBUG
|
||||
++this.version;
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>Removes the specified item from the deque</summary>
|
||||
/// <param name="item">Item that will be removed from the deque</param>
|
||||
/// <returns>True if the item was found and removed</returns>
|
||||
public bool Remove(TItem item) {
|
||||
int index = IndexOf(item);
|
||||
if(index == -1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
RemoveAt(index);
|
||||
#if DEBUG
|
||||
++this.version;
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>Removes the first item in the double-ended queue</summary>
|
||||
public void RemoveFirst() {
|
||||
if(this.count == 0) {
|
||||
throw new InvalidOperationException("Cannot remove items from empty deque");
|
||||
}
|
||||
|
||||
// This is necessary to make sure the deque doesn't hold dead objects alive
|
||||
// in unreachable spaces of its memory.
|
||||
this.blocks[0][this.firstBlockStartIndex] = default(TItem);
|
||||
|
||||
// Cut off the item from the first block. If the block became empty and it's
|
||||
// not the last remaining block, remove it as well.
|
||||
++this.firstBlockStartIndex;
|
||||
if(this.firstBlockStartIndex >= this.blockSize) { // Block became empty
|
||||
if(this.count > 1) { // Still more blocks in queue, remove block
|
||||
this.blocks.RemoveAt(0);
|
||||
this.firstBlockStartIndex = 0;
|
||||
} else { // Last block - do not remove
|
||||
this.firstBlockStartIndex = 0;
|
||||
this.lastBlockEndIndex = 0;
|
||||
}
|
||||
}
|
||||
--this.count;
|
||||
#if DEBUG
|
||||
++this.version;
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>Removes the last item in the double-ended queue</summary>
|
||||
public void RemoveLast() {
|
||||
if(this.count == 0) {
|
||||
throw new InvalidOperationException("Cannot remove items from empty deque");
|
||||
}
|
||||
|
||||
// This is necessary to make sure the deque doesn't hold dead objects alive
|
||||
// in unreachable spaces of its memory.
|
||||
int lastBlock = this.blocks.Count - 1;
|
||||
this.blocks[lastBlock][this.lastBlockEndIndex - 1] = default(TItem);
|
||||
|
||||
// Cut off the last item in the last block. If the block became empty and it's
|
||||
// not the last remaining block, remove it as well.
|
||||
--this.lastBlockEndIndex;
|
||||
if(this.lastBlockEndIndex == 0) { // Block became empty
|
||||
if(this.count > 1) {
|
||||
this.blocks.RemoveAt(lastBlock);
|
||||
this.lastBlockEndIndex = this.blockSize;
|
||||
} else { // Last block - do not remove
|
||||
this.firstBlockStartIndex = 0;
|
||||
this.lastBlockEndIndex = 0;
|
||||
}
|
||||
}
|
||||
--this.count;
|
||||
#if DEBUG
|
||||
++this.version;
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>Removes the item at the specified index</summary>
|
||||
/// <param name="index">Index of the item that will be removed</param>
|
||||
public void RemoveAt(int index) {
|
||||
int distanceToRightEnd = this.count - index;
|
||||
if(index < distanceToRightEnd) { // Are we closer to the left end?
|
||||
removeFromLeft(index);
|
||||
} else { // Nope, we're closer to the right end
|
||||
removeFromRight(index);
|
||||
}
|
||||
#if DEBUG
|
||||
++this.version;
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes an item from the left side of the queue by shifting all items that
|
||||
/// come before it to the right by one
|
||||
/// </summary>
|
||||
/// <param name="index">Index of the item that will be removed</param>
|
||||
private void removeFromLeft(int index) {
|
||||
if(index == 0) {
|
||||
RemoveFirst();
|
||||
} else {
|
||||
int blockIndex, subIndex;
|
||||
findIndex(index, out blockIndex, out subIndex);
|
||||
|
||||
int firstBlock = 0;
|
||||
int endIndex;
|
||||
|
||||
if(blockIndex > firstBlock) {
|
||||
Array.Copy(
|
||||
this.blocks[blockIndex], 0,
|
||||
this.blocks[blockIndex], 1,
|
||||
subIndex
|
||||
);
|
||||
this.blocks[blockIndex][0] = this.blocks[blockIndex - 1][this.blockSize - 1];
|
||||
|
||||
for(int tempIndex = blockIndex - 1; tempIndex > firstBlock; --tempIndex) {
|
||||
Array.Copy(
|
||||
this.blocks[tempIndex], 0,
|
||||
this.blocks[tempIndex], 1,
|
||||
this.blockSize - 1
|
||||
);
|
||||
this.blocks[tempIndex][0] = this.blocks[tempIndex - 1][this.blockSize - 1];
|
||||
}
|
||||
|
||||
endIndex = this.blockSize - 1;
|
||||
} else {
|
||||
endIndex = subIndex;
|
||||
}
|
||||
|
||||
Array.Copy(
|
||||
this.blocks[firstBlock], this.firstBlockStartIndex,
|
||||
this.blocks[firstBlock], this.firstBlockStartIndex + 1,
|
||||
endIndex - this.firstBlockStartIndex
|
||||
);
|
||||
|
||||
if(this.firstBlockStartIndex == this.blockSize - 1) {
|
||||
this.blocks.RemoveAt(0);
|
||||
this.firstBlockStartIndex = 0;
|
||||
} else {
|
||||
this.blocks[0][this.firstBlockStartIndex] = default(TItem);
|
||||
++this.firstBlockStartIndex;
|
||||
}
|
||||
|
||||
--this.count;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes an item from the right side of the queue by shifting all items that
|
||||
/// come after it to the left by one
|
||||
/// </summary>
|
||||
/// <param name="index">Index of the item that will be removed</param>
|
||||
private void removeFromRight(int index) {
|
||||
if(index == this.count - 1) {
|
||||
RemoveLast();
|
||||
} else {
|
||||
int blockIndex, subIndex;
|
||||
findIndex(index, out blockIndex, out subIndex);
|
||||
|
||||
int lastBlock = this.blocks.Count - 1;
|
||||
int startIndex;
|
||||
|
||||
if(blockIndex < lastBlock) {
|
||||
Array.Copy(
|
||||
this.blocks[blockIndex], subIndex + 1,
|
||||
this.blocks[blockIndex], subIndex,
|
||||
this.blockSize - subIndex - 1
|
||||
);
|
||||
this.blocks[blockIndex][this.blockSize - 1] = this.blocks[blockIndex + 1][0];
|
||||
|
||||
for(int tempIndex = blockIndex + 1; tempIndex < lastBlock; ++tempIndex) {
|
||||
Array.Copy(
|
||||
this.blocks[tempIndex], 1,
|
||||
this.blocks[tempIndex], 0,
|
||||
this.blockSize - 1
|
||||
);
|
||||
this.blocks[tempIndex][this.blockSize - 1] = this.blocks[tempIndex + 1][0];
|
||||
}
|
||||
|
||||
startIndex = 0;
|
||||
} else {
|
||||
startIndex = subIndex;
|
||||
}
|
||||
|
||||
Array.Copy(
|
||||
this.blocks[lastBlock], startIndex + 1,
|
||||
this.blocks[lastBlock], startIndex,
|
||||
this.lastBlockEndIndex - startIndex - 1
|
||||
);
|
||||
|
||||
if(this.lastBlockEndIndex == 1) {
|
||||
this.blocks.RemoveAt(lastBlock);
|
||||
this.lastBlockEndIndex = this.blockSize;
|
||||
} else {
|
||||
this.blocks[lastBlock][this.lastBlockEndIndex - 1] = default(TItem);
|
||||
--this.lastBlockEndIndex;
|
||||
}
|
||||
|
||||
--this.count;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} // namespace Nuclex.Support.Collections
|
||||
#region Apache License 2.0
|
||||
/*
|
||||
Nuclex .NET Framework
|
||||
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
#endregion // Apache License 2.0
|
||||
|
||||
using System;
|
||||
|
||||
namespace Nuclex.Support.Collections {
|
||||
|
||||
partial class Deque<TItem> {
|
||||
|
||||
/// <summary>Removes all items from the deque</summary>
|
||||
public void Clear() {
|
||||
if(this.blocks.Count > 1) { // Are there multiple blocks?
|
||||
|
||||
// Clear the items in the first block to avoid holding on to references
|
||||
// in memory unreachable to the user
|
||||
for(int index = this.firstBlockStartIndex; index < this.blockSize; ++index) {
|
||||
this.blocks[0][index] = default(TItem);
|
||||
}
|
||||
|
||||
// Remove any other blocks
|
||||
this.blocks.RemoveRange(1, this.blocks.Count - 1);
|
||||
|
||||
} else { // Nope, only a single block exists
|
||||
|
||||
// Clear the items in the block to release any reference we may be keeping alive
|
||||
for(
|
||||
int index = this.firstBlockStartIndex; index < this.lastBlockEndIndex; ++index
|
||||
) {
|
||||
this.blocks[0][index] = default(TItem);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Reset the counters to restart the deque from scratch
|
||||
this.firstBlockStartIndex = 0;
|
||||
this.lastBlockEndIndex = 0;
|
||||
this.count = 0;
|
||||
#if DEBUG
|
||||
++this.version;
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>Removes the specified item from the deque</summary>
|
||||
/// <param name="item">Item that will be removed from the deque</param>
|
||||
/// <returns>True if the item was found and removed</returns>
|
||||
public bool Remove(TItem item) {
|
||||
int index = IndexOf(item);
|
||||
if(index == -1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
RemoveAt(index);
|
||||
#if DEBUG
|
||||
++this.version;
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>Removes the first item in the double-ended queue</summary>
|
||||
public void RemoveFirst() {
|
||||
if(this.count == 0) {
|
||||
throw new InvalidOperationException("Cannot remove items from empty deque");
|
||||
}
|
||||
|
||||
// This is necessary to make sure the deque doesn't hold dead objects alive
|
||||
// in unreachable spaces of its memory.
|
||||
this.blocks[0][this.firstBlockStartIndex] = default(TItem);
|
||||
|
||||
// Cut off the item from the first block. If the block became empty and it's
|
||||
// not the last remaining block, remove it as well.
|
||||
++this.firstBlockStartIndex;
|
||||
if(this.firstBlockStartIndex >= this.blockSize) { // Block became empty
|
||||
if(this.count > 1) { // Still more blocks in queue, remove block
|
||||
this.blocks.RemoveAt(0);
|
||||
this.firstBlockStartIndex = 0;
|
||||
} else { // Last block - do not remove
|
||||
this.firstBlockStartIndex = 0;
|
||||
this.lastBlockEndIndex = 0;
|
||||
}
|
||||
}
|
||||
--this.count;
|
||||
#if DEBUG
|
||||
++this.version;
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>Removes the last item in the double-ended queue</summary>
|
||||
public void RemoveLast() {
|
||||
if(this.count == 0) {
|
||||
throw new InvalidOperationException("Cannot remove items from empty deque");
|
||||
}
|
||||
|
||||
// This is necessary to make sure the deque doesn't hold dead objects alive
|
||||
// in unreachable spaces of its memory.
|
||||
int lastBlock = this.blocks.Count - 1;
|
||||
this.blocks[lastBlock][this.lastBlockEndIndex - 1] = default(TItem);
|
||||
|
||||
// Cut off the last item in the last block. If the block became empty and it's
|
||||
// not the last remaining block, remove it as well.
|
||||
--this.lastBlockEndIndex;
|
||||
if(this.lastBlockEndIndex == 0) { // Block became empty
|
||||
if(this.count > 1) {
|
||||
this.blocks.RemoveAt(lastBlock);
|
||||
this.lastBlockEndIndex = this.blockSize;
|
||||
} else { // Last block - do not remove
|
||||
this.firstBlockStartIndex = 0;
|
||||
this.lastBlockEndIndex = 0;
|
||||
}
|
||||
}
|
||||
--this.count;
|
||||
#if DEBUG
|
||||
++this.version;
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>Removes the item at the specified index</summary>
|
||||
/// <param name="index">Index of the item that will be removed</param>
|
||||
public void RemoveAt(int index) {
|
||||
int distanceToRightEnd = this.count - index;
|
||||
if(index < distanceToRightEnd) { // Are we closer to the left end?
|
||||
removeFromLeft(index);
|
||||
} else { // Nope, we're closer to the right end
|
||||
removeFromRight(index);
|
||||
}
|
||||
#if DEBUG
|
||||
++this.version;
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes an item from the left side of the queue by shifting all items that
|
||||
/// come before it to the right by one
|
||||
/// </summary>
|
||||
/// <param name="index">Index of the item that will be removed</param>
|
||||
private void removeFromLeft(int index) {
|
||||
if(index == 0) {
|
||||
RemoveFirst();
|
||||
} else {
|
||||
int blockIndex, subIndex;
|
||||
findIndex(index, out blockIndex, out subIndex);
|
||||
|
||||
int firstBlock = 0;
|
||||
int endIndex;
|
||||
|
||||
if(blockIndex > firstBlock) {
|
||||
Array.Copy(
|
||||
this.blocks[blockIndex], 0,
|
||||
this.blocks[blockIndex], 1,
|
||||
subIndex
|
||||
);
|
||||
this.blocks[blockIndex][0] = this.blocks[blockIndex - 1][this.blockSize - 1];
|
||||
|
||||
for(int tempIndex = blockIndex - 1; tempIndex > firstBlock; --tempIndex) {
|
||||
Array.Copy(
|
||||
this.blocks[tempIndex], 0,
|
||||
this.blocks[tempIndex], 1,
|
||||
this.blockSize - 1
|
||||
);
|
||||
this.blocks[tempIndex][0] = this.blocks[tempIndex - 1][this.blockSize - 1];
|
||||
}
|
||||
|
||||
endIndex = this.blockSize - 1;
|
||||
} else {
|
||||
endIndex = subIndex;
|
||||
}
|
||||
|
||||
Array.Copy(
|
||||
this.blocks[firstBlock], this.firstBlockStartIndex,
|
||||
this.blocks[firstBlock], this.firstBlockStartIndex + 1,
|
||||
endIndex - this.firstBlockStartIndex
|
||||
);
|
||||
|
||||
if(this.firstBlockStartIndex == this.blockSize - 1) {
|
||||
this.blocks.RemoveAt(0);
|
||||
this.firstBlockStartIndex = 0;
|
||||
} else {
|
||||
this.blocks[0][this.firstBlockStartIndex] = default(TItem);
|
||||
++this.firstBlockStartIndex;
|
||||
}
|
||||
|
||||
--this.count;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes an item from the right side of the queue by shifting all items that
|
||||
/// come after it to the left by one
|
||||
/// </summary>
|
||||
/// <param name="index">Index of the item that will be removed</param>
|
||||
private void removeFromRight(int index) {
|
||||
if(index == this.count - 1) {
|
||||
RemoveLast();
|
||||
} else {
|
||||
int blockIndex, subIndex;
|
||||
findIndex(index, out blockIndex, out subIndex);
|
||||
|
||||
int lastBlock = this.blocks.Count - 1;
|
||||
int startIndex;
|
||||
|
||||
if(blockIndex < lastBlock) {
|
||||
Array.Copy(
|
||||
this.blocks[blockIndex], subIndex + 1,
|
||||
this.blocks[blockIndex], subIndex,
|
||||
this.blockSize - subIndex - 1
|
||||
);
|
||||
this.blocks[blockIndex][this.blockSize - 1] = this.blocks[blockIndex + 1][0];
|
||||
|
||||
for(int tempIndex = blockIndex + 1; tempIndex < lastBlock; ++tempIndex) {
|
||||
Array.Copy(
|
||||
this.blocks[tempIndex], 1,
|
||||
this.blocks[tempIndex], 0,
|
||||
this.blockSize - 1
|
||||
);
|
||||
this.blocks[tempIndex][this.blockSize - 1] = this.blocks[tempIndex + 1][0];
|
||||
}
|
||||
|
||||
startIndex = 0;
|
||||
} else {
|
||||
startIndex = subIndex;
|
||||
}
|
||||
|
||||
Array.Copy(
|
||||
this.blocks[lastBlock], startIndex + 1,
|
||||
this.blocks[lastBlock], startIndex,
|
||||
this.lastBlockEndIndex - startIndex - 1
|
||||
);
|
||||
|
||||
if(this.lastBlockEndIndex == 1) {
|
||||
this.blocks.RemoveAt(lastBlock);
|
||||
this.lastBlockEndIndex = this.blockSize;
|
||||
} else {
|
||||
this.blocks[lastBlock][this.lastBlockEndIndex - 1] = default(TItem);
|
||||
--this.lastBlockEndIndex;
|
||||
}
|
||||
|
||||
--this.count;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} // namespace Nuclex.Support.Collections
|
||||
|
|
|
|||
|
|
@ -1,84 +1,83 @@
|
|||
#region CPL License
|
||||
/*
|
||||
Nuclex Framework
|
||||
Copyright (C) 2002-2017 Nuclex Development Labs
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the IBM Common Public License as
|
||||
published by the IBM Corporation; either version 1.0 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
IBM Common Public License for more details.
|
||||
|
||||
You should have received a copy of the IBM Common Public
|
||||
License along with this library
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
|
||||
namespace Nuclex.Support.Collections {
|
||||
|
||||
partial class Deque<TItem> {
|
||||
|
||||
/// <summary>
|
||||
/// Determines the index of the first occurence of the specified item in the deque
|
||||
/// </summary>
|
||||
/// <param name="item">Item that will be located in the deque</param>
|
||||
/// <returns>The index of the item or -1 if it wasn't found</returns>
|
||||
public int IndexOf(TItem item) {
|
||||
if(this.blocks.Count == 1) { // Only one block to scan?
|
||||
int length = this.lastBlockEndIndex - this.firstBlockStartIndex;
|
||||
int index = Array.IndexOf<TItem>(
|
||||
this.blocks[0], item, this.firstBlockStartIndex, length
|
||||
);
|
||||
|
||||
// If we found something, we need to adjust its index so the first item in
|
||||
// the deque always appears at index 0 to the user
|
||||
if(index != -1) {
|
||||
return (index - this.firstBlockStartIndex);
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
} else { // At least two blocks exist
|
||||
|
||||
// Scan the first block for the item and if found, return the index
|
||||
int length = this.blockSize - this.firstBlockStartIndex;
|
||||
int index = Array.IndexOf<TItem>(
|
||||
this.blocks[0], item, this.firstBlockStartIndex, length
|
||||
);
|
||||
|
||||
// If we found something, we need to adjust its index
|
||||
if(index != -1) {
|
||||
return (index - this.firstBlockStartIndex);
|
||||
}
|
||||
|
||||
int lastBlock = this.blocks.Count - 1;
|
||||
for(int tempIndex = 1; tempIndex < lastBlock; ++tempIndex) {
|
||||
index = Array.IndexOf<TItem>(
|
||||
this.blocks[tempIndex], item, 0, this.blockSize
|
||||
);
|
||||
if(index != -1) {
|
||||
return (index - this.firstBlockStartIndex + tempIndex * this.blockSize);
|
||||
}
|
||||
}
|
||||
|
||||
// Nothing found, continue the search in the
|
||||
index = Array.IndexOf<TItem>(
|
||||
this.blocks[lastBlock], item, 0, this.lastBlockEndIndex
|
||||
);
|
||||
if(index == -1) {
|
||||
return -1;
|
||||
} else {
|
||||
return (index - this.firstBlockStartIndex + lastBlock * this.blockSize);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} // namespace Nuclex.Support.Collections
|
||||
#region Apache License 2.0
|
||||
/*
|
||||
Nuclex .NET Framework
|
||||
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
#endregion // Apache License 2.0
|
||||
|
||||
using System;
|
||||
|
||||
namespace Nuclex.Support.Collections {
|
||||
|
||||
partial class Deque<TItem> {
|
||||
|
||||
/// <summary>
|
||||
/// Determines the index of the first occurence of the specified item in the deque
|
||||
/// </summary>
|
||||
/// <param name="item">Item that will be located in the deque</param>
|
||||
/// <returns>The index of the item or -1 if it wasn't found</returns>
|
||||
public int IndexOf(TItem item) {
|
||||
if(this.blocks.Count == 1) { // Only one block to scan?
|
||||
int length = this.lastBlockEndIndex - this.firstBlockStartIndex;
|
||||
int index = Array.IndexOf<TItem>(
|
||||
this.blocks[0], item, this.firstBlockStartIndex, length
|
||||
);
|
||||
|
||||
// If we found something, we need to adjust its index so the first item in
|
||||
// the deque always appears at index 0 to the user
|
||||
if(index != -1) {
|
||||
return (index - this.firstBlockStartIndex);
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
} else { // At least two blocks exist
|
||||
|
||||
// Scan the first block for the item and if found, return the index
|
||||
int length = this.blockSize - this.firstBlockStartIndex;
|
||||
int index = Array.IndexOf<TItem>(
|
||||
this.blocks[0], item, this.firstBlockStartIndex, length
|
||||
);
|
||||
|
||||
// If we found something, we need to adjust its index
|
||||
if(index != -1) {
|
||||
return (index - this.firstBlockStartIndex);
|
||||
}
|
||||
|
||||
int lastBlock = this.blocks.Count - 1;
|
||||
for(int tempIndex = 1; tempIndex < lastBlock; ++tempIndex) {
|
||||
index = Array.IndexOf<TItem>(
|
||||
this.blocks[tempIndex], item, 0, this.blockSize
|
||||
);
|
||||
if(index != -1) {
|
||||
return (index - this.firstBlockStartIndex + tempIndex * this.blockSize);
|
||||
}
|
||||
}
|
||||
|
||||
// Nothing found, continue the search in the
|
||||
index = Array.IndexOf<TItem>(
|
||||
this.blocks[lastBlock], item, 0, this.lastBlockEndIndex
|
||||
);
|
||||
if(index == -1) {
|
||||
return -1;
|
||||
} else {
|
||||
return (index - this.firstBlockStartIndex + lastBlock * this.blockSize);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} // namespace Nuclex.Support.Collections
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -1,334 +1,333 @@
|
|||
#region CPL License
|
||||
/*
|
||||
Nuclex Framework
|
||||
Copyright (C) 2002-2017 Nuclex Development Labs
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the IBM Common Public License as
|
||||
published by the IBM Corporation; either version 1.0 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
IBM Common Public License for more details.
|
||||
|
||||
You should have received a copy of the IBM Common Public
|
||||
License along with this library
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections;
|
||||
|
||||
namespace Nuclex.Support.Collections {
|
||||
|
||||
/// <summary>A double-ended queue that allocates memory in blocks</summary>
|
||||
/// <typeparam name="TItem">Type of the items being stored in the queue</typeparam>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// The double-ended queue allows items to be appended to either side of the queue
|
||||
/// without a hefty toll on performance. Like its namesake in C++, it is implemented
|
||||
/// using multiple arrays.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Therefore, it's not only good at coping with lists that are modified at their
|
||||
/// beginning, but also at handling huge data sets since enlarging the deque doesn't
|
||||
/// require items to be copied around and it still can be accessed by index.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public partial class Deque<TItem> : IList<TItem>, IList {
|
||||
|
||||
#region class Enumerator
|
||||
|
||||
/// <summary>Enumerates over the items in a deque</summary>
|
||||
private class Enumerator : IEnumerator<TItem>, IEnumerator {
|
||||
|
||||
/// <summary>Initializes a new deque enumerator</summary>
|
||||
/// <param name="deque">Deque whose items will be enumerated</param>
|
||||
public Enumerator(Deque<TItem> deque) {
|
||||
this.deque = deque;
|
||||
this.blockSize = this.deque.blockSize;
|
||||
this.lastBlock = this.deque.blocks.Count - 1;
|
||||
this.lastBlockEndIndex = this.deque.lastBlockEndIndex - 1;
|
||||
|
||||
Reset();
|
||||
}
|
||||
|
||||
/// <summary>Immediately releases all resources owned by the instance</summary>
|
||||
public void Dispose() {
|
||||
this.deque = null;
|
||||
this.currentBlock = null;
|
||||
}
|
||||
|
||||
/// <summary>The item at the enumerator's current position</summary>
|
||||
public TItem Current {
|
||||
get {
|
||||
#if DEBUG
|
||||
checkVersion();
|
||||
#endif
|
||||
if (this.currentBlock == null) {
|
||||
throw new InvalidOperationException("Enumerator is not on a valid position");
|
||||
}
|
||||
|
||||
return this.currentBlock[this.subIndex];
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Advances the enumerator to the next item</summary>
|
||||
/// <returns>True if there was a next item</returns>
|
||||
public bool MoveNext() {
|
||||
|
||||
#if DEBUG
|
||||
checkVersion();
|
||||
#endif
|
||||
|
||||
// If we haven't reached the last block yet
|
||||
if (this.currentBlockIndex < this.lastBlock) {
|
||||
|
||||
// Advance to the next item. If the end of the current block is reached,
|
||||
// go to the next block's first item
|
||||
++this.subIndex;
|
||||
if (this.subIndex >= this.blockSize) {
|
||||
++this.currentBlockIndex;
|
||||
this.currentBlock = this.deque.blocks[this.currentBlockIndex];
|
||||
if (this.currentBlockIndex == 0) {
|
||||
this.subIndex = this.deque.firstBlockStartIndex;
|
||||
} else {
|
||||
this.subIndex = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Item found. If the current block wasn't the last block, an item *has*
|
||||
// to follow since otherwise, no further blocks would exist!
|
||||
return true;
|
||||
|
||||
} else { // We in or beyond the last block
|
||||
|
||||
// Are there any items left to advance to?
|
||||
if (this.subIndex < this.lastBlockEndIndex) {
|
||||
++this.subIndex;
|
||||
return true;
|
||||
} else { // Nope, we've reached the end of the deque
|
||||
this.currentBlock = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>Resets the enumerator to its initial position</summary>
|
||||
public void Reset() {
|
||||
this.currentBlock = null;
|
||||
this.currentBlockIndex = -1;
|
||||
this.subIndex = this.deque.blockSize - 1;
|
||||
#if DEBUG
|
||||
this.expectedVersion = this.deque.version;
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>The item at the enumerator's current position</summary>
|
||||
object IEnumerator.Current {
|
||||
get { return Current; }
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
/// <summary>Ensures that the deque has not changed</summary>
|
||||
private void checkVersion() {
|
||||
if(this.expectedVersion != this.deque.version)
|
||||
throw new InvalidOperationException("Deque has been modified");
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>Deque the enumerator belongs to</summary>
|
||||
private Deque<TItem> deque;
|
||||
/// <summary>Size of the blocks in the deque</summary>
|
||||
private int blockSize;
|
||||
/// <summary>Index of the last block in the deque</summary>
|
||||
private int lastBlock;
|
||||
/// <summary>End index of the items in the deque's last block</summary>
|
||||
private int lastBlockEndIndex;
|
||||
|
||||
/// <summary>Index of the block the enumerator currently is in</summary>
|
||||
private int currentBlockIndex;
|
||||
/// <summary>Reference to the block being enumerated</summary>
|
||||
private TItem[] currentBlock;
|
||||
/// <summary>Index in the current block</summary>
|
||||
private int subIndex;
|
||||
#if DEBUG
|
||||
/// <summary>Version the deque is expected to have</summary>
|
||||
private int expectedVersion;
|
||||
#endif
|
||||
}
|
||||
|
||||
#endregion // class Enumerator
|
||||
|
||||
/// <summary>Initializes a new deque</summary>
|
||||
public Deque() : this(512) { }
|
||||
|
||||
/// <summary>Initializes a new deque using the specified block size</summary>
|
||||
/// <param name="blockSize">Size of the individual memory blocks used</param>
|
||||
public Deque(int blockSize) {
|
||||
this.blockSize = blockSize;
|
||||
|
||||
this.blocks = new List<TItem[]>();
|
||||
this.blocks.Add(new TItem[this.blockSize]);
|
||||
}
|
||||
|
||||
/// <summary>Number of items contained in the double ended queue</summary>
|
||||
public int Count {
|
||||
get { return this.count; }
|
||||
}
|
||||
|
||||
/// <summary>Accesses an item by its index</summary>
|
||||
/// <param name="index">Index of the item that will be accessed</param>
|
||||
/// <returns>The item at the specified index</returns>
|
||||
public TItem this[int index] {
|
||||
get {
|
||||
int blockIndex, subIndex;
|
||||
findIndex(index, out blockIndex, out subIndex);
|
||||
|
||||
return this.blocks[blockIndex][subIndex];
|
||||
}
|
||||
set {
|
||||
int blockIndex, subIndex;
|
||||
findIndex(index, out blockIndex, out subIndex);
|
||||
|
||||
this.blocks[blockIndex][subIndex] = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>The first item in the double-ended queue</summary>
|
||||
public TItem First {
|
||||
get {
|
||||
if (this.count == 0) {
|
||||
throw new InvalidOperationException("The deque is empty");
|
||||
}
|
||||
return this.blocks[0][this.firstBlockStartIndex];
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>The last item in the double-ended queue</summary>
|
||||
public TItem Last {
|
||||
get {
|
||||
if (this.count == 0) {
|
||||
throw new InvalidOperationException("The deque is empty");
|
||||
}
|
||||
return this.blocks[this.blocks.Count - 1][this.lastBlockEndIndex - 1];
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Determines whether the deque contains the specified item</summary>
|
||||
/// <param name="item">Item the deque will be scanned for</param>
|
||||
/// <returns>True if the deque contains the item, false otherwise</returns>
|
||||
public bool Contains(TItem item) {
|
||||
return (IndexOf(item) != -1);
|
||||
}
|
||||
|
||||
/// <summary>Copies the contents of the deque into an array</summary>
|
||||
/// <param name="array">Array the contents of the deque will be copied into</param>
|
||||
/// <param name="arrayIndex">Array index the deque contents will begin at</param>
|
||||
public void CopyTo(TItem[] array, int arrayIndex) {
|
||||
if (this.count > (array.Length - arrayIndex)) {
|
||||
throw new ArgumentException(
|
||||
"Array too small to hold the collection items starting at the specified index"
|
||||
);
|
||||
}
|
||||
|
||||
if (this.blocks.Count == 1) { // Does only one block exist?
|
||||
|
||||
// Copy the one and only block there is
|
||||
Array.Copy(
|
||||
this.blocks[0], this.firstBlockStartIndex,
|
||||
array, arrayIndex,
|
||||
this.lastBlockEndIndex - this.firstBlockStartIndex
|
||||
);
|
||||
|
||||
} else { // Multiple blocks exist
|
||||
|
||||
// Copy the first block which is filled from the start index to its end
|
||||
int length = this.blockSize - this.firstBlockStartIndex;
|
||||
Array.Copy(
|
||||
this.blocks[0], this.firstBlockStartIndex,
|
||||
array, arrayIndex,
|
||||
length
|
||||
);
|
||||
arrayIndex += length;
|
||||
|
||||
// Copy all intermediate blocks (if there are any). These are completely filled
|
||||
int lastBlock = this.blocks.Count - 1;
|
||||
for (int index = 1; index < lastBlock; ++index) {
|
||||
Array.Copy(
|
||||
this.blocks[index], 0,
|
||||
array, arrayIndex,
|
||||
this.blockSize
|
||||
);
|
||||
arrayIndex += this.blockSize;
|
||||
}
|
||||
|
||||
// Copy the final block which is filled from the beginning to the end index
|
||||
Array.Copy(
|
||||
this.blocks[lastBlock], 0,
|
||||
array, arrayIndex,
|
||||
this.lastBlockEndIndex
|
||||
);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Obtains a new enumerator for the contents of the deque</summary>
|
||||
/// <returns>The new enumerator</returns>
|
||||
public IEnumerator<TItem> GetEnumerator() {
|
||||
return new Enumerator(this);
|
||||
}
|
||||
|
||||
/// <summary>Calculates the block index and local sub index of an entry</summary>
|
||||
/// <param name="index">Index of the entry that will be located</param>
|
||||
/// <param name="blockIndex">Index of the block the entry is contained in</param>
|
||||
/// <param name="subIndex">Local sub index of the entry within the block</param>
|
||||
private void findIndex(int index, out int blockIndex, out int subIndex) {
|
||||
if ((index < 0) || (index >= this.count)) {
|
||||
throw new ArgumentOutOfRangeException("Index out of range", "index");
|
||||
}
|
||||
|
||||
index += this.firstBlockStartIndex;
|
||||
blockIndex = Math.DivRem(index, this.blockSize, out subIndex);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the provided object can be placed in the deque
|
||||
/// </summary>
|
||||
/// <param name="value">Value that will be checked for compatibility</param>
|
||||
/// <returns>True if the value can be placed in the deque</returns>
|
||||
private static bool isCompatibleObject(object value) {
|
||||
return ((value is TItem) || ((value == null) && !typeof(TItem).IsValueType));
|
||||
}
|
||||
|
||||
/// <summary>Verifies that the provided object matches the deque's type</summary>
|
||||
/// <param name="value">Value that will be checked for compatibility</param>
|
||||
private static void verifyCompatibleObject(object value) {
|
||||
if (!isCompatibleObject(value)) {
|
||||
throw new ArgumentException("Value does not match the deque's type", "value");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Number if items currently stored in the deque</summary>
|
||||
private int count;
|
||||
/// <summary>Size of a single deque block</summary>
|
||||
private int blockSize;
|
||||
/// <summary>Memory blocks being used to store the deque's data</summary>
|
||||
private List<TItem[]> blocks;
|
||||
/// <summary>Starting index of data in the first block</summary>
|
||||
private int firstBlockStartIndex;
|
||||
/// <summary>End index of data in the last block</summary>
|
||||
private int lastBlockEndIndex;
|
||||
#if DEBUG
|
||||
/// <summary>Used to detect when enumerators go out of sync</summary>
|
||||
private int version;
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
} // namespace Nuclex.Support.Collections
|
||||
#region Apache License 2.0
|
||||
/*
|
||||
Nuclex .NET Framework
|
||||
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
#endregion // Apache License 2.0
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections;
|
||||
|
||||
namespace Nuclex.Support.Collections {
|
||||
|
||||
/// <summary>A double-ended queue that allocates memory in blocks</summary>
|
||||
/// <typeparam name="TItem">Type of the items being stored in the queue</typeparam>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// The double-ended queue allows items to be appended to either side of the queue
|
||||
/// without a hefty toll on performance. Like its namesake in C++, it is implemented
|
||||
/// using multiple arrays.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Therefore, it's not only good at coping with lists that are modified at their
|
||||
/// beginning, but also at handling huge data sets since enlarging the deque doesn't
|
||||
/// require items to be copied around and it still can be accessed by index.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public partial class Deque<TItem> : IList<TItem>, IList {
|
||||
|
||||
#region class Enumerator
|
||||
|
||||
/// <summary>Enumerates over the items in a deque</summary>
|
||||
private class Enumerator : IEnumerator<TItem>, IEnumerator {
|
||||
|
||||
/// <summary>Initializes a new deque enumerator</summary>
|
||||
/// <param name="deque">Deque whose items will be enumerated</param>
|
||||
public Enumerator(Deque<TItem> deque) {
|
||||
this.deque = deque;
|
||||
this.blockSize = this.deque.blockSize;
|
||||
this.lastBlock = this.deque.blocks.Count - 1;
|
||||
this.lastBlockEndIndex = this.deque.lastBlockEndIndex - 1;
|
||||
|
||||
Reset();
|
||||
}
|
||||
|
||||
/// <summary>Immediately releases all resources owned by the instance</summary>
|
||||
public void Dispose() {
|
||||
this.deque = null;
|
||||
this.currentBlock = null;
|
||||
}
|
||||
|
||||
/// <summary>The item at the enumerator's current position</summary>
|
||||
public TItem Current {
|
||||
get {
|
||||
#if DEBUG
|
||||
checkVersion();
|
||||
#endif
|
||||
if (this.currentBlock == null) {
|
||||
throw new InvalidOperationException("Enumerator is not on a valid position");
|
||||
}
|
||||
|
||||
return this.currentBlock[this.subIndex];
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Advances the enumerator to the next item</summary>
|
||||
/// <returns>True if there was a next item</returns>
|
||||
public bool MoveNext() {
|
||||
|
||||
#if DEBUG
|
||||
checkVersion();
|
||||
#endif
|
||||
|
||||
// If we haven't reached the last block yet
|
||||
if (this.currentBlockIndex < this.lastBlock) {
|
||||
|
||||
// Advance to the next item. If the end of the current block is reached,
|
||||
// go to the next block's first item
|
||||
++this.subIndex;
|
||||
if (this.subIndex >= this.blockSize) {
|
||||
++this.currentBlockIndex;
|
||||
this.currentBlock = this.deque.blocks[this.currentBlockIndex];
|
||||
if (this.currentBlockIndex == 0) {
|
||||
this.subIndex = this.deque.firstBlockStartIndex;
|
||||
} else {
|
||||
this.subIndex = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Item found. If the current block wasn't the last block, an item *has*
|
||||
// to follow since otherwise, no further blocks would exist!
|
||||
return true;
|
||||
|
||||
} else { // We in or beyond the last block
|
||||
|
||||
// Are there any items left to advance to?
|
||||
if (this.subIndex < this.lastBlockEndIndex) {
|
||||
++this.subIndex;
|
||||
return true;
|
||||
} else { // Nope, we've reached the end of the deque
|
||||
this.currentBlock = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>Resets the enumerator to its initial position</summary>
|
||||
public void Reset() {
|
||||
this.currentBlock = null;
|
||||
this.currentBlockIndex = -1;
|
||||
this.subIndex = this.deque.blockSize - 1;
|
||||
#if DEBUG
|
||||
this.expectedVersion = this.deque.version;
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>The item at the enumerator's current position</summary>
|
||||
object IEnumerator.Current {
|
||||
get { return Current; }
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
/// <summary>Ensures that the deque has not changed</summary>
|
||||
private void checkVersion() {
|
||||
if(this.expectedVersion != this.deque.version)
|
||||
throw new InvalidOperationException("Deque has been modified");
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>Deque the enumerator belongs to</summary>
|
||||
private Deque<TItem> deque;
|
||||
/// <summary>Size of the blocks in the deque</summary>
|
||||
private int blockSize;
|
||||
/// <summary>Index of the last block in the deque</summary>
|
||||
private int lastBlock;
|
||||
/// <summary>End index of the items in the deque's last block</summary>
|
||||
private int lastBlockEndIndex;
|
||||
|
||||
/// <summary>Index of the block the enumerator currently is in</summary>
|
||||
private int currentBlockIndex;
|
||||
/// <summary>Reference to the block being enumerated</summary>
|
||||
private TItem[] currentBlock;
|
||||
/// <summary>Index in the current block</summary>
|
||||
private int subIndex;
|
||||
#if DEBUG
|
||||
/// <summary>Version the deque is expected to have</summary>
|
||||
private int expectedVersion;
|
||||
#endif
|
||||
}
|
||||
|
||||
#endregion // class Enumerator
|
||||
|
||||
/// <summary>Initializes a new deque</summary>
|
||||
public Deque() : this(512) { }
|
||||
|
||||
/// <summary>Initializes a new deque using the specified block size</summary>
|
||||
/// <param name="blockSize">Size of the individual memory blocks used</param>
|
||||
public Deque(int blockSize) {
|
||||
this.blockSize = blockSize;
|
||||
|
||||
this.blocks = new List<TItem[]>();
|
||||
this.blocks.Add(new TItem[this.blockSize]);
|
||||
}
|
||||
|
||||
/// <summary>Number of items contained in the double ended queue</summary>
|
||||
public int Count {
|
||||
get { return this.count; }
|
||||
}
|
||||
|
||||
/// <summary>Accesses an item by its index</summary>
|
||||
/// <param name="index">Index of the item that will be accessed</param>
|
||||
/// <returns>The item at the specified index</returns>
|
||||
public TItem this[int index] {
|
||||
get {
|
||||
int blockIndex, subIndex;
|
||||
findIndex(index, out blockIndex, out subIndex);
|
||||
|
||||
return this.blocks[blockIndex][subIndex];
|
||||
}
|
||||
set {
|
||||
int blockIndex, subIndex;
|
||||
findIndex(index, out blockIndex, out subIndex);
|
||||
|
||||
this.blocks[blockIndex][subIndex] = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>The first item in the double-ended queue</summary>
|
||||
public TItem First {
|
||||
get {
|
||||
if (this.count == 0) {
|
||||
throw new InvalidOperationException("The deque is empty");
|
||||
}
|
||||
return this.blocks[0][this.firstBlockStartIndex];
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>The last item in the double-ended queue</summary>
|
||||
public TItem Last {
|
||||
get {
|
||||
if (this.count == 0) {
|
||||
throw new InvalidOperationException("The deque is empty");
|
||||
}
|
||||
return this.blocks[this.blocks.Count - 1][this.lastBlockEndIndex - 1];
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Determines whether the deque contains the specified item</summary>
|
||||
/// <param name="item">Item the deque will be scanned for</param>
|
||||
/// <returns>True if the deque contains the item, false otherwise</returns>
|
||||
public bool Contains(TItem item) {
|
||||
return (IndexOf(item) != -1);
|
||||
}
|
||||
|
||||
/// <summary>Copies the contents of the deque into an array</summary>
|
||||
/// <param name="array">Array the contents of the deque will be copied into</param>
|
||||
/// <param name="arrayIndex">Array index the deque contents will begin at</param>
|
||||
public void CopyTo(TItem[] array, int arrayIndex) {
|
||||
if (this.count > (array.Length - arrayIndex)) {
|
||||
throw new ArgumentException(
|
||||
"Array too small to hold the collection items starting at the specified index"
|
||||
);
|
||||
}
|
||||
|
||||
if (this.blocks.Count == 1) { // Does only one block exist?
|
||||
|
||||
// Copy the one and only block there is
|
||||
Array.Copy(
|
||||
this.blocks[0], this.firstBlockStartIndex,
|
||||
array, arrayIndex,
|
||||
this.lastBlockEndIndex - this.firstBlockStartIndex
|
||||
);
|
||||
|
||||
} else { // Multiple blocks exist
|
||||
|
||||
// Copy the first block which is filled from the start index to its end
|
||||
int length = this.blockSize - this.firstBlockStartIndex;
|
||||
Array.Copy(
|
||||
this.blocks[0], this.firstBlockStartIndex,
|
||||
array, arrayIndex,
|
||||
length
|
||||
);
|
||||
arrayIndex += length;
|
||||
|
||||
// Copy all intermediate blocks (if there are any). These are completely filled
|
||||
int lastBlock = this.blocks.Count - 1;
|
||||
for (int index = 1; index < lastBlock; ++index) {
|
||||
Array.Copy(
|
||||
this.blocks[index], 0,
|
||||
array, arrayIndex,
|
||||
this.blockSize
|
||||
);
|
||||
arrayIndex += this.blockSize;
|
||||
}
|
||||
|
||||
// Copy the final block which is filled from the beginning to the end index
|
||||
Array.Copy(
|
||||
this.blocks[lastBlock], 0,
|
||||
array, arrayIndex,
|
||||
this.lastBlockEndIndex
|
||||
);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Obtains a new enumerator for the contents of the deque</summary>
|
||||
/// <returns>The new enumerator</returns>
|
||||
public IEnumerator<TItem> GetEnumerator() {
|
||||
return new Enumerator(this);
|
||||
}
|
||||
|
||||
/// <summary>Calculates the block index and local sub index of an entry</summary>
|
||||
/// <param name="index">Index of the entry that will be located</param>
|
||||
/// <param name="blockIndex">Index of the block the entry is contained in</param>
|
||||
/// <param name="subIndex">Local sub index of the entry within the block</param>
|
||||
private void findIndex(int index, out int blockIndex, out int subIndex) {
|
||||
if ((index < 0) || (index >= this.count)) {
|
||||
throw new ArgumentOutOfRangeException("Index out of range", "index");
|
||||
}
|
||||
|
||||
index += this.firstBlockStartIndex;
|
||||
blockIndex = Math.DivRem(index, this.blockSize, out subIndex);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the provided object can be placed in the deque
|
||||
/// </summary>
|
||||
/// <param name="value">Value that will be checked for compatibility</param>
|
||||
/// <returns>True if the value can be placed in the deque</returns>
|
||||
private static bool isCompatibleObject(object value) {
|
||||
return ((value is TItem) || ((value == null) && !typeof(TItem).IsValueType));
|
||||
}
|
||||
|
||||
/// <summary>Verifies that the provided object matches the deque's type</summary>
|
||||
/// <param name="value">Value that will be checked for compatibility</param>
|
||||
private static void verifyCompatibleObject(object value) {
|
||||
if (!isCompatibleObject(value)) {
|
||||
throw new ArgumentException("Value does not match the deque's type", "value");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Number if items currently stored in the deque</summary>
|
||||
private int count;
|
||||
/// <summary>Size of a single deque block</summary>
|
||||
private int blockSize;
|
||||
/// <summary>Memory blocks being used to store the deque's data</summary>
|
||||
private List<TItem[]> blocks;
|
||||
/// <summary>Starting index of data in the first block</summary>
|
||||
private int firstBlockStartIndex;
|
||||
/// <summary>End index of data in the last block</summary>
|
||||
private int lastBlockEndIndex;
|
||||
#if DEBUG
|
||||
/// <summary>Used to detect when enumerators go out of sync</summary>
|
||||
private int version;
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
} // namespace Nuclex.Support.Collections
|
||||
|
|
|
|||
|
|
@ -1,137 +1,136 @@
|
|||
#region CPL License
|
||||
/*
|
||||
Nuclex Framework
|
||||
Copyright (C) 2002-2017 Nuclex Development Labs
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the IBM Common Public License as
|
||||
published by the IBM Corporation; either version 1.0 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
IBM Common Public License for more details.
|
||||
|
||||
You should have received a copy of the IBM Common Public
|
||||
License along with this library
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
||||
#if UNITTEST
|
||||
|
||||
using NUnit.Framework;
|
||||
|
||||
namespace Nuclex.Support.Collections {
|
||||
|
||||
/// <summary>Unit Test for the IList extension methods</summary>
|
||||
[TestFixture]
|
||||
internal class IListExtensionsTest {
|
||||
|
||||
/// <summary>Tests whether the insertion sort algorithm sorts a list correctly</summary>
|
||||
[Test]
|
||||
public void InsertionSortCanSortWholeList() {
|
||||
var testList = new List<int>(capacity: 5) { 1, 5, 2, 4, 3 };
|
||||
var testListAsIList = (IList<int>)testList;
|
||||
|
||||
testListAsIList.InsertionSort();
|
||||
|
||||
CollectionAssert.AreEqual(
|
||||
new List<int>(capacity: 5) { 1, 2, 3, 4, 5 },
|
||||
testList
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>Tests whether the insertion sort algorithm works on big lists</summary>
|
||||
[Test]
|
||||
public void InsertionSortCanSortBigList() {
|
||||
const int ListSize = 16384;
|
||||
|
||||
var testList = new List<int>(capacity: ListSize);
|
||||
{
|
||||
var random = new Random();
|
||||
for(int index = 0; index < ListSize; ++index) {
|
||||
testList.Add(random.Next());
|
||||
}
|
||||
}
|
||||
|
||||
var testListAsIList = (IList<int>)testList;
|
||||
testListAsIList.InsertionSort();
|
||||
|
||||
for(int index = 1; index < ListSize; ++index) {
|
||||
Assert.LessOrEqual(testListAsIList[index - 1], testListAsIList[index]);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Tests whether the insertion sort algorithm respects custom boundaries</summary>
|
||||
[Test]
|
||||
public void InsertionSortCanSortListSegment() {
|
||||
var testList = new List<int>(capacity: 7) { 9, 1, 5, 2, 4, 3, 0 };
|
||||
var testListAsIList = (IList<int>)testList;
|
||||
|
||||
testListAsIList.InsertionSort(1, 5, Comparer<int>.Default);
|
||||
|
||||
CollectionAssert.AreEqual(
|
||||
new List<int>(capacity: 7) { 9, 1, 2, 3, 4, 5, 0 },
|
||||
testList
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>Tests whether the quicksort algorithm sorts a list correctly</summary>
|
||||
[Test]
|
||||
public void QuickSortCanSortWholeList() {
|
||||
var testList = new List<int>(capacity: 5) { 1, 5, 2, 4, 3 };
|
||||
var testListAsIList = (IList<int>)testList;
|
||||
|
||||
testListAsIList.QuickSort();
|
||||
|
||||
CollectionAssert.AreEqual(
|
||||
new List<int>(capacity: 5) { 1, 2, 3, 4, 5 },
|
||||
testList
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>Tests whether the quicksort algorithm works on big lists</summary>
|
||||
[Test]
|
||||
public void QuickSortCanSortBigList() {
|
||||
const int ListSize = 16384;
|
||||
|
||||
var testList = new List<int>(capacity: ListSize);
|
||||
{
|
||||
var random = new Random();
|
||||
for(int index = 0; index < ListSize; ++index) {
|
||||
testList.Add(random.Next());
|
||||
}
|
||||
}
|
||||
|
||||
var testListAsIList = (IList<int>)testList;
|
||||
testListAsIList.QuickSort();
|
||||
|
||||
for(int index = 1; index < ListSize; ++index) {
|
||||
Assert.LessOrEqual(testListAsIList[index - 1], testListAsIList[index]);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Tests whether the quicksort algorithm respects custom boundaries</summary>
|
||||
[Test]
|
||||
public void QuickSortCanSortListSegment() {
|
||||
var testList = new List<int>(capacity: 7) { 9, 1, 5, 2, 4, 3, 0 };
|
||||
var testListAsIList = (IList<int>)testList;
|
||||
|
||||
testListAsIList.QuickSort(1, 5, Comparer<int>.Default);
|
||||
|
||||
CollectionAssert.AreEqual(
|
||||
new List<int>(capacity: 7) { 9, 1, 2, 3, 4, 5, 0 },
|
||||
testList
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} // namespace Nuclex.Support.Collections
|
||||
|
||||
#endif // UNITTEST
|
||||
#region Apache License 2.0
|
||||
/*
|
||||
Nuclex .NET Framework
|
||||
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
#endregion // Apache License 2.0
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
||||
#if UNITTEST
|
||||
|
||||
using NUnit.Framework;
|
||||
|
||||
namespace Nuclex.Support.Collections {
|
||||
|
||||
/// <summary>Unit Test for the IList extension methods</summary>
|
||||
[TestFixture]
|
||||
internal class IListExtensionsTest {
|
||||
|
||||
/// <summary>Tests whether the insertion sort algorithm sorts a list correctly</summary>
|
||||
[Test]
|
||||
public void InsertionSortCanSortWholeList() {
|
||||
var testList = new List<int>(capacity: 5) { 1, 5, 2, 4, 3 };
|
||||
var testListAsIList = (IList<int>)testList;
|
||||
|
||||
testListAsIList.InsertionSort();
|
||||
|
||||
CollectionAssert.AreEqual(
|
||||
new List<int>(capacity: 5) { 1, 2, 3, 4, 5 },
|
||||
testList
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>Tests whether the insertion sort algorithm works on big lists</summary>
|
||||
[Test]
|
||||
public void InsertionSortCanSortBigList() {
|
||||
const int ListSize = 16384;
|
||||
|
||||
var testList = new List<int>(capacity: ListSize);
|
||||
{
|
||||
var random = new Random();
|
||||
for(int index = 0; index < ListSize; ++index) {
|
||||
testList.Add(random.Next());
|
||||
}
|
||||
}
|
||||
|
||||
var testListAsIList = (IList<int>)testList;
|
||||
testListAsIList.InsertionSort();
|
||||
|
||||
for(int index = 1; index < ListSize; ++index) {
|
||||
Assert.LessOrEqual(testListAsIList[index - 1], testListAsIList[index]);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Tests whether the insertion sort algorithm respects custom boundaries</summary>
|
||||
[Test]
|
||||
public void InsertionSortCanSortListSegment() {
|
||||
var testList = new List<int>(capacity: 7) { 9, 1, 5, 2, 4, 3, 0 };
|
||||
var testListAsIList = (IList<int>)testList;
|
||||
|
||||
testListAsIList.InsertionSort(1, 5, Comparer<int>.Default);
|
||||
|
||||
CollectionAssert.AreEqual(
|
||||
new List<int>(capacity: 7) { 9, 1, 2, 3, 4, 5, 0 },
|
||||
testList
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>Tests whether the quicksort algorithm sorts a list correctly</summary>
|
||||
[Test]
|
||||
public void QuickSortCanSortWholeList() {
|
||||
var testList = new List<int>(capacity: 5) { 1, 5, 2, 4, 3 };
|
||||
var testListAsIList = (IList<int>)testList;
|
||||
|
||||
testListAsIList.QuickSort();
|
||||
|
||||
CollectionAssert.AreEqual(
|
||||
new List<int>(capacity: 5) { 1, 2, 3, 4, 5 },
|
||||
testList
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>Tests whether the quicksort algorithm works on big lists</summary>
|
||||
[Test]
|
||||
public void QuickSortCanSortBigList() {
|
||||
const int ListSize = 16384;
|
||||
|
||||
var testList = new List<int>(capacity: ListSize);
|
||||
{
|
||||
var random = new Random();
|
||||
for(int index = 0; index < ListSize; ++index) {
|
||||
testList.Add(random.Next());
|
||||
}
|
||||
}
|
||||
|
||||
var testListAsIList = (IList<int>)testList;
|
||||
testListAsIList.QuickSort();
|
||||
|
||||
for(int index = 1; index < ListSize; ++index) {
|
||||
Assert.LessOrEqual(testListAsIList[index - 1], testListAsIList[index]);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Tests whether the quicksort algorithm respects custom boundaries</summary>
|
||||
[Test]
|
||||
public void QuickSortCanSortListSegment() {
|
||||
var testList = new List<int>(capacity: 7) { 9, 1, 5, 2, 4, 3, 0 };
|
||||
var testListAsIList = (IList<int>)testList;
|
||||
|
||||
testListAsIList.QuickSort(1, 5, Comparer<int>.Default);
|
||||
|
||||
CollectionAssert.AreEqual(
|
||||
new List<int>(capacity: 7) { 9, 1, 2, 3, 4, 5, 0 },
|
||||
testList
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} // namespace Nuclex.Support.Collections
|
||||
|
||||
#endif // UNITTEST
|
||||
|
|
|
|||
|
|
@ -1,219 +1,218 @@
|
|||
#region CPL License
|
||||
/*
|
||||
Nuclex Framework
|
||||
Copyright (C) 2002-2017 Nuclex Development Labs
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the IBM Common Public License as
|
||||
published by the IBM Corporation; either version 1.0 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
IBM Common Public License for more details.
|
||||
|
||||
You should have received a copy of the IBM Common Public
|
||||
License along with this library
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Nuclex.Support.Collections {
|
||||
|
||||
/// <summary>Extension methods for the IList interface</summary>
|
||||
public static class ListExtensions {
|
||||
|
||||
#region struct Partition
|
||||
|
||||
/// <summary>
|
||||
/// Stores the left and right index of a partition for the quicksort algorithm
|
||||
/// </summary>
|
||||
private struct Partition {
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new partition using the specified left and right index
|
||||
/// </summary>
|
||||
/// <param name="leftmostIndex">Index of the leftmost element in the partition</param>
|
||||
/// <param name="rightmostIndex">Index of the rightmost element in the partition</param>
|
||||
public Partition(int leftmostIndex, int rightmostIndex) {
|
||||
this.LeftmostIndex = leftmostIndex;
|
||||
this.RightmostIndex = rightmostIndex;
|
||||
}
|
||||
|
||||
/// <summary>Index of the leftmost element in the partition</summary>
|
||||
public int LeftmostIndex;
|
||||
/// <summary>Index of the rightmost element in the partition</summary>
|
||||
public int RightmostIndex;
|
||||
|
||||
}
|
||||
|
||||
#endregion // struct Partition
|
||||
|
||||
/// <summary>
|
||||
/// Sorts a subset of the elements in an IList<T> using the insertion sort algorithm
|
||||
/// </summary>
|
||||
/// <typeparam name="TElement">Type of elements the list contains</typeparam>
|
||||
/// <param name="list">List in which a subset will be sorted</param>
|
||||
/// <param name="startIndex">Index at which the sorting process will begin</param>
|
||||
/// <param name="count">Index one past the last element that will be sorted</param>
|
||||
/// <param name="comparer">Comparison function to use for comparing list elements</param>
|
||||
public static void InsertionSort<TElement>(
|
||||
this IList<TElement> list, int startIndex, int count, IComparer<TElement> comparer
|
||||
) {
|
||||
int rightIndex = startIndex;
|
||||
|
||||
int lastIndex = startIndex + count - 1;
|
||||
for(int index = startIndex + 1; index <= lastIndex; ++index) {
|
||||
TElement temp = list[index];
|
||||
|
||||
while(rightIndex >= startIndex) {
|
||||
if(comparer.Compare(list[rightIndex], temp) < 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
list[rightIndex + 1] = list[rightIndex];
|
||||
--rightIndex;
|
||||
}
|
||||
|
||||
list[rightIndex + 1] = temp;
|
||||
|
||||
rightIndex = index;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sorts all the elements in an IList<T> using the insertion sort algorithm
|
||||
/// </summary>
|
||||
/// <typeparam name="TElement">Type of elements the list contains</typeparam>
|
||||
/// <param name="list">List in which a subset will be sorted</param>
|
||||
/// <param name="comparer">Comparison function to use for comparing list elements</param>
|
||||
public static void InsertionSort<TElement>(
|
||||
this IList<TElement> list, IComparer<TElement> comparer
|
||||
) {
|
||||
InsertionSort(list, 0, list.Count, comparer);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sorts all the elements in an IList<T> using the insertion sort algorithm
|
||||
/// </summary>
|
||||
/// <typeparam name="TElement">Type of elements the list contains</typeparam>
|
||||
/// <param name="list">List in which a subset will be sorted</param>
|
||||
public static void InsertionSort<TElement>(this IList<TElement> list) {
|
||||
InsertionSort(list, 0, list.Count, Comparer<TElement>.Default);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sorts all the elements in an IList<T> using the quicksort algorithm
|
||||
/// </summary>
|
||||
/// <typeparam name="TElement">Type of elements the list contains</typeparam>
|
||||
/// <param name="list">List in which a subset will be sorted</param>
|
||||
/// <param name="startIndex">Index at which the sorting process will begin</param>
|
||||
/// <param name="count">Index one past the last element that will be sorted</param>
|
||||
/// <param name="comparer">Comparison function to use for comparing list elements</param>
|
||||
public static void QuickSort<TElement>(
|
||||
this IList<TElement> list, int startIndex, int count, IComparer<TElement> comparer
|
||||
) {
|
||||
var remainingPartitions = new Stack<Partition>();
|
||||
|
||||
int lastIndex = startIndex + count - 1;
|
||||
for(; ; ) {
|
||||
int pivotIndex = quicksortPartition(list, startIndex, lastIndex, comparer);
|
||||
|
||||
// This block just queues the next partitions left of the pivot point and right
|
||||
// of the pivot point (if they contain at least 2 elements). It's fattened up
|
||||
// a bit by trying to forego the stack and adjusting the startIndex/lastIndex
|
||||
// directly where it's clear the next loop can process these partitions.
|
||||
if(pivotIndex - 1 > startIndex) { // Are the elements to sort right of the pivot?
|
||||
if(pivotIndex + 1 < lastIndex) { // Are the elements left of the pivot as well?
|
||||
remainingPartitions.Push(new Partition(startIndex, pivotIndex - 1));
|
||||
startIndex = pivotIndex + 1;
|
||||
} else { // Elements to sort are only right of the pivot
|
||||
lastIndex = pivotIndex - 1;
|
||||
}
|
||||
} else if(pivotIndex + 1 < lastIndex) { // Are elements to sort only left of the pivot?
|
||||
startIndex = pivotIndex + 1;
|
||||
} else { // Partition was fully sorted
|
||||
|
||||
// Did we process all queued partitions? If so, the list is sorted
|
||||
if(remainingPartitions.Count == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Pull the next partition that needs to be sorted from the stack
|
||||
Partition current = remainingPartitions.Pop();
|
||||
startIndex = current.LeftmostIndex;
|
||||
lastIndex = current.RightmostIndex;
|
||||
|
||||
} // if sortable sub-partitions exist left/right/nowhere
|
||||
} // for ever (termination inside loop)
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sorts all the elements in an IList<T> using the insertion sort algorithm
|
||||
/// </summary>
|
||||
/// <typeparam name="TElement">Type of elements the list contains</typeparam>
|
||||
/// <param name="list">List in which a subset will be sorted</param>
|
||||
/// <param name="comparer">Comparison function to use for comparing list elements</param>
|
||||
public static void QuickSort<TElement>(
|
||||
this IList<TElement> list, IComparer<TElement> comparer
|
||||
) {
|
||||
QuickSort(list, 0, list.Count, comparer);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sorts all the elements in an IList<T> using the insertion sort algorithm
|
||||
/// </summary>
|
||||
/// <typeparam name="TElement">Type of elements the list contains</typeparam>
|
||||
/// <param name="list">List in which a subset will be sorted</param>
|
||||
public static void QuickSort<TElement>(this IList<TElement> list) {
|
||||
QuickSort(list, 0, list.Count, Comparer<TElement>.Default);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Moves an element downward over all elements that precede it in the sort order
|
||||
/// </summary>
|
||||
/// <typeparam name="TElement">Type of elements stored in the sorted list</typeparam>
|
||||
/// <param name="list">List that is being sorted</param>
|
||||
/// <param name="firstIndex">Index of the first element in the partition</param>
|
||||
/// <param name="lastIndex">Index of hte last element in the partition</param>
|
||||
/// <param name="comparer">
|
||||
/// Comparison function that decides the ordering of elements
|
||||
/// </param>
|
||||
/// <returns>The index of the next pivot element</returns>
|
||||
private static int quicksortPartition<TElement>(
|
||||
IList<TElement> list, int firstIndex, int lastIndex, IComparer<TElement> comparer
|
||||
) {
|
||||
|
||||
// Step through all elements in the partition and accumulate those that are smaller
|
||||
// than the last element on the left (by swapping). At the end 'firstIndex' will be
|
||||
// the new pivot point, left of which are all elements smaller than the element at
|
||||
// 'lastIndex' and right of it will be all elements which are larger.
|
||||
for(int index = firstIndex; index < lastIndex; ++index) {
|
||||
if(comparer.Compare(list[index], list[lastIndex]) < 0) {
|
||||
TElement temp = list[firstIndex];
|
||||
list[firstIndex] = list[index];
|
||||
list[index] = temp;
|
||||
|
||||
++firstIndex;
|
||||
}
|
||||
}
|
||||
|
||||
// The element at 'lastIndex' as a sort value that's in the middle of the two sides,
|
||||
// so we'll have to swap it, too, putting it in the middle and making it the new pivot.
|
||||
{
|
||||
TElement temp = list[firstIndex];
|
||||
list[firstIndex] = list[lastIndex];
|
||||
list[lastIndex] = temp;
|
||||
}
|
||||
|
||||
// Return the index of the new pivot position
|
||||
return firstIndex;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} // namespace Nuclex.Support.Collections
|
||||
#region Apache License 2.0
|
||||
/*
|
||||
Nuclex .NET Framework
|
||||
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
#endregion // Apache License 2.0
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Nuclex.Support.Collections {
|
||||
|
||||
/// <summary>Extension methods for the IList interface</summary>
|
||||
public static class ListExtensions {
|
||||
|
||||
#region struct Partition
|
||||
|
||||
/// <summary>
|
||||
/// Stores the left and right index of a partition for the quicksort algorithm
|
||||
/// </summary>
|
||||
private struct Partition {
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new partition using the specified left and right index
|
||||
/// </summary>
|
||||
/// <param name="leftmostIndex">Index of the leftmost element in the partition</param>
|
||||
/// <param name="rightmostIndex">Index of the rightmost element in the partition</param>
|
||||
public Partition(int leftmostIndex, int rightmostIndex) {
|
||||
this.LeftmostIndex = leftmostIndex;
|
||||
this.RightmostIndex = rightmostIndex;
|
||||
}
|
||||
|
||||
/// <summary>Index of the leftmost element in the partition</summary>
|
||||
public int LeftmostIndex;
|
||||
/// <summary>Index of the rightmost element in the partition</summary>
|
||||
public int RightmostIndex;
|
||||
|
||||
}
|
||||
|
||||
#endregion // struct Partition
|
||||
|
||||
/// <summary>
|
||||
/// Sorts a subset of the elements in an IList<T> using the insertion sort algorithm
|
||||
/// </summary>
|
||||
/// <typeparam name="TElement">Type of elements the list contains</typeparam>
|
||||
/// <param name="list">List in which a subset will be sorted</param>
|
||||
/// <param name="startIndex">Index at which the sorting process will begin</param>
|
||||
/// <param name="count">Index one past the last element that will be sorted</param>
|
||||
/// <param name="comparer">Comparison function to use for comparing list elements</param>
|
||||
public static void InsertionSort<TElement>(
|
||||
this IList<TElement> list, int startIndex, int count, IComparer<TElement> comparer
|
||||
) {
|
||||
int rightIndex = startIndex;
|
||||
|
||||
int lastIndex = startIndex + count - 1;
|
||||
for(int index = startIndex + 1; index <= lastIndex; ++index) {
|
||||
TElement temp = list[index];
|
||||
|
||||
while(rightIndex >= startIndex) {
|
||||
if(comparer.Compare(list[rightIndex], temp) < 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
list[rightIndex + 1] = list[rightIndex];
|
||||
--rightIndex;
|
||||
}
|
||||
|
||||
list[rightIndex + 1] = temp;
|
||||
|
||||
rightIndex = index;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sorts all the elements in an IList<T> using the insertion sort algorithm
|
||||
/// </summary>
|
||||
/// <typeparam name="TElement">Type of elements the list contains</typeparam>
|
||||
/// <param name="list">List in which a subset will be sorted</param>
|
||||
/// <param name="comparer">Comparison function to use for comparing list elements</param>
|
||||
public static void InsertionSort<TElement>(
|
||||
this IList<TElement> list, IComparer<TElement> comparer
|
||||
) {
|
||||
InsertionSort(list, 0, list.Count, comparer);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sorts all the elements in an IList<T> using the insertion sort algorithm
|
||||
/// </summary>
|
||||
/// <typeparam name="TElement">Type of elements the list contains</typeparam>
|
||||
/// <param name="list">List in which a subset will be sorted</param>
|
||||
public static void InsertionSort<TElement>(this IList<TElement> list) {
|
||||
InsertionSort(list, 0, list.Count, Comparer<TElement>.Default);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sorts all the elements in an IList<T> using the quicksort algorithm
|
||||
/// </summary>
|
||||
/// <typeparam name="TElement">Type of elements the list contains</typeparam>
|
||||
/// <param name="list">List in which a subset will be sorted</param>
|
||||
/// <param name="startIndex">Index at which the sorting process will begin</param>
|
||||
/// <param name="count">Index one past the last element that will be sorted</param>
|
||||
/// <param name="comparer">Comparison function to use for comparing list elements</param>
|
||||
public static void QuickSort<TElement>(
|
||||
this IList<TElement> list, int startIndex, int count, IComparer<TElement> comparer
|
||||
) {
|
||||
var remainingPartitions = new Stack<Partition>();
|
||||
|
||||
int lastIndex = startIndex + count - 1;
|
||||
for(; ; ) {
|
||||
int pivotIndex = quicksortPartition(list, startIndex, lastIndex, comparer);
|
||||
|
||||
// This block just queues the next partitions left of the pivot point and right
|
||||
// of the pivot point (if they contain at least 2 elements). It's fattened up
|
||||
// a bit by trying to forego the stack and adjusting the startIndex/lastIndex
|
||||
// directly where it's clear the next loop can process these partitions.
|
||||
if(pivotIndex - 1 > startIndex) { // Are the elements to sort right of the pivot?
|
||||
if(pivotIndex + 1 < lastIndex) { // Are the elements left of the pivot as well?
|
||||
remainingPartitions.Push(new Partition(startIndex, pivotIndex - 1));
|
||||
startIndex = pivotIndex + 1;
|
||||
} else { // Elements to sort are only right of the pivot
|
||||
lastIndex = pivotIndex - 1;
|
||||
}
|
||||
} else if(pivotIndex + 1 < lastIndex) { // Are elements to sort only left of the pivot?
|
||||
startIndex = pivotIndex + 1;
|
||||
} else { // Partition was fully sorted
|
||||
|
||||
// Did we process all queued partitions? If so, the list is sorted
|
||||
if(remainingPartitions.Count == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Pull the next partition that needs to be sorted from the stack
|
||||
Partition current = remainingPartitions.Pop();
|
||||
startIndex = current.LeftmostIndex;
|
||||
lastIndex = current.RightmostIndex;
|
||||
|
||||
} // if sortable sub-partitions exist left/right/nowhere
|
||||
} // for ever (termination inside loop)
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sorts all the elements in an IList<T> using the insertion sort algorithm
|
||||
/// </summary>
|
||||
/// <typeparam name="TElement">Type of elements the list contains</typeparam>
|
||||
/// <param name="list">List in which a subset will be sorted</param>
|
||||
/// <param name="comparer">Comparison function to use for comparing list elements</param>
|
||||
public static void QuickSort<TElement>(
|
||||
this IList<TElement> list, IComparer<TElement> comparer
|
||||
) {
|
||||
QuickSort(list, 0, list.Count, comparer);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sorts all the elements in an IList<T> using the insertion sort algorithm
|
||||
/// </summary>
|
||||
/// <typeparam name="TElement">Type of elements the list contains</typeparam>
|
||||
/// <param name="list">List in which a subset will be sorted</param>
|
||||
public static void QuickSort<TElement>(this IList<TElement> list) {
|
||||
QuickSort(list, 0, list.Count, Comparer<TElement>.Default);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Moves an element downward over all elements that precede it in the sort order
|
||||
/// </summary>
|
||||
/// <typeparam name="TElement">Type of elements stored in the sorted list</typeparam>
|
||||
/// <param name="list">List that is being sorted</param>
|
||||
/// <param name="firstIndex">Index of the first element in the partition</param>
|
||||
/// <param name="lastIndex">Index of hte last element in the partition</param>
|
||||
/// <param name="comparer">
|
||||
/// Comparison function that decides the ordering of elements
|
||||
/// </param>
|
||||
/// <returns>The index of the next pivot element</returns>
|
||||
private static int quicksortPartition<TElement>(
|
||||
IList<TElement> list, int firstIndex, int lastIndex, IComparer<TElement> comparer
|
||||
) {
|
||||
|
||||
// Step through all elements in the partition and accumulate those that are smaller
|
||||
// than the last element on the left (by swapping). At the end 'firstIndex' will be
|
||||
// the new pivot point, left of which are all elements smaller than the element at
|
||||
// 'lastIndex' and right of it will be all elements which are larger.
|
||||
for(int index = firstIndex; index < lastIndex; ++index) {
|
||||
if(comparer.Compare(list[index], list[lastIndex]) < 0) {
|
||||
TElement temp = list[firstIndex];
|
||||
list[firstIndex] = list[index];
|
||||
list[index] = temp;
|
||||
|
||||
++firstIndex;
|
||||
}
|
||||
}
|
||||
|
||||
// The element at 'lastIndex' as a sort value that's in the middle of the two sides,
|
||||
// so we'll have to swap it, too, putting it in the middle and making it the new pivot.
|
||||
{
|
||||
TElement temp = list[firstIndex];
|
||||
list[firstIndex] = list[lastIndex];
|
||||
list[lastIndex] = temp;
|
||||
}
|
||||
|
||||
// Return the index of the new pivot position
|
||||
return firstIndex;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} // namespace Nuclex.Support.Collections
|
||||
|
|
|
|||
|
|
@ -1,68 +1,67 @@
|
|||
#region CPL License
|
||||
/*
|
||||
Nuclex Framework
|
||||
Copyright (C) 2002-2017 Nuclex Development Labs
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the IBM Common Public License as
|
||||
published by the IBM Corporation; either version 1.0 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
IBM Common Public License for more details.
|
||||
|
||||
You should have received a copy of the IBM Common Public
|
||||
License along with this library
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Nuclex.Support.Collections {
|
||||
|
||||
/// <summary>
|
||||
/// Associative collection that can store several values under one key and vice versa
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">Type of keys used within the dictionary</typeparam>
|
||||
/// <typeparam name="TValue">Type of values stored in the dictionary</typeparam>
|
||||
public interface IMultiDictionary<TKey, TValue> :
|
||||
IDictionary<TKey, ICollection<TValue>>,
|
||||
IDictionary,
|
||||
ICollection<KeyValuePair<TKey, TValue>>,
|
||||
IEnumerable<KeyValuePair<TKey, TValue>>,
|
||||
IEnumerable {
|
||||
|
||||
/// <summary>Adds a value into the dictionary under the provided key</summary>
|
||||
/// <param name="key">Key the value will be stored under</param>
|
||||
/// <param name="value">Value that will be stored under the specified key</param>
|
||||
void Add(TKey key, TValue value);
|
||||
|
||||
/// <summary>Determines the number of values stored under the specified key</summary>
|
||||
/// <param name="key">Key whose values will be counted</param>
|
||||
/// <returns>The number of values stored under the specified key</returns>
|
||||
int CountValues(TKey key);
|
||||
|
||||
/// <summary>
|
||||
/// Removes the item with the specified key and value from the dictionary
|
||||
/// </summary>
|
||||
/// <param name="key">Key of the item that will be removed</param>
|
||||
/// <param name="value">Value of the item that will be removed</param>
|
||||
/// <returns>
|
||||
/// True if the specified item was contained in the dictionary and was removed
|
||||
/// </returns>
|
||||
/// <exception cref="NotSupportedException">If the dictionary is read-only</exception>
|
||||
bool Remove(TKey key, TValue value);
|
||||
|
||||
/// <summary>Removes all items with the specified key from the dictionary</summary>
|
||||
/// <param name="key">Key of the item that will be removed</param>
|
||||
/// <returns>The number of items that have been removed from the dictionary</returns>
|
||||
/// <exception cref="NotSupportedException">If the dictionary is read-only</exception>
|
||||
int RemoveKey(TKey key);
|
||||
|
||||
}
|
||||
|
||||
} // namespace Nuclex.Support.Collections
|
||||
#region Apache License 2.0
|
||||
/*
|
||||
Nuclex .NET Framework
|
||||
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
#endregion // Apache License 2.0
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Nuclex.Support.Collections {
|
||||
|
||||
/// <summary>
|
||||
/// Associative collection that can store several values under one key and vice versa
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">Type of keys used within the dictionary</typeparam>
|
||||
/// <typeparam name="TValue">Type of values stored in the dictionary</typeparam>
|
||||
public interface IMultiDictionary<TKey, TValue> :
|
||||
IDictionary<TKey, ICollection<TValue>>,
|
||||
IDictionary,
|
||||
ICollection<KeyValuePair<TKey, TValue>>,
|
||||
IEnumerable<KeyValuePair<TKey, TValue>>,
|
||||
IEnumerable {
|
||||
|
||||
/// <summary>Adds a value into the dictionary under the provided key</summary>
|
||||
/// <param name="key">Key the value will be stored under</param>
|
||||
/// <param name="value">Value that will be stored under the specified key</param>
|
||||
void Add(TKey key, TValue value);
|
||||
|
||||
/// <summary>Determines the number of values stored under the specified key</summary>
|
||||
/// <param name="key">Key whose values will be counted</param>
|
||||
/// <returns>The number of values stored under the specified key</returns>
|
||||
int CountValues(TKey key);
|
||||
|
||||
/// <summary>
|
||||
/// Removes the item with the specified key and value from the dictionary
|
||||
/// </summary>
|
||||
/// <param name="key">Key of the item that will be removed</param>
|
||||
/// <param name="value">Value of the item that will be removed</param>
|
||||
/// <returns>
|
||||
/// True if the specified item was contained in the dictionary and was removed
|
||||
/// </returns>
|
||||
/// <exception cref="NotSupportedException">If the dictionary is read-only</exception>
|
||||
bool Remove(TKey key, TValue value);
|
||||
|
||||
/// <summary>Removes all items with the specified key from the dictionary</summary>
|
||||
/// <param name="key">Key of the item that will be removed</param>
|
||||
/// <returns>The number of items that have been removed from the dictionary</returns>
|
||||
/// <exception cref="NotSupportedException">If the dictionary is read-only</exception>
|
||||
int RemoveKey(TKey key);
|
||||
|
||||
}
|
||||
|
||||
} // namespace Nuclex.Support.Collections
|
||||
|
|
|
|||
|
|
@ -1,51 +1,50 @@
|
|||
#region CPL License
|
||||
/*
|
||||
Nuclex Framework
|
||||
Copyright (C) 2002-2017 Nuclex Development Labs
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the IBM Common Public License as
|
||||
published by the IBM Corporation; either version 1.0 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
IBM Common Public License for more details.
|
||||
|
||||
You should have received a copy of the IBM Common Public
|
||||
License along with this library
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
|
||||
namespace Nuclex.Support.Collections {
|
||||
|
||||
/// <summary>Interface for collections that can be observed</summary>
|
||||
/// <typeparam name="TItem">Type of items managed in the collection</typeparam>
|
||||
public interface IObservableCollection<TItem> {
|
||||
|
||||
/// <summary>Raised when an item has been added to the collection</summary>
|
||||
event EventHandler<ItemEventArgs<TItem>> ItemAdded;
|
||||
|
||||
/// <summary>Raised when an item is removed from the collection</summary>
|
||||
event EventHandler<ItemEventArgs<TItem>> ItemRemoved;
|
||||
|
||||
/// <summary>Raised when an item is replaced in the collection</summary>
|
||||
event EventHandler<ItemReplaceEventArgs<TItem>> ItemReplaced;
|
||||
|
||||
/// <summary>Raised when the collection is about to be cleared</summary>
|
||||
/// <remarks>
|
||||
/// This could be covered by calling ItemRemoved for each item currently
|
||||
/// contained in the collection, but it is often simpler and more efficient
|
||||
/// to process the clearing of the entire collection as a special operation.
|
||||
/// </remarks>
|
||||
event EventHandler Clearing;
|
||||
|
||||
/// <summary>Raised when the collection has been cleared of its items</summary>
|
||||
event EventHandler Cleared;
|
||||
|
||||
}
|
||||
|
||||
} // namespace Nuclex.Support.Collections
|
||||
#region Apache License 2.0
|
||||
/*
|
||||
Nuclex .NET Framework
|
||||
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
#endregion // Apache License 2.0
|
||||
|
||||
using System;
|
||||
|
||||
namespace Nuclex.Support.Collections {
|
||||
|
||||
/// <summary>Interface for collections that can be observed</summary>
|
||||
/// <typeparam name="TItem">Type of items managed in the collection</typeparam>
|
||||
public interface IObservableCollection<TItem> {
|
||||
|
||||
/// <summary>Raised when an item has been added to the collection</summary>
|
||||
event EventHandler<ItemEventArgs<TItem>> ItemAdded;
|
||||
|
||||
/// <summary>Raised when an item is removed from the collection</summary>
|
||||
event EventHandler<ItemEventArgs<TItem>> ItemRemoved;
|
||||
|
||||
/// <summary>Raised when an item is replaced in the collection</summary>
|
||||
event EventHandler<ItemReplaceEventArgs<TItem>> ItemReplaced;
|
||||
|
||||
/// <summary>Raised when the collection is about to be cleared</summary>
|
||||
/// <remarks>
|
||||
/// This could be covered by calling ItemRemoved for each item currently
|
||||
/// contained in the collection, but it is often simpler and more efficient
|
||||
/// to process the clearing of the entire collection as a special operation.
|
||||
/// </remarks>
|
||||
event EventHandler Clearing;
|
||||
|
||||
/// <summary>Raised when the collection has been cleared of its items</summary>
|
||||
event EventHandler Cleared;
|
||||
|
||||
}
|
||||
|
||||
} // namespace Nuclex.Support.Collections
|
||||
|
|
|
|||
|
|
@ -1,45 +1,44 @@
|
|||
#region CPL License
|
||||
/*
|
||||
Nuclex Framework
|
||||
Copyright (C) 2002-2017 Nuclex Development Labs
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the IBM Common Public License as
|
||||
published by the IBM Corporation; either version 1.0 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
IBM Common Public License for more details.
|
||||
|
||||
You should have received a copy of the IBM Common Public
|
||||
License along with this library
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
|
||||
namespace Nuclex.Support.Collections {
|
||||
|
||||
/// <summary>Allows an object to be returned to its initial state</summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// This interface is typically implemented by objects which can be recycled
|
||||
/// in order to avoid the construction overhead of a heavyweight class and to
|
||||
/// eliminate garbage by reusing instances.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Recyclable objects should have a parameterless constructor and calling
|
||||
/// their Recycle() method should bring them back into the state they were
|
||||
/// in right after they had been constructed.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public interface IRecyclable {
|
||||
|
||||
/// <summary>Returns the object to its initial state</summary>
|
||||
void Recycle();
|
||||
|
||||
}
|
||||
|
||||
} // namespace Nuclex.Support.Collections
|
||||
#region Apache License 2.0
|
||||
/*
|
||||
Nuclex .NET Framework
|
||||
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
#endregion // Apache License 2.0
|
||||
|
||||
using System;
|
||||
|
||||
namespace Nuclex.Support.Collections {
|
||||
|
||||
/// <summary>Allows an object to be returned to its initial state</summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// This interface is typically implemented by objects which can be recycled
|
||||
/// in order to avoid the construction overhead of a heavyweight class and to
|
||||
/// eliminate garbage by reusing instances.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Recyclable objects should have a parameterless constructor and calling
|
||||
/// their Recycle() method should bring them back into the state they were
|
||||
/// in right after they had been constructed.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public interface IRecyclable {
|
||||
|
||||
/// <summary>Returns the object to its initial state</summary>
|
||||
void Recycle();
|
||||
|
||||
}
|
||||
|
||||
} // namespace Nuclex.Support.Collections
|
||||
|
|
|
|||
|
|
@ -1,56 +1,55 @@
|
|||
#region CPL License
|
||||
/*
|
||||
Nuclex Framework
|
||||
Copyright (C) 2002-2017 Nuclex Development Labs
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the IBM Common Public License as
|
||||
published by the IBM Corporation; either version 1.0 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
IBM Common Public License for more details.
|
||||
|
||||
You should have received a copy of the IBM Common Public
|
||||
License along with this library
|
||||
*/
|
||||
#endregion
|
||||
|
||||
#if UNITTEST
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
using NUnit.Framework;
|
||||
|
||||
namespace Nuclex.Support.Collections {
|
||||
|
||||
/// <summary>Unit Test for the item event argument container</summary>
|
||||
[TestFixture]
|
||||
internal class ItemEventArgsTest {
|
||||
|
||||
/// <summary>
|
||||
/// Tests whether an integer argument can be stored in the argument container
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void IntegersCanBeCarried() {
|
||||
var test = new ItemEventArgs<int>(12345);
|
||||
Assert.AreEqual(12345, test.Item);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests whether a string argument can be stored in the argument container
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void StringsCanBeCarried() {
|
||||
var test = new ItemEventArgs<string>("hello world");
|
||||
Assert.AreEqual("hello world", test.Item);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} // namespace Nuclex.Support.Collections
|
||||
|
||||
#endif // UNITTEST
|
||||
#region Apache License 2.0
|
||||
/*
|
||||
Nuclex .NET Framework
|
||||
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
#endregion // Apache License 2.0
|
||||
|
||||
#if UNITTEST
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
using NUnit.Framework;
|
||||
|
||||
namespace Nuclex.Support.Collections {
|
||||
|
||||
/// <summary>Unit Test for the item event argument container</summary>
|
||||
[TestFixture]
|
||||
internal class ItemEventArgsTest {
|
||||
|
||||
/// <summary>
|
||||
/// Tests whether an integer argument can be stored in the argument container
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void IntegersCanBeCarried() {
|
||||
var test = new ItemEventArgs<int>(12345);
|
||||
Assert.AreEqual(12345, test.Item);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests whether a string argument can be stored in the argument container
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void StringsCanBeCarried() {
|
||||
var test = new ItemEventArgs<string>("hello world");
|
||||
Assert.AreEqual("hello world", test.Item);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} // namespace Nuclex.Support.Collections
|
||||
|
||||
#endif // UNITTEST
|
||||
|
|
|
|||
|
|
@ -1,46 +1,45 @@
|
|||
#region CPL License
|
||||
/*
|
||||
Nuclex Framework
|
||||
Copyright (C) 2002-2017 Nuclex Development Labs
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the IBM Common Public License as
|
||||
published by the IBM Corporation; either version 1.0 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
IBM Common Public License for more details.
|
||||
|
||||
You should have received a copy of the IBM Common Public
|
||||
License along with this library
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
|
||||
namespace Nuclex.Support.Collections {
|
||||
|
||||
/// <summary>
|
||||
/// Argument container used by collections to notify about changed items
|
||||
/// </summary>
|
||||
public class ItemEventArgs<TItem> : EventArgs {
|
||||
|
||||
/// <summary>Initializes a new event arguments supplier</summary>
|
||||
/// <param name="item">Item to be supplied to the event handler</param>
|
||||
public ItemEventArgs(TItem item) {
|
||||
this.item = item;
|
||||
}
|
||||
|
||||
/// <summary>Obtains the collection item the event arguments are carrying</summary>
|
||||
public TItem Item {
|
||||
get { return this.item; }
|
||||
}
|
||||
|
||||
/// <summary>Item to be passed to the event handler</summary>
|
||||
private TItem item;
|
||||
|
||||
}
|
||||
|
||||
} // namespace Nuclex.Support.Collections
|
||||
#region Apache License 2.0
|
||||
/*
|
||||
Nuclex .NET Framework
|
||||
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
#endregion // Apache License 2.0
|
||||
|
||||
using System;
|
||||
|
||||
namespace Nuclex.Support.Collections {
|
||||
|
||||
/// <summary>
|
||||
/// Argument container used by collections to notify about changed items
|
||||
/// </summary>
|
||||
public class ItemEventArgs<TItem> : EventArgs {
|
||||
|
||||
/// <summary>Initializes a new event arguments supplier</summary>
|
||||
/// <param name="item">Item to be supplied to the event handler</param>
|
||||
public ItemEventArgs(TItem item) {
|
||||
this.item = item;
|
||||
}
|
||||
|
||||
/// <summary>Obtains the collection item the event arguments are carrying</summary>
|
||||
public TItem Item {
|
||||
get { return this.item; }
|
||||
}
|
||||
|
||||
/// <summary>Item to be passed to the event handler</summary>
|
||||
private TItem item;
|
||||
|
||||
}
|
||||
|
||||
} // namespace Nuclex.Support.Collections
|
||||
|
|
|
|||
|
|
@ -1,58 +1,57 @@
|
|||
#region CPL License
|
||||
/*
|
||||
Nuclex Framework
|
||||
Copyright (C) 2002-2017 Nuclex Development Labs
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the IBM Common Public License as
|
||||
published by the IBM Corporation; either version 1.0 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
IBM Common Public License for more details.
|
||||
|
||||
You should have received a copy of the IBM Common Public
|
||||
License along with this library
|
||||
*/
|
||||
#endregion
|
||||
|
||||
#if UNITTEST
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
using NUnit.Framework;
|
||||
|
||||
namespace Nuclex.Support.Collections {
|
||||
|
||||
/// <summary>Unit Test for the item event argument container</summary>
|
||||
[TestFixture]
|
||||
internal class ItemReplaceEventArgsTest {
|
||||
|
||||
/// <summary>
|
||||
/// Tests whether an integer argument can be stored in the argument container
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void IntegersCanBeCarried() {
|
||||
var test = new ItemReplaceEventArgs<int>(12345, 54321);
|
||||
Assert.AreEqual(12345, test.OldItem);
|
||||
Assert.AreEqual(54321, test.NewItem);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests whether a string argument can be stored in the argument container
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void StringsCanBeCarried() {
|
||||
var test = new ItemReplaceEventArgs<string>("hello", "world");
|
||||
Assert.AreEqual("hello", test.OldItem);
|
||||
Assert.AreEqual("world", test.NewItem);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} // namespace Nuclex.Support.Collections
|
||||
|
||||
#endif // UNITTEST
|
||||
#region Apache License 2.0
|
||||
/*
|
||||
Nuclex .NET Framework
|
||||
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
#endregion // Apache License 2.0
|
||||
|
||||
#if UNITTEST
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
using NUnit.Framework;
|
||||
|
||||
namespace Nuclex.Support.Collections {
|
||||
|
||||
/// <summary>Unit Test for the item event argument container</summary>
|
||||
[TestFixture]
|
||||
internal class ItemReplaceEventArgsTest {
|
||||
|
||||
/// <summary>
|
||||
/// Tests whether an integer argument can be stored in the argument container
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void IntegersCanBeCarried() {
|
||||
var test = new ItemReplaceEventArgs<int>(12345, 54321);
|
||||
Assert.AreEqual(12345, test.OldItem);
|
||||
Assert.AreEqual(54321, test.NewItem);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests whether a string argument can be stored in the argument container
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void StringsCanBeCarried() {
|
||||
var test = new ItemReplaceEventArgs<string>("hello", "world");
|
||||
Assert.AreEqual("hello", test.OldItem);
|
||||
Assert.AreEqual("world", test.NewItem);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} // namespace Nuclex.Support.Collections
|
||||
|
||||
#endif // UNITTEST
|
||||
|
|
|
|||
|
|
@ -1,55 +1,54 @@
|
|||
#region CPL License
|
||||
/*
|
||||
Nuclex Framework
|
||||
Copyright (C) 2002-2017 Nuclex Development Labs
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the IBM Common Public License as
|
||||
published by the IBM Corporation; either version 1.0 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
IBM Common Public License for more details.
|
||||
|
||||
You should have received a copy of the IBM Common Public
|
||||
License along with this library
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
|
||||
namespace Nuclex.Support.Collections {
|
||||
|
||||
/// <summary>
|
||||
/// Argument container used by collections to notify about replaced items
|
||||
/// </summary>
|
||||
public class ItemReplaceEventArgs<TItem> : EventArgs {
|
||||
|
||||
/// <summary>Initializes a new event arguments supplier</summary>
|
||||
/// <param name="oldItem">Item that has been replaced by another item</param>
|
||||
/// <param name="newItem">Replacement item that is now part of the collection</param>
|
||||
public ItemReplaceEventArgs(TItem oldItem, TItem newItem) {
|
||||
this.oldItem = oldItem;
|
||||
this.newItem = newItem;
|
||||
}
|
||||
|
||||
/// <summary>Item that has been replaced by another item</summary>
|
||||
public TItem OldItem {
|
||||
get { return this.oldItem; }
|
||||
}
|
||||
|
||||
/// <summary>Replacement item that is now part of the collection</summary>
|
||||
public TItem NewItem {
|
||||
get { return this.newItem; }
|
||||
}
|
||||
|
||||
/// <summary>Item that was removed from the collection</summary>
|
||||
private TItem oldItem;
|
||||
/// <summary>Item that was added to the collection</summary>
|
||||
private TItem newItem;
|
||||
|
||||
}
|
||||
|
||||
} // namespace Nuclex.Support.Collections
|
||||
#region Apache License 2.0
|
||||
/*
|
||||
Nuclex .NET Framework
|
||||
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
#endregion // Apache License 2.0
|
||||
|
||||
using System;
|
||||
|
||||
namespace Nuclex.Support.Collections {
|
||||
|
||||
/// <summary>
|
||||
/// Argument container used by collections to notify about replaced items
|
||||
/// </summary>
|
||||
public class ItemReplaceEventArgs<TItem> : EventArgs {
|
||||
|
||||
/// <summary>Initializes a new event arguments supplier</summary>
|
||||
/// <param name="oldItem">Item that has been replaced by another item</param>
|
||||
/// <param name="newItem">Replacement item that is now part of the collection</param>
|
||||
public ItemReplaceEventArgs(TItem oldItem, TItem newItem) {
|
||||
this.oldItem = oldItem;
|
||||
this.newItem = newItem;
|
||||
}
|
||||
|
||||
/// <summary>Item that has been replaced by another item</summary>
|
||||
public TItem OldItem {
|
||||
get { return this.oldItem; }
|
||||
}
|
||||
|
||||
/// <summary>Replacement item that is now part of the collection</summary>
|
||||
public TItem NewItem {
|
||||
get { return this.newItem; }
|
||||
}
|
||||
|
||||
/// <summary>Item that was removed from the collection</summary>
|
||||
private TItem oldItem;
|
||||
/// <summary>Item that was added to the collection</summary>
|
||||
private TItem newItem;
|
||||
|
||||
}
|
||||
|
||||
} // namespace Nuclex.Support.Collections
|
||||
|
|
|
|||
|
|
@ -1,257 +1,256 @@
|
|||
#region CPL License
|
||||
/*
|
||||
Nuclex Framework
|
||||
Copyright (C) 2002-2017 Nuclex Development Labs
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the IBM Common Public License as
|
||||
published by the IBM Corporation; either version 1.0 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
IBM Common Public License for more details.
|
||||
|
||||
You should have received a copy of the IBM Common Public
|
||||
License along with this library
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
||||
#if UNITTEST
|
||||
|
||||
using NUnit.Framework;
|
||||
|
||||
namespace Nuclex.Support.Collections {
|
||||
|
||||
/// <summary>Unit Test for the list segment class</summary>
|
||||
[TestFixture]
|
||||
internal class ListSegmentTest {
|
||||
|
||||
/// <summary>
|
||||
/// Tests whether the default constructor of the ListSegment class throws the
|
||||
/// right exception when being passed 'null' instead of a list
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void SimpleConstructorThrowsWhenListIsNull() {
|
||||
Assert.Throws<ArgumentNullException>(
|
||||
delegate() { new ListSegment<int>(null); }
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests whether the simple constructor of the ListSegment class accepts
|
||||
/// an empty list
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void SimpleConstructorAcceptsEmptyList() {
|
||||
new ListSegment<int>(new List<int>());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests whether the full constructor of the ListSegment class throws the
|
||||
/// right exception when being passed 'null' instead of a string
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void ConstructorThrowsWhenListIsNull() {
|
||||
Assert.Throws<ArgumentNullException>(
|
||||
delegate() { new ListSegment<int>(null, 0, 0); }
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests whether the full constructor of the ListSegment class accepts
|
||||
/// an empty string
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void ConstructorAcceptsEmptyList() {
|
||||
new ListSegment<int>(new List<int>(), 0, 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests whether the full constructor of the ListSegment class throws the
|
||||
/// right exception when being passed an invalid start offset
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void ConstructorThrowsOnInvalidOffset() {
|
||||
Assert.Throws<ArgumentOutOfRangeException>(
|
||||
delegate() { new ListSegment<int>(new List<int>(), -1, 0); }
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests whether the full constructor of the ListSegment class throws the
|
||||
/// right exception when being passed an invalid element count
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void ConstructorThrowsOnInvalidCount() {
|
||||
Assert.Throws<ArgumentOutOfRangeException>(
|
||||
delegate() { new ListSegment<int>(new List<int>(), 0, -1); }
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests whether the full constructor of the ListSegment class throws the
|
||||
/// right exception when being passed a string length that's too large
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void ConstructorThrowsOnListOverrun() {
|
||||
var testList = new List<int>(capacity: 5) { 1, 2, 3, 4, 5 };
|
||||
Assert.Throws<ArgumentException>(
|
||||
delegate() { new ListSegment<int>(testList, 3, 3); }
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>Tests whether the 'Text' property works as expected</summary>
|
||||
[Test]
|
||||
public void ListPropertyStoresOriginalList() {
|
||||
var testList = new List<int>(capacity: 5) { 1, 2, 3, 4, 5 };
|
||||
ListSegment<int> testSegment = new ListSegment<int>(testList, 1, 3);
|
||||
Assert.AreSame(testList, testSegment.List);
|
||||
}
|
||||
|
||||
/// <summary>Tests whether the 'Offset' property works as expected</summary>
|
||||
[Test]
|
||||
public void OffsetPropertyIsStored() {
|
||||
var testList = new List<int>(capacity: 5) { 1, 2, 3, 4, 5 };
|
||||
ListSegment<int> testSegment = new ListSegment<int>(testList, 1, 3);
|
||||
Assert.AreEqual(1, testSegment.Offset);
|
||||
}
|
||||
|
||||
/// <summary>Tests whether the 'Count' property works as expected</summary>
|
||||
[Test]
|
||||
public void CountPropertyIsStored() {
|
||||
var testList = new List<int>(capacity: 5) { 1, 2, 3, 4, 5 };
|
||||
ListSegment<int> testSegment = new ListSegment<int>(testList, 1, 3);
|
||||
Assert.AreEqual(3, testSegment.Count);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests whether two differing instances produce different hash codes
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void DifferentInstancesHaveDifferentHashCodes_Usually() {
|
||||
var forwardCountSegment = new ListSegment<int>(
|
||||
new List<int>(capacity: 9) { 1, 2, 3, 4, 5, 6, 7, 8, 9 }, 2, 7
|
||||
);
|
||||
var reverseCountSegment = new ListSegment<int>(
|
||||
new List<int>(capacity: 9) { 9, 8, 7, 6, 5, 4, 3, 2, 1 }, 1, 8
|
||||
);
|
||||
|
||||
Assert.AreNotEqual(
|
||||
forwardCountSegment.GetHashCode(), reverseCountSegment.GetHashCode()
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests whether two equivalent instances produce an identical hash code
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void EquivalentInstancesHaveSameHashcode() {
|
||||
var testSegment = new ListSegment<int>(
|
||||
new List<int>(capacity: 9) { 1, 2, 3, 4, 5, 6, 7, 8, 9 }, 2, 7
|
||||
);
|
||||
var identicalSegment = new ListSegment<int>(
|
||||
new List<int>(capacity: 9) { 1, 2, 3, 4, 5, 6, 7, 8, 9 }, 2, 7
|
||||
);
|
||||
|
||||
Assert.AreEqual(
|
||||
testSegment.GetHashCode(), identicalSegment.GetHashCode()
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>Tests the equals method performing a comparison against null</summary>
|
||||
[Test]
|
||||
public void EqualsAgainstNullIsAlwaysFalse() {
|
||||
var testList = new List<int>(capacity: 5) { 1, 2, 3, 4, 5 };
|
||||
ListSegment<int> testSegment = new ListSegment<int>(testList, 1, 3);
|
||||
|
||||
Assert.IsFalse(
|
||||
testSegment.Equals(null)
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>Tests the equality operator with differing instances</summary>
|
||||
[Test]
|
||||
public void DifferingInstancesAreNotEqual() {
|
||||
var forwardCountSegment = new ListSegment<int>(
|
||||
new List<int>(capacity: 9) { 1, 2, 3, 4, 5, 6, 7, 8, 9 }, 2, 7
|
||||
);
|
||||
var reverseCountSegment = new ListSegment<int>(
|
||||
new List<int>(capacity: 9) { 9, 8, 7, 6, 5, 4, 3, 2, 1 }, 1, 8
|
||||
);
|
||||
|
||||
Assert.IsFalse(forwardCountSegment == reverseCountSegment);
|
||||
}
|
||||
|
||||
/// <summary>Tests the equality operator with equivalent instances</summary>
|
||||
[Test]
|
||||
public void EquivalentInstancesAreEqual() {
|
||||
var testSegment = new ListSegment<int>(
|
||||
new List<int>(capacity: 9) { 1, 2, 3, 4, 5, 6, 7, 8, 9 }, 2, 7
|
||||
);
|
||||
var identicalSegment = new ListSegment<int>(
|
||||
new List<int>(capacity: 9) { 1, 2, 3, 4, 5, 6, 7, 8, 9 }, 2, 7
|
||||
);
|
||||
|
||||
Assert.IsTrue(testSegment == identicalSegment);
|
||||
}
|
||||
|
||||
/// <summary>Tests the inequality operator with differing instances</summary>
|
||||
[Test]
|
||||
public void DifferingInstancesAreUnequal() {
|
||||
var forwardCountSegment = new ListSegment<int>(
|
||||
new List<int>(capacity: 9) { 1, 2, 3, 4, 5, 6, 7, 8, 9 }, 2, 7
|
||||
);
|
||||
var reverseCountSegment = new ListSegment<int>(
|
||||
new List<int>(capacity: 9) { 9, 8, 7, 6, 5, 4, 3, 2, 1 }, 1, 8
|
||||
);
|
||||
|
||||
Assert.IsTrue(forwardCountSegment != reverseCountSegment);
|
||||
}
|
||||
|
||||
/// <summary>Tests the inequality operator with equivalent instances</summary>
|
||||
[Test]
|
||||
public void EquivalentInstancesAreNotUnequal() {
|
||||
var testSegment = new ListSegment<int>(
|
||||
new List<int>(capacity: 9) { 1, 2, 3, 4, 5, 6, 7, 8, 9 }, 2, 7
|
||||
);
|
||||
var identicalSegment = new ListSegment<int>(
|
||||
new List<int>(capacity: 9) { 1, 2, 3, 4, 5, 6, 7, 8, 9 }, 2, 7
|
||||
);
|
||||
|
||||
Assert.IsFalse(testSegment != identicalSegment);
|
||||
}
|
||||
|
||||
/// <summary>Tests the ToString() method of the string segment</summary>
|
||||
[Test]
|
||||
public void TestToString() {
|
||||
var testList = new List<int>(capacity: 6) { 1, 2, 3, 4, 5, 6 };
|
||||
ListSegment<int> testSegment = new ListSegment<int>(testList, 2, 2);
|
||||
|
||||
string stringRepresentation = testSegment.ToString();
|
||||
StringAssert.Contains("3, 4", stringRepresentation);
|
||||
StringAssert.DoesNotContain("2", stringRepresentation);
|
||||
StringAssert.DoesNotContain("5", stringRepresentation);
|
||||
}
|
||||
|
||||
/// <summary>Tests whether the 'Text' property works as expected</summary>
|
||||
[Test]
|
||||
public void ToListReturnsSubset() {
|
||||
var testList = new List<int>(capacity: 5) { 1, 2, 3, 4, 5 };
|
||||
ListSegment<int> testSegment = new ListSegment<int>(testList, 1, 3);
|
||||
CollectionAssert.AreEqual(
|
||||
new List<int>(capacity: 3) { 2, 3, 4 },
|
||||
testSegment.ToList()
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} // namespace Nuclex.Support.Collections
|
||||
|
||||
#endif // UNITTEST
|
||||
#region Apache License 2.0
|
||||
/*
|
||||
Nuclex .NET Framework
|
||||
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
#endregion // Apache License 2.0
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
||||
#if UNITTEST
|
||||
|
||||
using NUnit.Framework;
|
||||
|
||||
namespace Nuclex.Support.Collections {
|
||||
|
||||
/// <summary>Unit Test for the list segment class</summary>
|
||||
[TestFixture]
|
||||
internal class ListSegmentTest {
|
||||
|
||||
/// <summary>
|
||||
/// Tests whether the default constructor of the ListSegment class throws the
|
||||
/// right exception when being passed 'null' instead of a list
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void SimpleConstructorThrowsWhenListIsNull() {
|
||||
Assert.Throws<ArgumentNullException>(
|
||||
delegate() { new ListSegment<int>(null); }
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests whether the simple constructor of the ListSegment class accepts
|
||||
/// an empty list
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void SimpleConstructorAcceptsEmptyList() {
|
||||
new ListSegment<int>(new List<int>());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests whether the full constructor of the ListSegment class throws the
|
||||
/// right exception when being passed 'null' instead of a string
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void ConstructorThrowsWhenListIsNull() {
|
||||
Assert.Throws<ArgumentNullException>(
|
||||
delegate() { new ListSegment<int>(null, 0, 0); }
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests whether the full constructor of the ListSegment class accepts
|
||||
/// an empty string
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void ConstructorAcceptsEmptyList() {
|
||||
new ListSegment<int>(new List<int>(), 0, 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests whether the full constructor of the ListSegment class throws the
|
||||
/// right exception when being passed an invalid start offset
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void ConstructorThrowsOnInvalidOffset() {
|
||||
Assert.Throws<ArgumentOutOfRangeException>(
|
||||
delegate() { new ListSegment<int>(new List<int>(), -1, 0); }
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests whether the full constructor of the ListSegment class throws the
|
||||
/// right exception when being passed an invalid element count
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void ConstructorThrowsOnInvalidCount() {
|
||||
Assert.Throws<ArgumentOutOfRangeException>(
|
||||
delegate() { new ListSegment<int>(new List<int>(), 0, -1); }
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests whether the full constructor of the ListSegment class throws the
|
||||
/// right exception when being passed a string length that's too large
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void ConstructorThrowsOnListOverrun() {
|
||||
var testList = new List<int>(capacity: 5) { 1, 2, 3, 4, 5 };
|
||||
Assert.Throws<ArgumentException>(
|
||||
delegate() { new ListSegment<int>(testList, 3, 3); }
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>Tests whether the 'Text' property works as expected</summary>
|
||||
[Test]
|
||||
public void ListPropertyStoresOriginalList() {
|
||||
var testList = new List<int>(capacity: 5) { 1, 2, 3, 4, 5 };
|
||||
ListSegment<int> testSegment = new ListSegment<int>(testList, 1, 3);
|
||||
Assert.AreSame(testList, testSegment.List);
|
||||
}
|
||||
|
||||
/// <summary>Tests whether the 'Offset' property works as expected</summary>
|
||||
[Test]
|
||||
public void OffsetPropertyIsStored() {
|
||||
var testList = new List<int>(capacity: 5) { 1, 2, 3, 4, 5 };
|
||||
ListSegment<int> testSegment = new ListSegment<int>(testList, 1, 3);
|
||||
Assert.AreEqual(1, testSegment.Offset);
|
||||
}
|
||||
|
||||
/// <summary>Tests whether the 'Count' property works as expected</summary>
|
||||
[Test]
|
||||
public void CountPropertyIsStored() {
|
||||
var testList = new List<int>(capacity: 5) { 1, 2, 3, 4, 5 };
|
||||
ListSegment<int> testSegment = new ListSegment<int>(testList, 1, 3);
|
||||
Assert.AreEqual(3, testSegment.Count);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests whether two differing instances produce different hash codes
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void DifferentInstancesHaveDifferentHashCodes_Usually() {
|
||||
var forwardCountSegment = new ListSegment<int>(
|
||||
new List<int>(capacity: 9) { 1, 2, 3, 4, 5, 6, 7, 8, 9 }, 2, 7
|
||||
);
|
||||
var reverseCountSegment = new ListSegment<int>(
|
||||
new List<int>(capacity: 9) { 9, 8, 7, 6, 5, 4, 3, 2, 1 }, 1, 8
|
||||
);
|
||||
|
||||
Assert.AreNotEqual(
|
||||
forwardCountSegment.GetHashCode(), reverseCountSegment.GetHashCode()
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests whether two equivalent instances produce an identical hash code
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void EquivalentInstancesHaveSameHashcode() {
|
||||
var testSegment = new ListSegment<int>(
|
||||
new List<int>(capacity: 9) { 1, 2, 3, 4, 5, 6, 7, 8, 9 }, 2, 7
|
||||
);
|
||||
var identicalSegment = new ListSegment<int>(
|
||||
new List<int>(capacity: 9) { 1, 2, 3, 4, 5, 6, 7, 8, 9 }, 2, 7
|
||||
);
|
||||
|
||||
Assert.AreEqual(
|
||||
testSegment.GetHashCode(), identicalSegment.GetHashCode()
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>Tests the equals method performing a comparison against null</summary>
|
||||
[Test]
|
||||
public void EqualsAgainstNullIsAlwaysFalse() {
|
||||
var testList = new List<int>(capacity: 5) { 1, 2, 3, 4, 5 };
|
||||
ListSegment<int> testSegment = new ListSegment<int>(testList, 1, 3);
|
||||
|
||||
Assert.IsFalse(
|
||||
testSegment.Equals(null)
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>Tests the equality operator with differing instances</summary>
|
||||
[Test]
|
||||
public void DifferingInstancesAreNotEqual() {
|
||||
var forwardCountSegment = new ListSegment<int>(
|
||||
new List<int>(capacity: 9) { 1, 2, 3, 4, 5, 6, 7, 8, 9 }, 2, 7
|
||||
);
|
||||
var reverseCountSegment = new ListSegment<int>(
|
||||
new List<int>(capacity: 9) { 9, 8, 7, 6, 5, 4, 3, 2, 1 }, 1, 8
|
||||
);
|
||||
|
||||
Assert.IsFalse(forwardCountSegment == reverseCountSegment);
|
||||
}
|
||||
|
||||
/// <summary>Tests the equality operator with equivalent instances</summary>
|
||||
[Test]
|
||||
public void EquivalentInstancesAreEqual() {
|
||||
var testSegment = new ListSegment<int>(
|
||||
new List<int>(capacity: 9) { 1, 2, 3, 4, 5, 6, 7, 8, 9 }, 2, 7
|
||||
);
|
||||
var identicalSegment = new ListSegment<int>(
|
||||
new List<int>(capacity: 9) { 1, 2, 3, 4, 5, 6, 7, 8, 9 }, 2, 7
|
||||
);
|
||||
|
||||
Assert.IsTrue(testSegment == identicalSegment);
|
||||
}
|
||||
|
||||
/// <summary>Tests the inequality operator with differing instances</summary>
|
||||
[Test]
|
||||
public void DifferingInstancesAreUnequal() {
|
||||
var forwardCountSegment = new ListSegment<int>(
|
||||
new List<int>(capacity: 9) { 1, 2, 3, 4, 5, 6, 7, 8, 9 }, 2, 7
|
||||
);
|
||||
var reverseCountSegment = new ListSegment<int>(
|
||||
new List<int>(capacity: 9) { 9, 8, 7, 6, 5, 4, 3, 2, 1 }, 1, 8
|
||||
);
|
||||
|
||||
Assert.IsTrue(forwardCountSegment != reverseCountSegment);
|
||||
}
|
||||
|
||||
/// <summary>Tests the inequality operator with equivalent instances</summary>
|
||||
[Test]
|
||||
public void EquivalentInstancesAreNotUnequal() {
|
||||
var testSegment = new ListSegment<int>(
|
||||
new List<int>(capacity: 9) { 1, 2, 3, 4, 5, 6, 7, 8, 9 }, 2, 7
|
||||
);
|
||||
var identicalSegment = new ListSegment<int>(
|
||||
new List<int>(capacity: 9) { 1, 2, 3, 4, 5, 6, 7, 8, 9 }, 2, 7
|
||||
);
|
||||
|
||||
Assert.IsFalse(testSegment != identicalSegment);
|
||||
}
|
||||
|
||||
/// <summary>Tests the ToString() method of the string segment</summary>
|
||||
[Test]
|
||||
public void TestToString() {
|
||||
var testList = new List<int>(capacity: 6) { 1, 2, 3, 4, 5, 6 };
|
||||
ListSegment<int> testSegment = new ListSegment<int>(testList, 2, 2);
|
||||
|
||||
string stringRepresentation = testSegment.ToString();
|
||||
StringAssert.Contains("3, 4", stringRepresentation);
|
||||
StringAssert.DoesNotContain("2", stringRepresentation);
|
||||
StringAssert.DoesNotContain("5", stringRepresentation);
|
||||
}
|
||||
|
||||
/// <summary>Tests whether the 'Text' property works as expected</summary>
|
||||
[Test]
|
||||
public void ToListReturnsSubset() {
|
||||
var testList = new List<int>(capacity: 5) { 1, 2, 3, 4, 5 };
|
||||
ListSegment<int> testSegment = new ListSegment<int>(testList, 1, 3);
|
||||
CollectionAssert.AreEqual(
|
||||
new List<int>(capacity: 3) { 2, 3, 4 },
|
||||
testSegment.ToList()
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} // namespace Nuclex.Support.Collections
|
||||
|
||||
#endif // UNITTEST
|
||||
|
|
|
|||
|
|
@ -1,281 +1,280 @@
|
|||
#region CPL License
|
||||
/*
|
||||
Nuclex Framework
|
||||
Copyright (C) 2002-2017 Nuclex Development Labs
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the IBM Common Public License as
|
||||
published by the IBM Corporation; either version 1.0 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
IBM Common Public License for more details.
|
||||
|
||||
You should have received a copy of the IBM Common Public
|
||||
License along with this library
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Nuclex.Support.Collections {
|
||||
|
||||
/// <summary>View into a section of an IList<T> without copying said string</summary>
|
||||
/// <typeparam name="TElement">
|
||||
/// Type of elements that are stored in the list the segment references
|
||||
/// </typeparam>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// The design of this class pretty much mirrors that of the
|
||||
/// <see cref="T:System.ArraySegment" /> class found in the .NET framework, but is
|
||||
/// specialized to be used for IList<T>, which can not be cast to arrays
|
||||
/// directly (and <see cref="M:System.ArrayList.Adapter" /> loses type safety).
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// In certain situations, passing a ListSegment instead of storing the selected
|
||||
/// elements in a new list is useful. For example, the caller might want to know
|
||||
/// from which index of the original list the section was taken. When the original
|
||||
/// list needs to be modified, for example in a sorting algorithm, the list segment
|
||||
/// can be used to specify a region for the algorithm to work on while still accessing
|
||||
/// the original list.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
#if !NO_SERIALIZATION
|
||||
[Serializable, StructLayout(LayoutKind.Sequential)]
|
||||
#endif
|
||||
public struct ListSegment<TElement> {
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ListSegment<TElement>" /> class
|
||||
/// that delimits all the elements in the specified string
|
||||
/// </summary>
|
||||
/// <param name="list">List that will be wrapped</param>
|
||||
/// <exception cref="System.ArgumentNullException">String is null</exception>
|
||||
public ListSegment(IList<TElement> list) {
|
||||
if(list == null) { // questionable, but matches behavior of ArraySegment class
|
||||
throw new ArgumentNullException("text", "Text must not be null");
|
||||
}
|
||||
|
||||
this.list = list;
|
||||
this.offset = 0;
|
||||
this.count = list.Count;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ListSegment<TElement>" /> class
|
||||
/// that delimits the specified range of the elements in the specified string
|
||||
/// </summary>
|
||||
/// <param name="list">The list containing the range of elements to delimit</param>
|
||||
/// <param name="offset">The zero-based index of the first element in the range</param>
|
||||
/// <param name="count">The number of elements in the range</param>
|
||||
/// <exception cref="System.ArgumentOutOfRangeException">
|
||||
/// Offset or count is less than 0
|
||||
/// </exception>
|
||||
/// <exception cref="System.ArgumentException">
|
||||
/// Offset and count do not specify a valid range in array
|
||||
/// </exception>
|
||||
/// <exception cref="System.ArgumentNullException">String is null</exception>
|
||||
public ListSegment(IList<TElement> list, int offset, int count) {
|
||||
if(list == null) { // questionable, but matches behavior of ArraySegment class
|
||||
throw new ArgumentNullException("list");
|
||||
}
|
||||
if(offset < 0) {
|
||||
throw new ArgumentOutOfRangeException(
|
||||
"offset", "Argument out of range, non-negative number required"
|
||||
);
|
||||
}
|
||||
if(count < 0) {
|
||||
throw new ArgumentOutOfRangeException(
|
||||
"count", "Argument out of range, non-negative number required"
|
||||
);
|
||||
}
|
||||
if(count > (list.Count - offset)) {
|
||||
throw new ArgumentException(
|
||||
"Invalid argument, specified offset and count exceed list size"
|
||||
);
|
||||
}
|
||||
|
||||
this.list = list;
|
||||
this.offset = offset;
|
||||
this.count = count;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the original list containing the range of elements that the list
|
||||
/// segment delimits
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// The original list that was passed to the constructor, and that contains the range
|
||||
/// delimited by the <see cref="ListSegment<TElement>" />
|
||||
/// </returns>
|
||||
public IList<TElement> List {
|
||||
get { return this.list; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the position of the first element in the range delimited by the list segment,
|
||||
/// relative to the start of the original list
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// The position of the first element in the range delimited by the
|
||||
/// <see cref="ListSegment<TElement>" />, relative to the start of the original list
|
||||
/// </returns>
|
||||
public int Offset {
|
||||
get { return this.offset; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of elements in the range delimited by the list segment
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// The number of elements in the range delimited by
|
||||
/// the <see cref="ListSegment<TElement>" />
|
||||
/// </returns>
|
||||
public int Count {
|
||||
get { return this.count; }
|
||||
}
|
||||
|
||||
/// <summary>Returns the hash code for the current instance</summary>
|
||||
/// <returns>A 32-bit signed integer hash code</returns>
|
||||
public override int GetHashCode() {
|
||||
int hashCode = this.offset ^ this.count;
|
||||
for(int index = 0; index < this.count; ++index) {
|
||||
hashCode ^= this.list[index + this.offset].GetHashCode();
|
||||
}
|
||||
return hashCode;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified object is equal to the current instance
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// True if the specified object is a <see cref="ListSegment<TElement>" /> structure
|
||||
/// and is equal to the current instance; otherwise, false
|
||||
/// </returns>
|
||||
/// <param name="other">The object to be compared with the current instance</param>
|
||||
public override bool Equals(object other) {
|
||||
return
|
||||
(other is ListSegment<TElement>) &&
|
||||
this.Equals((ListSegment<TElement>)other);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified <see cref="ListSegment<TElement>" />
|
||||
/// structure is equal to the current instance
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// True if the specified <see cref="ListSegment<TElement>" /> structure is equal
|
||||
/// to the current instance; otherwise, false
|
||||
/// </returns>
|
||||
/// <param name="other">
|
||||
/// The <see cref="ListSegment<TElement>" /> structure to be compared with
|
||||
/// the current instance
|
||||
/// </param>
|
||||
public bool Equals(ListSegment<TElement> other) {
|
||||
if(other.count != this.count) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if(ReferenceEquals(other.list, this.list)) {
|
||||
return (other.offset == this.offset);
|
||||
} else {
|
||||
var comparer = Comparer<TElement>.Default;
|
||||
for(int index = 0; index < this.count; ++index) {
|
||||
int difference = comparer.Compare(
|
||||
other.list[index + other.offset], this.list[index + this.offset]
|
||||
);
|
||||
if(difference != 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Indicates whether two <see cref="ListSegment<TElement>" /> structures are equal
|
||||
/// </summary>
|
||||
/// <returns>True if a is equal to b; otherwise, false</returns>
|
||||
/// <param name="left">
|
||||
/// The <see cref="ListSegment<TElement>" /> structure on the left side of
|
||||
/// the equality operator
|
||||
/// </param>
|
||||
/// <param name="right">
|
||||
/// The <see cref="ListSegment<TElement>" /> structure on the right side of
|
||||
/// the equality operator
|
||||
/// </param>
|
||||
public static bool operator ==(ListSegment<TElement> left, ListSegment<TElement> right) {
|
||||
return left.Equals(right);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Indicates whether two <see cref="ListSegment<TElement>" /> structures are unequal
|
||||
/// </summary>
|
||||
/// <returns>True if a is not equal to b; otherwise, false</returns>
|
||||
/// <param name="left">
|
||||
/// The <see cref="ListSegment<TElement>" /> structure on the left side of
|
||||
/// the inequality operator
|
||||
/// </param>
|
||||
/// <param name="right">
|
||||
/// The <see cref="ListSegment<TElement>" /> structure on the right side of
|
||||
/// the inequality operator
|
||||
/// </param>
|
||||
public static bool operator !=(ListSegment<TElement> left, ListSegment<TElement> right) {
|
||||
return !(left == right);
|
||||
}
|
||||
|
||||
/// <summary>Returns a string representation of the list segment</summary>
|
||||
/// <returns>The string representation of the list segment</returns>
|
||||
public override string ToString() {
|
||||
var builder = new System.Text.StringBuilder();
|
||||
builder.Append("ListSegment {");
|
||||
for(int index = 0; index < Math.Min(this.count, 10); ++index) {
|
||||
if(index == 0) {
|
||||
builder.Append(" ");
|
||||
} else {
|
||||
builder.Append(", ");
|
||||
}
|
||||
builder.Append(this.list[index + this.offset].ToString());
|
||||
}
|
||||
|
||||
if(this.count >= 11) {
|
||||
builder.Append(", ... }");
|
||||
} else {
|
||||
builder.Append(" }");
|
||||
}
|
||||
|
||||
return builder.ToString();
|
||||
}
|
||||
|
||||
/// <summary>Returns a new list containing only the elements in the list segment</summary>
|
||||
/// <returns>A new list containing only the elements in the list segment</returns>
|
||||
public List<TElement> ToList() {
|
||||
if(this.count == 0) {
|
||||
return new List<TElement>(capacity: 0);
|
||||
} else {
|
||||
var newList = new List<TElement>(capacity: this.count);
|
||||
{
|
||||
int endIndex = this.offset + this.count;
|
||||
for(int index = this.offset; index < endIndex; ++index) {
|
||||
newList.Add(this.list[index]);
|
||||
}
|
||||
}
|
||||
|
||||
return newList;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>List wrapped by the list segment</summary>
|
||||
private IList<TElement> list;
|
||||
/// <summary>Offset in the original list the segment begins at</summary>
|
||||
private int offset;
|
||||
/// <summary>Number of elements in the segment</summary>
|
||||
private int count;
|
||||
|
||||
}
|
||||
|
||||
} // namespace Nuclex.Support.Collections
|
||||
#region Apache License 2.0
|
||||
/*
|
||||
Nuclex .NET Framework
|
||||
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
#endregion // Apache License 2.0
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Nuclex.Support.Collections {
|
||||
|
||||
/// <summary>View into a section of an IList<T> without copying said string</summary>
|
||||
/// <typeparam name="TElement">
|
||||
/// Type of elements that are stored in the list the segment references
|
||||
/// </typeparam>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// The design of this class pretty much mirrors that of the
|
||||
/// <see cref="T:System.ArraySegment" /> class found in the .NET framework, but is
|
||||
/// specialized to be used for IList<T>, which can not be cast to arrays
|
||||
/// directly (and <see cref="M:System.ArrayList.Adapter" /> loses type safety).
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// In certain situations, passing a ListSegment instead of storing the selected
|
||||
/// elements in a new list is useful. For example, the caller might want to know
|
||||
/// from which index of the original list the section was taken. When the original
|
||||
/// list needs to be modified, for example in a sorting algorithm, the list segment
|
||||
/// can be used to specify a region for the algorithm to work on while still accessing
|
||||
/// the original list.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
#if !NO_SERIALIZATION
|
||||
[Serializable, StructLayout(LayoutKind.Sequential)]
|
||||
#endif
|
||||
public struct ListSegment<TElement> {
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ListSegment<TElement>" /> class
|
||||
/// that delimits all the elements in the specified string
|
||||
/// </summary>
|
||||
/// <param name="list">List that will be wrapped</param>
|
||||
/// <exception cref="System.ArgumentNullException">String is null</exception>
|
||||
public ListSegment(IList<TElement> list) {
|
||||
if(list == null) { // questionable, but matches behavior of ArraySegment class
|
||||
throw new ArgumentNullException("text", "Text must not be null");
|
||||
}
|
||||
|
||||
this.list = list;
|
||||
this.offset = 0;
|
||||
this.count = list.Count;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ListSegment<TElement>" /> class
|
||||
/// that delimits the specified range of the elements in the specified string
|
||||
/// </summary>
|
||||
/// <param name="list">The list containing the range of elements to delimit</param>
|
||||
/// <param name="offset">The zero-based index of the first element in the range</param>
|
||||
/// <param name="count">The number of elements in the range</param>
|
||||
/// <exception cref="System.ArgumentOutOfRangeException">
|
||||
/// Offset or count is less than 0
|
||||
/// </exception>
|
||||
/// <exception cref="System.ArgumentException">
|
||||
/// Offset and count do not specify a valid range in array
|
||||
/// </exception>
|
||||
/// <exception cref="System.ArgumentNullException">String is null</exception>
|
||||
public ListSegment(IList<TElement> list, int offset, int count) {
|
||||
if(list == null) { // questionable, but matches behavior of ArraySegment class
|
||||
throw new ArgumentNullException("list");
|
||||
}
|
||||
if(offset < 0) {
|
||||
throw new ArgumentOutOfRangeException(
|
||||
"offset", "Argument out of range, non-negative number required"
|
||||
);
|
||||
}
|
||||
if(count < 0) {
|
||||
throw new ArgumentOutOfRangeException(
|
||||
"count", "Argument out of range, non-negative number required"
|
||||
);
|
||||
}
|
||||
if(count > (list.Count - offset)) {
|
||||
throw new ArgumentException(
|
||||
"Invalid argument, specified offset and count exceed list size"
|
||||
);
|
||||
}
|
||||
|
||||
this.list = list;
|
||||
this.offset = offset;
|
||||
this.count = count;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the original list containing the range of elements that the list
|
||||
/// segment delimits
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// The original list that was passed to the constructor, and that contains the range
|
||||
/// delimited by the <see cref="ListSegment<TElement>" />
|
||||
/// </returns>
|
||||
public IList<TElement> List {
|
||||
get { return this.list; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the position of the first element in the range delimited by the list segment,
|
||||
/// relative to the start of the original list
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// The position of the first element in the range delimited by the
|
||||
/// <see cref="ListSegment<TElement>" />, relative to the start of the original list
|
||||
/// </returns>
|
||||
public int Offset {
|
||||
get { return this.offset; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of elements in the range delimited by the list segment
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// The number of elements in the range delimited by
|
||||
/// the <see cref="ListSegment<TElement>" />
|
||||
/// </returns>
|
||||
public int Count {
|
||||
get { return this.count; }
|
||||
}
|
||||
|
||||
/// <summary>Returns the hash code for the current instance</summary>
|
||||
/// <returns>A 32-bit signed integer hash code</returns>
|
||||
public override int GetHashCode() {
|
||||
int hashCode = this.offset ^ this.count;
|
||||
for(int index = 0; index < this.count; ++index) {
|
||||
hashCode ^= this.list[index + this.offset].GetHashCode();
|
||||
}
|
||||
return hashCode;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified object is equal to the current instance
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// True if the specified object is a <see cref="ListSegment<TElement>" /> structure
|
||||
/// and is equal to the current instance; otherwise, false
|
||||
/// </returns>
|
||||
/// <param name="other">The object to be compared with the current instance</param>
|
||||
public override bool Equals(object other) {
|
||||
return
|
||||
(other is ListSegment<TElement>) &&
|
||||
this.Equals((ListSegment<TElement>)other);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified <see cref="ListSegment<TElement>" />
|
||||
/// structure is equal to the current instance
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// True if the specified <see cref="ListSegment<TElement>" /> structure is equal
|
||||
/// to the current instance; otherwise, false
|
||||
/// </returns>
|
||||
/// <param name="other">
|
||||
/// The <see cref="ListSegment<TElement>" /> structure to be compared with
|
||||
/// the current instance
|
||||
/// </param>
|
||||
public bool Equals(ListSegment<TElement> other) {
|
||||
if(other.count != this.count) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if(ReferenceEquals(other.list, this.list)) {
|
||||
return (other.offset == this.offset);
|
||||
} else {
|
||||
var comparer = Comparer<TElement>.Default;
|
||||
for(int index = 0; index < this.count; ++index) {
|
||||
int difference = comparer.Compare(
|
||||
other.list[index + other.offset], this.list[index + this.offset]
|
||||
);
|
||||
if(difference != 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Indicates whether two <see cref="ListSegment<TElement>" /> structures are equal
|
||||
/// </summary>
|
||||
/// <returns>True if a is equal to b; otherwise, false</returns>
|
||||
/// <param name="left">
|
||||
/// The <see cref="ListSegment<TElement>" /> structure on the left side of
|
||||
/// the equality operator
|
||||
/// </param>
|
||||
/// <param name="right">
|
||||
/// The <see cref="ListSegment<TElement>" /> structure on the right side of
|
||||
/// the equality operator
|
||||
/// </param>
|
||||
public static bool operator ==(ListSegment<TElement> left, ListSegment<TElement> right) {
|
||||
return left.Equals(right);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Indicates whether two <see cref="ListSegment<TElement>" /> structures are unequal
|
||||
/// </summary>
|
||||
/// <returns>True if a is not equal to b; otherwise, false</returns>
|
||||
/// <param name="left">
|
||||
/// The <see cref="ListSegment<TElement>" /> structure on the left side of
|
||||
/// the inequality operator
|
||||
/// </param>
|
||||
/// <param name="right">
|
||||
/// The <see cref="ListSegment<TElement>" /> structure on the right side of
|
||||
/// the inequality operator
|
||||
/// </param>
|
||||
public static bool operator !=(ListSegment<TElement> left, ListSegment<TElement> right) {
|
||||
return !(left == right);
|
||||
}
|
||||
|
||||
/// <summary>Returns a string representation of the list segment</summary>
|
||||
/// <returns>The string representation of the list segment</returns>
|
||||
public override string ToString() {
|
||||
var builder = new System.Text.StringBuilder();
|
||||
builder.Append("ListSegment {");
|
||||
for(int index = 0; index < Math.Min(this.count, 10); ++index) {
|
||||
if(index == 0) {
|
||||
builder.Append(" ");
|
||||
} else {
|
||||
builder.Append(", ");
|
||||
}
|
||||
builder.Append(this.list[index + this.offset].ToString());
|
||||
}
|
||||
|
||||
if(this.count >= 11) {
|
||||
builder.Append(", ... }");
|
||||
} else {
|
||||
builder.Append(" }");
|
||||
}
|
||||
|
||||
return builder.ToString();
|
||||
}
|
||||
|
||||
/// <summary>Returns a new list containing only the elements in the list segment</summary>
|
||||
/// <returns>A new list containing only the elements in the list segment</returns>
|
||||
public List<TElement> ToList() {
|
||||
if(this.count == 0) {
|
||||
return new List<TElement>(capacity: 0);
|
||||
} else {
|
||||
var newList = new List<TElement>(capacity: this.count);
|
||||
{
|
||||
int endIndex = this.offset + this.count;
|
||||
for(int index = this.offset; index < endIndex; ++index) {
|
||||
newList.Add(this.list[index]);
|
||||
}
|
||||
}
|
||||
|
||||
return newList;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>List wrapped by the list segment</summary>
|
||||
private IList<TElement> list;
|
||||
/// <summary>Offset in the original list the segment begins at</summary>
|
||||
private int offset;
|
||||
/// <summary>Number of elements in the segment</summary>
|
||||
private int count;
|
||||
|
||||
}
|
||||
|
||||
} // namespace Nuclex.Support.Collections
|
||||
|
|
|
|||
|
|
@ -1,273 +1,272 @@
|
|||
#region CPL License
|
||||
/*
|
||||
Nuclex Framework
|
||||
Copyright (C) 2002-2017 Nuclex Development Labs
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the IBM Common Public License as
|
||||
published by the IBM Corporation; either version 1.0 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
IBM Common Public License for more details.
|
||||
|
||||
You should have received a copy of the IBM Common Public
|
||||
License along with this library
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Nuclex.Support.Collections {
|
||||
|
||||
partial class MultiDictionary<TKey, TValue> {
|
||||
|
||||
#region IEnumerable implementation
|
||||
|
||||
/// <summary>Returns a new object enumerator for the Dictionary</summary>
|
||||
/// <returns>The new object enumerator</returns>
|
||||
IEnumerator IEnumerable.GetEnumerator() {
|
||||
return GetEnumerator();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IDictionary implementation
|
||||
|
||||
/// <summary>Adds an item into the dictionary</summary>
|
||||
/// <param name="key">Key under which the item will be added</param>
|
||||
/// <param name="value">Item that will be added</param>
|
||||
void IDictionary.Add(object key, object value) {
|
||||
Add((TKey)key, (TValue)value);
|
||||
}
|
||||
|
||||
/// <summary>Determines whether the specified key exists in the dictionary</summary>
|
||||
/// <param name="key">Key that will be checked for</param>
|
||||
/// <returns>True if an item with the specified key exists in the dictionary</returns>
|
||||
bool IDictionary.Contains(object key) {
|
||||
return this.objectDictionary.Contains(key);
|
||||
}
|
||||
|
||||
/// <summary>Whether the size of the dictionary is fixed</summary>
|
||||
bool IDictionary.IsFixedSize {
|
||||
get { return this.objectDictionary.IsFixedSize; }
|
||||
}
|
||||
|
||||
/// <summary>Returns a collection of all keys in the dictionary</summary>
|
||||
ICollection IDictionary.Keys {
|
||||
get { return this.objectDictionary.Keys; }
|
||||
}
|
||||
|
||||
/// <summary>Returns a collection of all values stored in the dictionary</summary>
|
||||
ICollection IDictionary.Values {
|
||||
get {
|
||||
if(this.valueCollection == null) {
|
||||
this.valueCollection = new ValueCollection(this);
|
||||
}
|
||||
|
||||
return this.valueCollection;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Removes an item from the dictionary</summary>
|
||||
/// <param name="key">Key of the item that will be removed</param>
|
||||
void IDictionary.Remove(object key) {
|
||||
RemoveKey((TKey)key);
|
||||
}
|
||||
|
||||
/// <summary>Accesses an item in the dictionary by its key</summary>
|
||||
/// <param name="key">Key of the item that will be accessed</param>
|
||||
/// <returns>The item with the specified key</returns>
|
||||
object IDictionary.this[object key] {
|
||||
get { return this.objectDictionary[key]; }
|
||||
set { this[(TKey)key] = (ICollection<TValue>)value; }
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IDictionaryEnumerator implementation
|
||||
|
||||
/// <summary>Returns a new entry enumerator for the dictionary</summary>
|
||||
/// <returns>The new entry enumerator</returns>
|
||||
IDictionaryEnumerator IDictionary.GetEnumerator() {
|
||||
return new Enumerator(this);
|
||||
}
|
||||
|
||||
#endregion // IDictionaryEnumerator implementation
|
||||
|
||||
#region ICollection<KeyValuePair<TKey, TValue>> implementation
|
||||
|
||||
/// <summary>Inserts an already prepared element into the dictionary</summary>
|
||||
/// <param name="item">Prepared element that will be added to the dictionary</param>
|
||||
void ICollection<KeyValuePair<TKey, TValue>>.Add(
|
||||
KeyValuePair<TKey, TValue> item
|
||||
) {
|
||||
Add(item.Key, item.Value);
|
||||
}
|
||||
|
||||
/// <summary>Removes all items from the dictionary</summary>
|
||||
/// <param name="itemToRemove">Item that will be removed from the dictionary</param>
|
||||
bool ICollection<KeyValuePair<TKey, TValue>>.Remove(
|
||||
KeyValuePair<TKey, TValue> itemToRemove
|
||||
) {
|
||||
ICollection<TValue> values;
|
||||
if(!this.typedDictionary.TryGetValue(itemToRemove.Key, out values)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if(values.Remove(itemToRemove.Value)) {
|
||||
if(values.Count == 0) {
|
||||
this.typedDictionary.Remove(itemToRemove.Key);
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region ICollection implementation
|
||||
|
||||
/// <summary>Copies the contents of the Dictionary into an array</summary>
|
||||
/// <param name="array">Array the Dictionary contents will be copied into</param>
|
||||
/// <param name="arrayIndex">
|
||||
/// Starting index at which to begin filling the destination array
|
||||
/// </param>
|
||||
void ICollection.CopyTo(Array array, int arrayIndex) {
|
||||
foreach(KeyValuePair<TKey, ICollection<TValue>> item in this.typedDictionary) {
|
||||
foreach(TValue value in item.Value) {
|
||||
array.SetValue(new KeyValuePair<TKey, TValue>(item.Key, value), arrayIndex);
|
||||
++arrayIndex;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Whether the Dictionary is synchronized for multi-threaded usage</summary>
|
||||
bool ICollection.IsSynchronized {
|
||||
get { return this.objectDictionary.IsSynchronized; }
|
||||
}
|
||||
|
||||
/// <summary>Synchronization root on which the Dictionary locks</summary>
|
||||
object ICollection.SyncRoot {
|
||||
get { return this.objectDictionary.SyncRoot; }
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IDictionary<TKey, ICollection<TValue>> implementation
|
||||
|
||||
/// <summary>Adds a series of values to a dictionary</summary>
|
||||
/// <param name="key">Key under which the values will be added</param>
|
||||
/// <param name="values">Values that will be added to the dictionary</param>
|
||||
void IDictionary<TKey, ICollection<TValue>>.Add(TKey key, ICollection<TValue> values) {
|
||||
ICollection<TValue> currentValues;
|
||||
if(!this.typedDictionary.TryGetValue(key, out currentValues)) {
|
||||
currentValues = new ValueList(this);
|
||||
}
|
||||
|
||||
foreach(TValue value in values) {
|
||||
currentValues.Add(value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Removes all values with the specified key</summary>
|
||||
/// <param name="key">Key whose associated entries will be removed</param>
|
||||
/// <returns>True if at least one entry has been removed from the dictionary</returns>
|
||||
bool IDictionary<TKey, ICollection<TValue>>.Remove(TKey key) {
|
||||
return (RemoveKey(key) > 0);
|
||||
}
|
||||
|
||||
/// <summary>Returns a collection of value collections</summary>
|
||||
ICollection<ICollection<TValue>> IDictionary<TKey, ICollection<TValue>>.Values {
|
||||
get { return this.typedDictionary.Values; }
|
||||
}
|
||||
|
||||
#endregion // IDictionary<TKey, ICollection<TValue>> implementation
|
||||
|
||||
#region ICollection<KeyValuePair<TKey, ICollection<TValue>>> implementation
|
||||
|
||||
/// <summary>Adds a series of values to a dictionary</summary>
|
||||
/// <param name="item">Entry containing the values that will be added</param>
|
||||
void ICollection<KeyValuePair<TKey, ICollection<TValue>>>.Add(
|
||||
KeyValuePair<TKey, ICollection<TValue>> item
|
||||
) {
|
||||
ICollection<TValue> currentValues;
|
||||
if(!this.typedDictionary.TryGetValue(item.Key, out currentValues)) {
|
||||
currentValues = new ValueList(this);
|
||||
}
|
||||
|
||||
foreach(TValue value in item.Value) {
|
||||
currentValues.Add(value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether the dictionary contains the specified key/value pair
|
||||
/// </summary>
|
||||
/// <param name="item">Key/value pair for which the dictionary will be checked</param>
|
||||
/// <returns>True if the dictionary contains the specified key/value pair</returns>
|
||||
bool ICollection<KeyValuePair<TKey, ICollection<TValue>>>.Contains(
|
||||
KeyValuePair<TKey, ICollection<TValue>> item
|
||||
) {
|
||||
return this.typedDictionary.Contains(item);
|
||||
}
|
||||
|
||||
/// <summary>Copies the contents of the dictionary into an array</summary>
|
||||
/// <param name="array"></param>
|
||||
/// <param name="arrayIndex"></param>
|
||||
void ICollection<KeyValuePair<TKey, ICollection<TValue>>>.CopyTo(
|
||||
KeyValuePair<TKey, ICollection<TValue>>[] array, int arrayIndex
|
||||
) {
|
||||
this.typedDictionary.CopyTo(array, arrayIndex);
|
||||
}
|
||||
|
||||
/// <summary>Removes the specified key/value pair from the dictionary</summary>
|
||||
/// <param name="item">Key/value pair that will be removed</param>
|
||||
/// <returns>True if the key/value pair was contained in the dictionary</returns>
|
||||
bool ICollection<KeyValuePair<TKey, ICollection<TValue>>>.Remove(
|
||||
KeyValuePair<TKey, ICollection<TValue>> item
|
||||
) {
|
||||
return this.typedDictionary.Remove(item);
|
||||
}
|
||||
|
||||
/// <summary>Returns an enumerator for the dictionary</summary>
|
||||
/// <returns>An enumerator for the key/value pairs in the dictionary</returns>
|
||||
IEnumerator<KeyValuePair<TKey, ICollection<TValue>>> IEnumerable<
|
||||
KeyValuePair<TKey, ICollection<TValue>>
|
||||
>.GetEnumerator() {
|
||||
return this.typedDictionary.GetEnumerator();
|
||||
}
|
||||
|
||||
/// <summary>Number of unique keys in the dictionary</summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// This Count property returns a different value from the main interface of
|
||||
/// the multi dictionary to stay consistent with the implemented interfaces.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// If you cast a multi dictionary to a collection of collections, the count
|
||||
/// property of the outer collection should, of course, be the number of inner
|
||||
/// collections it contains (and not the sum of the items contained in all of
|
||||
/// the inner collections).
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// If you use the count property in the main interface of the multi dictionary,
|
||||
/// the value collections are hidden (it behaves as if the key was in the
|
||||
/// dictionary multiple times), so now the sum of all key-value pairs should
|
||||
/// be returned.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
int ICollection<KeyValuePair<TKey, ICollection<TValue>>>.Count {
|
||||
get { return this.typedDictionary.Count; }
|
||||
}
|
||||
|
||||
#endregion // ICollection<KeyValuePair<TKey, ICollection<TValue>>> implementation
|
||||
|
||||
}
|
||||
|
||||
} // namespace Nuclex.Support.Collections
|
||||
#region Apache License 2.0
|
||||
/*
|
||||
Nuclex .NET Framework
|
||||
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
#endregion // Apache License 2.0
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Nuclex.Support.Collections {
|
||||
|
||||
partial class MultiDictionary<TKey, TValue> {
|
||||
|
||||
#region IEnumerable implementation
|
||||
|
||||
/// <summary>Returns a new object enumerator for the Dictionary</summary>
|
||||
/// <returns>The new object enumerator</returns>
|
||||
IEnumerator IEnumerable.GetEnumerator() {
|
||||
return GetEnumerator();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IDictionary implementation
|
||||
|
||||
/// <summary>Adds an item into the dictionary</summary>
|
||||
/// <param name="key">Key under which the item will be added</param>
|
||||
/// <param name="value">Item that will be added</param>
|
||||
void IDictionary.Add(object key, object value) {
|
||||
Add((TKey)key, (TValue)value);
|
||||
}
|
||||
|
||||
/// <summary>Determines whether the specified key exists in the dictionary</summary>
|
||||
/// <param name="key">Key that will be checked for</param>
|
||||
/// <returns>True if an item with the specified key exists in the dictionary</returns>
|
||||
bool IDictionary.Contains(object key) {
|
||||
return this.objectDictionary.Contains(key);
|
||||
}
|
||||
|
||||
/// <summary>Whether the size of the dictionary is fixed</summary>
|
||||
bool IDictionary.IsFixedSize {
|
||||
get { return this.objectDictionary.IsFixedSize; }
|
||||
}
|
||||
|
||||
/// <summary>Returns a collection of all keys in the dictionary</summary>
|
||||
ICollection IDictionary.Keys {
|
||||
get { return this.objectDictionary.Keys; }
|
||||
}
|
||||
|
||||
/// <summary>Returns a collection of all values stored in the dictionary</summary>
|
||||
ICollection IDictionary.Values {
|
||||
get {
|
||||
if(this.valueCollection == null) {
|
||||
this.valueCollection = new ValueCollection(this);
|
||||
}
|
||||
|
||||
return this.valueCollection;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Removes an item from the dictionary</summary>
|
||||
/// <param name="key">Key of the item that will be removed</param>
|
||||
void IDictionary.Remove(object key) {
|
||||
RemoveKey((TKey)key);
|
||||
}
|
||||
|
||||
/// <summary>Accesses an item in the dictionary by its key</summary>
|
||||
/// <param name="key">Key of the item that will be accessed</param>
|
||||
/// <returns>The item with the specified key</returns>
|
||||
object IDictionary.this[object key] {
|
||||
get { return this.objectDictionary[key]; }
|
||||
set { this[(TKey)key] = (ICollection<TValue>)value; }
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IDictionaryEnumerator implementation
|
||||
|
||||
/// <summary>Returns a new entry enumerator for the dictionary</summary>
|
||||
/// <returns>The new entry enumerator</returns>
|
||||
IDictionaryEnumerator IDictionary.GetEnumerator() {
|
||||
return new Enumerator(this);
|
||||
}
|
||||
|
||||
#endregion // IDictionaryEnumerator implementation
|
||||
|
||||
#region ICollection<KeyValuePair<TKey, TValue>> implementation
|
||||
|
||||
/// <summary>Inserts an already prepared element into the dictionary</summary>
|
||||
/// <param name="item">Prepared element that will be added to the dictionary</param>
|
||||
void ICollection<KeyValuePair<TKey, TValue>>.Add(
|
||||
KeyValuePair<TKey, TValue> item
|
||||
) {
|
||||
Add(item.Key, item.Value);
|
||||
}
|
||||
|
||||
/// <summary>Removes all items from the dictionary</summary>
|
||||
/// <param name="itemToRemove">Item that will be removed from the dictionary</param>
|
||||
bool ICollection<KeyValuePair<TKey, TValue>>.Remove(
|
||||
KeyValuePair<TKey, TValue> itemToRemove
|
||||
) {
|
||||
ICollection<TValue> values;
|
||||
if(!this.typedDictionary.TryGetValue(itemToRemove.Key, out values)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if(values.Remove(itemToRemove.Value)) {
|
||||
if(values.Count == 0) {
|
||||
this.typedDictionary.Remove(itemToRemove.Key);
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region ICollection implementation
|
||||
|
||||
/// <summary>Copies the contents of the Dictionary into an array</summary>
|
||||
/// <param name="array">Array the Dictionary contents will be copied into</param>
|
||||
/// <param name="arrayIndex">
|
||||
/// Starting index at which to begin filling the destination array
|
||||
/// </param>
|
||||
void ICollection.CopyTo(Array array, int arrayIndex) {
|
||||
foreach(KeyValuePair<TKey, ICollection<TValue>> item in this.typedDictionary) {
|
||||
foreach(TValue value in item.Value) {
|
||||
array.SetValue(new KeyValuePair<TKey, TValue>(item.Key, value), arrayIndex);
|
||||
++arrayIndex;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Whether the Dictionary is synchronized for multi-threaded usage</summary>
|
||||
bool ICollection.IsSynchronized {
|
||||
get { return this.objectDictionary.IsSynchronized; }
|
||||
}
|
||||
|
||||
/// <summary>Synchronization root on which the Dictionary locks</summary>
|
||||
object ICollection.SyncRoot {
|
||||
get { return this.objectDictionary.SyncRoot; }
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IDictionary<TKey, ICollection<TValue>> implementation
|
||||
|
||||
/// <summary>Adds a series of values to a dictionary</summary>
|
||||
/// <param name="key">Key under which the values will be added</param>
|
||||
/// <param name="values">Values that will be added to the dictionary</param>
|
||||
void IDictionary<TKey, ICollection<TValue>>.Add(TKey key, ICollection<TValue> values) {
|
||||
ICollection<TValue> currentValues;
|
||||
if(!this.typedDictionary.TryGetValue(key, out currentValues)) {
|
||||
currentValues = new ValueList(this);
|
||||
}
|
||||
|
||||
foreach(TValue value in values) {
|
||||
currentValues.Add(value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Removes all values with the specified key</summary>
|
||||
/// <param name="key">Key whose associated entries will be removed</param>
|
||||
/// <returns>True if at least one entry has been removed from the dictionary</returns>
|
||||
bool IDictionary<TKey, ICollection<TValue>>.Remove(TKey key) {
|
||||
return (RemoveKey(key) > 0);
|
||||
}
|
||||
|
||||
/// <summary>Returns a collection of value collections</summary>
|
||||
ICollection<ICollection<TValue>> IDictionary<TKey, ICollection<TValue>>.Values {
|
||||
get { return this.typedDictionary.Values; }
|
||||
}
|
||||
|
||||
#endregion // IDictionary<TKey, ICollection<TValue>> implementation
|
||||
|
||||
#region ICollection<KeyValuePair<TKey, ICollection<TValue>>> implementation
|
||||
|
||||
/// <summary>Adds a series of values to a dictionary</summary>
|
||||
/// <param name="item">Entry containing the values that will be added</param>
|
||||
void ICollection<KeyValuePair<TKey, ICollection<TValue>>>.Add(
|
||||
KeyValuePair<TKey, ICollection<TValue>> item
|
||||
) {
|
||||
ICollection<TValue> currentValues;
|
||||
if(!this.typedDictionary.TryGetValue(item.Key, out currentValues)) {
|
||||
currentValues = new ValueList(this);
|
||||
}
|
||||
|
||||
foreach(TValue value in item.Value) {
|
||||
currentValues.Add(value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether the dictionary contains the specified key/value pair
|
||||
/// </summary>
|
||||
/// <param name="item">Key/value pair for which the dictionary will be checked</param>
|
||||
/// <returns>True if the dictionary contains the specified key/value pair</returns>
|
||||
bool ICollection<KeyValuePair<TKey, ICollection<TValue>>>.Contains(
|
||||
KeyValuePair<TKey, ICollection<TValue>> item
|
||||
) {
|
||||
return this.typedDictionary.Contains(item);
|
||||
}
|
||||
|
||||
/// <summary>Copies the contents of the dictionary into an array</summary>
|
||||
/// <param name="array"></param>
|
||||
/// <param name="arrayIndex"></param>
|
||||
void ICollection<KeyValuePair<TKey, ICollection<TValue>>>.CopyTo(
|
||||
KeyValuePair<TKey, ICollection<TValue>>[] array, int arrayIndex
|
||||
) {
|
||||
this.typedDictionary.CopyTo(array, arrayIndex);
|
||||
}
|
||||
|
||||
/// <summary>Removes the specified key/value pair from the dictionary</summary>
|
||||
/// <param name="item">Key/value pair that will be removed</param>
|
||||
/// <returns>True if the key/value pair was contained in the dictionary</returns>
|
||||
bool ICollection<KeyValuePair<TKey, ICollection<TValue>>>.Remove(
|
||||
KeyValuePair<TKey, ICollection<TValue>> item
|
||||
) {
|
||||
return this.typedDictionary.Remove(item);
|
||||
}
|
||||
|
||||
/// <summary>Returns an enumerator for the dictionary</summary>
|
||||
/// <returns>An enumerator for the key/value pairs in the dictionary</returns>
|
||||
IEnumerator<KeyValuePair<TKey, ICollection<TValue>>> IEnumerable<
|
||||
KeyValuePair<TKey, ICollection<TValue>>
|
||||
>.GetEnumerator() {
|
||||
return this.typedDictionary.GetEnumerator();
|
||||
}
|
||||
|
||||
/// <summary>Number of unique keys in the dictionary</summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// This Count property returns a different value from the main interface of
|
||||
/// the multi dictionary to stay consistent with the implemented interfaces.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// If you cast a multi dictionary to a collection of collections, the count
|
||||
/// property of the outer collection should, of course, be the number of inner
|
||||
/// collections it contains (and not the sum of the items contained in all of
|
||||
/// the inner collections).
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// If you use the count property in the main interface of the multi dictionary,
|
||||
/// the value collections are hidden (it behaves as if the key was in the
|
||||
/// dictionary multiple times), so now the sum of all key-value pairs should
|
||||
/// be returned.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
int ICollection<KeyValuePair<TKey, ICollection<TValue>>>.Count {
|
||||
get { return this.typedDictionary.Count; }
|
||||
}
|
||||
|
||||
#endregion // ICollection<KeyValuePair<TKey, ICollection<TValue>>> implementation
|
||||
|
||||
}
|
||||
|
||||
} // namespace Nuclex.Support.Collections
|
||||
|
|
|
|||
|
|
@ -1,391 +1,390 @@
|
|||
#region CPL License
|
||||
/*
|
||||
Nuclex Framework
|
||||
Copyright (C) 2002-2017 Nuclex Development Labs
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the IBM Common Public License as
|
||||
published by the IBM Corporation; either version 1.0 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
IBM Common Public License for more details.
|
||||
|
||||
You should have received a copy of the IBM Common Public
|
||||
License along with this library
|
||||
*/
|
||||
#endregion
|
||||
|
||||
#if UNITTEST
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
using NUnit.Framework;
|
||||
using NMock;
|
||||
|
||||
namespace Nuclex.Support.Collections {
|
||||
|
||||
/// <summary>Unit tests for the multi dictionary</summary>
|
||||
[TestFixture]
|
||||
internal class MultiDictionaryTest {
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that new instances of the multi dictionary can be created
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void CanConstructNewDictionary() {
|
||||
var dictionary = new MultiDictionary<int, string>();
|
||||
Assert.IsNotNull(dictionary); // nonsense, prevents compiler warning
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that the count is initialized correctly when building
|
||||
/// a multi dictionary from a dictionary of value collections.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void CountIsCalculatedIfInitializedFromDictionary() {
|
||||
var contents = new Dictionary<int, ICollection<string>>();
|
||||
contents.Add(1, new List<string>(new string[] { "one", "eins" }));
|
||||
contents.Add(2, new List<string>(new string[] { "two", "zwei" }));
|
||||
|
||||
var multiDictionary = new MultiDictionary<int, string>(contents);
|
||||
Assert.AreEqual(4, multiDictionary.Count);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that a new multi dictionary based on a read-only dictionary is
|
||||
/// also read-only
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void IsReadOnlyWhenBasedOnReadOnlyContainer() {
|
||||
var readOnly = new ReadOnlyDictionary<int, ICollection<string>>(
|
||||
new Dictionary<int, ICollection<string>>()
|
||||
);
|
||||
var dictionary = new MultiDictionary<int, string>(readOnly);
|
||||
|
||||
Assert.IsTrue(dictionary.IsReadOnly);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ensures that the multi dictionary can contain the same key multiple times
|
||||
/// (or in other words, multiple values on the same key)
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void CanContainKeyMultipleTimes() {
|
||||
var dictionary = new MultiDictionary<int, string>();
|
||||
dictionary.Add(123, "one two three");
|
||||
dictionary.Add(123, "eins zwei drei");
|
||||
|
||||
Assert.AreEqual(2, dictionary.Count);
|
||||
|
||||
CollectionAssert.AreEquivalent(
|
||||
new KeyValuePair<int, string>[] {
|
||||
new KeyValuePair<int, string>(123, "one two three"),
|
||||
new KeyValuePair<int, string>(123, "eins zwei drei")
|
||||
},
|
||||
dictionary
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that adding values through the indexer still updates the item count
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void AddingValuesFromIndexerUpdatesCount() {
|
||||
var dictionary = new MultiDictionary<int, string>();
|
||||
dictionary.Add(42, "the answer to everything");
|
||||
dictionary[42].Add("21x2");
|
||||
|
||||
Assert.AreEqual(2, dictionary.Count);
|
||||
|
||||
CollectionAssert.AreEquivalent(
|
||||
new KeyValuePair<int, string>[] {
|
||||
new KeyValuePair<int, string>(42, "the answer to everything"),
|
||||
new KeyValuePair<int, string>(42, "21x2")
|
||||
},
|
||||
dictionary
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests whether the collection can count the number of values stored
|
||||
/// under a key
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void ValuesWithSameKeyCanBeCounted() {
|
||||
var dictionary = new MultiDictionary<int, string>();
|
||||
dictionary.Add(10, "ten");
|
||||
dictionary.Add(20, "twenty");
|
||||
dictionary.Add(30, "thirty");
|
||||
dictionary.Add(10, "zehn");
|
||||
dictionary.Add(20, "zwanzig");
|
||||
dictionary.Add(10, "dix");
|
||||
|
||||
Assert.AreEqual(6, dictionary.Count);
|
||||
Assert.AreEqual(3, dictionary.CountValues(10));
|
||||
Assert.AreEqual(2, dictionary.CountValues(20));
|
||||
Assert.AreEqual(1, dictionary.CountValues(30));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that counting the values of a non-existing key returns 0
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void CountingValuesOfNonExistentKeyReturnsNull() {
|
||||
var dictionary = new MultiDictionary<int, string>();
|
||||
Assert.AreEqual(0, dictionary.CountValues(1));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ensures that its possible to remove values individually without affecting
|
||||
/// other values stored under the same key
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void ValuesCanBeRemovedIndividually() {
|
||||
var dictionary = new MultiDictionary<int, string>();
|
||||
dictionary.Add(10, "ten");
|
||||
dictionary.Add(10, "zehn");
|
||||
dictionary.Add(10, "dix");
|
||||
|
||||
dictionary.Remove(10, "zehn");
|
||||
|
||||
Assert.AreEqual(2, dictionary.Count);
|
||||
CollectionAssert.AreEquivalent(
|
||||
new KeyValuePair<int, string>[] {
|
||||
new KeyValuePair<int, string>(10, "ten"),
|
||||
new KeyValuePair<int, string>(10, "dix")
|
||||
},
|
||||
dictionary
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that the Count property returns the number of unique keys if it is called
|
||||
/// on the collection-of-collections interface implemented by the multi dictionary
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void CollectionOfCollectionCountIsUniqueKeyCount() {
|
||||
var dictionary = new MultiDictionary<int, string>();
|
||||
dictionary.Add(10, "ten");
|
||||
dictionary.Add(10, "zehn");
|
||||
|
||||
Assert.AreEqual(2, dictionary.Count);
|
||||
var collectionOfCollections =
|
||||
(ICollection<KeyValuePair<int, ICollection<string>>>)dictionary;
|
||||
Assert.AreEqual(1, collectionOfCollections.Count);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that the multi dictionary can be tested for containment of a specific value
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void ContainmentCanBeTested() {
|
||||
var dictionary = new MultiDictionary<int, string>();
|
||||
dictionary.Add(10, "ten");
|
||||
dictionary.Add(10, "zehn");
|
||||
|
||||
Assert.IsTrue(dictionary.Contains(new KeyValuePair<int, string>(10, "ten")));
|
||||
Assert.IsTrue(dictionary.Contains(new KeyValuePair<int, string>(10, "zehn")));
|
||||
Assert.IsFalse(dictionary.Contains(new KeyValuePair<int, string>(10, "dix")));
|
||||
Assert.IsFalse(dictionary.Contains(new KeyValuePair<int, string>(20, "ten")));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that the multi dictionary can be tested for containment of a specific key
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void KeyContainmentCanBeTested() {
|
||||
var dictionary = new MultiDictionary<int, string>();
|
||||
dictionary.Add(10, "ten");
|
||||
dictionary.Add(10, "zehn");
|
||||
|
||||
Assert.IsTrue(dictionary.ContainsKey(10));
|
||||
Assert.IsFalse(dictionary.ContainsKey(20));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that the key collection can be retrieved from the dictionary
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void KeyCollectionCanBeRetrieved() {
|
||||
var dictionary = new MultiDictionary<int, string>();
|
||||
dictionary.Add(10, "ten");
|
||||
dictionary.Add(10, "zehn");
|
||||
|
||||
ICollection<int> keys = dictionary.Keys;
|
||||
Assert.IsNotNull(keys);
|
||||
Assert.AreEqual(1, keys.Count);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that the key collection can be retrieved from the dictionary
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void ValueCollectionCanBeRetrieved() {
|
||||
var dictionary = new MultiDictionary<int, string>();
|
||||
dictionary.Add(10, "ten");
|
||||
dictionary.Add(10, "zehn");
|
||||
dictionary.Add(20, "twenty");
|
||||
|
||||
ICollection<string> values = dictionary.Values;
|
||||
Assert.IsNotNull(values);
|
||||
Assert.AreEqual(3, values.Count);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that TryGetValue() returns false and doesn't throw if a key
|
||||
/// is not found in the collection
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TryGetValueReturnsFalseOnMissingKey() {
|
||||
var dictionary = new MultiDictionary<int, string>();
|
||||
ICollection<string> values;
|
||||
Assert.IsFalse(dictionary.TryGetValue(123, out values));
|
||||
}
|
||||
|
||||
/// <summary>Verifies that keys can be looked up via TryGetValue()</summary>
|
||||
[Test]
|
||||
public void TryGetValueCanLookUpValues() {
|
||||
var dictionary = new MultiDictionary<int, string>();
|
||||
dictionary.Add(10, "ten");
|
||||
dictionary.Add(10, "zehn");
|
||||
ICollection<string> values;
|
||||
Assert.IsTrue(dictionary.TryGetValue(10, out values));
|
||||
Assert.AreEqual(2, values.Count);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that assigning null to a key deletes all the values stored
|
||||
/// under it
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void AssigningNullToKeyRemovesAllValues() {
|
||||
var dictionary = new MultiDictionary<int, string>();
|
||||
dictionary.Add(10, "ten");
|
||||
dictionary.Add(10, "zehn");
|
||||
dictionary.Add(20, "twenty");
|
||||
|
||||
Assert.AreEqual(3, dictionary.Count);
|
||||
dictionary[10] = null;
|
||||
Assert.AreEqual(1, dictionary.Count);
|
||||
Assert.IsFalse(dictionary.ContainsKey(10));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that assigning null to a key deletes all the values stored
|
||||
/// under it
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void ValueListCanBeAssignedToNewKey() {
|
||||
var dictionary = new MultiDictionary<int, string>();
|
||||
dictionary[3] = new List<string>() { "three", "drei" };
|
||||
|
||||
Assert.AreEqual(2, dictionary.Count);
|
||||
Assert.IsTrue(dictionary.Contains(new KeyValuePair<int, string>(3, "three")));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that assigning null to a key deletes all the values stored
|
||||
/// under it
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void ValueListCanOverwriteExistingKey() {
|
||||
var dictionary = new MultiDictionary<int, string>();
|
||||
dictionary.Add(10, "dix");
|
||||
|
||||
Assert.AreEqual(1, dictionary.Count);
|
||||
|
||||
dictionary[10] = new List<string>() { "ten", "zehn" };
|
||||
|
||||
Assert.AreEqual(2, dictionary.Count);
|
||||
Assert.IsFalse(dictionary.Contains(new KeyValuePair<int, string>(10, "dix")));
|
||||
Assert.IsTrue(dictionary.Contains(new KeyValuePair<int, string>(10, "ten")));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that nothing bad happens when a key is removed from the dictionary
|
||||
/// that it doesn't contain
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void NonExistingKeyCanBeRemoved() {
|
||||
var dictionary = new MultiDictionary<int, string>();
|
||||
Assert.AreEqual(0, dictionary.RemoveKey(123));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that the remove method returns the number of values that have
|
||||
/// been removed from the dictionary
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void RemoveReturnsNumberOfValuesRemoved() {
|
||||
var dictionary = new MultiDictionary<int, string>();
|
||||
dictionary.Add(10, "ten");
|
||||
dictionary.Add(10, "zehn");
|
||||
Assert.AreEqual(2, dictionary.RemoveKey(10));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that the dictionary becomes empty after clearing it
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void DictionaryIsEmptyAfterClear() {
|
||||
var dictionary = new MultiDictionary<int, string>();
|
||||
dictionary.Add(10, "ten");
|
||||
dictionary.Add(10, "zehn");
|
||||
dictionary.Add(20, "twenty");
|
||||
Assert.AreEqual(3, dictionary.Count);
|
||||
dictionary.Clear();
|
||||
Assert.AreEqual(0, dictionary.Count);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that non-existing values can be removed from the dictionary
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void NonExistingValueCanBeRemoved() {
|
||||
var dictionary = new MultiDictionary<int, string>();
|
||||
Assert.IsFalse(dictionary.Remove(123, "test"));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that nothing bad happens when the last value under a key is removed
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void LastValueOfKeyCanBeRemoved() {
|
||||
var dictionary = new MultiDictionary<int, string>();
|
||||
dictionary.Add(123, "test");
|
||||
dictionary.Remove(123, "test");
|
||||
Assert.AreEqual(0, dictionary.CountValues(123));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that the dictionary can be copied into an array
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void DictionaryCanBeCopiedIntoArray() {
|
||||
var expected = new List<KeyValuePair<int, string>>() {
|
||||
new KeyValuePair<int, string>(1, "one"),
|
||||
new KeyValuePair<int, string>(1, "eins"),
|
||||
new KeyValuePair<int, string>(2, "two"),
|
||||
new KeyValuePair<int, string>(2, "zwei")
|
||||
};
|
||||
|
||||
var dictionary = new MultiDictionary<int, string>();
|
||||
foreach(KeyValuePair<int, string> entry in expected) {
|
||||
dictionary.Add(entry.Key, entry.Value);
|
||||
}
|
||||
|
||||
var actual = new KeyValuePair<int, string>[4];
|
||||
dictionary.CopyTo(actual, 0);
|
||||
|
||||
CollectionAssert.AreEquivalent(expected, actual);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} // namespace Nuclex.Support.Collections
|
||||
|
||||
#endif // UNITTEST
|
||||
#region Apache License 2.0
|
||||
/*
|
||||
Nuclex .NET Framework
|
||||
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
#endregion // Apache License 2.0
|
||||
|
||||
#if UNITTEST
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
using NUnit.Framework;
|
||||
using NMock;
|
||||
|
||||
namespace Nuclex.Support.Collections {
|
||||
|
||||
/// <summary>Unit tests for the multi dictionary</summary>
|
||||
[TestFixture]
|
||||
internal class MultiDictionaryTest {
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that new instances of the multi dictionary can be created
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void CanConstructNewDictionary() {
|
||||
var dictionary = new MultiDictionary<int, string>();
|
||||
Assert.IsNotNull(dictionary); // nonsense, prevents compiler warning
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that the count is initialized correctly when building
|
||||
/// a multi dictionary from a dictionary of value collections.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void CountIsCalculatedIfInitializedFromDictionary() {
|
||||
var contents = new Dictionary<int, ICollection<string>>();
|
||||
contents.Add(1, new List<string>(new string[] { "one", "eins" }));
|
||||
contents.Add(2, new List<string>(new string[] { "two", "zwei" }));
|
||||
|
||||
var multiDictionary = new MultiDictionary<int, string>(contents);
|
||||
Assert.AreEqual(4, multiDictionary.Count);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that a new multi dictionary based on a read-only dictionary is
|
||||
/// also read-only
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void IsReadOnlyWhenBasedOnReadOnlyContainer() {
|
||||
var readOnly = new ReadOnlyDictionary<int, ICollection<string>>(
|
||||
new Dictionary<int, ICollection<string>>()
|
||||
);
|
||||
var dictionary = new MultiDictionary<int, string>(readOnly);
|
||||
|
||||
Assert.IsTrue(dictionary.IsReadOnly);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ensures that the multi dictionary can contain the same key multiple times
|
||||
/// (or in other words, multiple values on the same key)
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void CanContainKeyMultipleTimes() {
|
||||
var dictionary = new MultiDictionary<int, string>();
|
||||
dictionary.Add(123, "one two three");
|
||||
dictionary.Add(123, "eins zwei drei");
|
||||
|
||||
Assert.AreEqual(2, dictionary.Count);
|
||||
|
||||
CollectionAssert.AreEquivalent(
|
||||
new KeyValuePair<int, string>[] {
|
||||
new KeyValuePair<int, string>(123, "one two three"),
|
||||
new KeyValuePair<int, string>(123, "eins zwei drei")
|
||||
},
|
||||
dictionary
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that adding values through the indexer still updates the item count
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void AddingValuesFromIndexerUpdatesCount() {
|
||||
var dictionary = new MultiDictionary<int, string>();
|
||||
dictionary.Add(42, "the answer to everything");
|
||||
dictionary[42].Add("21x2");
|
||||
|
||||
Assert.AreEqual(2, dictionary.Count);
|
||||
|
||||
CollectionAssert.AreEquivalent(
|
||||
new KeyValuePair<int, string>[] {
|
||||
new KeyValuePair<int, string>(42, "the answer to everything"),
|
||||
new KeyValuePair<int, string>(42, "21x2")
|
||||
},
|
||||
dictionary
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests whether the collection can count the number of values stored
|
||||
/// under a key
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void ValuesWithSameKeyCanBeCounted() {
|
||||
var dictionary = new MultiDictionary<int, string>();
|
||||
dictionary.Add(10, "ten");
|
||||
dictionary.Add(20, "twenty");
|
||||
dictionary.Add(30, "thirty");
|
||||
dictionary.Add(10, "zehn");
|
||||
dictionary.Add(20, "zwanzig");
|
||||
dictionary.Add(10, "dix");
|
||||
|
||||
Assert.AreEqual(6, dictionary.Count);
|
||||
Assert.AreEqual(3, dictionary.CountValues(10));
|
||||
Assert.AreEqual(2, dictionary.CountValues(20));
|
||||
Assert.AreEqual(1, dictionary.CountValues(30));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that counting the values of a non-existing key returns 0
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void CountingValuesOfNonExistentKeyReturnsNull() {
|
||||
var dictionary = new MultiDictionary<int, string>();
|
||||
Assert.AreEqual(0, dictionary.CountValues(1));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ensures that its possible to remove values individually without affecting
|
||||
/// other values stored under the same key
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void ValuesCanBeRemovedIndividually() {
|
||||
var dictionary = new MultiDictionary<int, string>();
|
||||
dictionary.Add(10, "ten");
|
||||
dictionary.Add(10, "zehn");
|
||||
dictionary.Add(10, "dix");
|
||||
|
||||
dictionary.Remove(10, "zehn");
|
||||
|
||||
Assert.AreEqual(2, dictionary.Count);
|
||||
CollectionAssert.AreEquivalent(
|
||||
new KeyValuePair<int, string>[] {
|
||||
new KeyValuePair<int, string>(10, "ten"),
|
||||
new KeyValuePair<int, string>(10, "dix")
|
||||
},
|
||||
dictionary
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that the Count property returns the number of unique keys if it is called
|
||||
/// on the collection-of-collections interface implemented by the multi dictionary
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void CollectionOfCollectionCountIsUniqueKeyCount() {
|
||||
var dictionary = new MultiDictionary<int, string>();
|
||||
dictionary.Add(10, "ten");
|
||||
dictionary.Add(10, "zehn");
|
||||
|
||||
Assert.AreEqual(2, dictionary.Count);
|
||||
var collectionOfCollections =
|
||||
(ICollection<KeyValuePair<int, ICollection<string>>>)dictionary;
|
||||
Assert.AreEqual(1, collectionOfCollections.Count);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that the multi dictionary can be tested for containment of a specific value
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void ContainmentCanBeTested() {
|
||||
var dictionary = new MultiDictionary<int, string>();
|
||||
dictionary.Add(10, "ten");
|
||||
dictionary.Add(10, "zehn");
|
||||
|
||||
Assert.IsTrue(dictionary.Contains(new KeyValuePair<int, string>(10, "ten")));
|
||||
Assert.IsTrue(dictionary.Contains(new KeyValuePair<int, string>(10, "zehn")));
|
||||
Assert.IsFalse(dictionary.Contains(new KeyValuePair<int, string>(10, "dix")));
|
||||
Assert.IsFalse(dictionary.Contains(new KeyValuePair<int, string>(20, "ten")));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that the multi dictionary can be tested for containment of a specific key
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void KeyContainmentCanBeTested() {
|
||||
var dictionary = new MultiDictionary<int, string>();
|
||||
dictionary.Add(10, "ten");
|
||||
dictionary.Add(10, "zehn");
|
||||
|
||||
Assert.IsTrue(dictionary.ContainsKey(10));
|
||||
Assert.IsFalse(dictionary.ContainsKey(20));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that the key collection can be retrieved from the dictionary
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void KeyCollectionCanBeRetrieved() {
|
||||
var dictionary = new MultiDictionary<int, string>();
|
||||
dictionary.Add(10, "ten");
|
||||
dictionary.Add(10, "zehn");
|
||||
|
||||
ICollection<int> keys = dictionary.Keys;
|
||||
Assert.IsNotNull(keys);
|
||||
Assert.AreEqual(1, keys.Count);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that the key collection can be retrieved from the dictionary
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void ValueCollectionCanBeRetrieved() {
|
||||
var dictionary = new MultiDictionary<int, string>();
|
||||
dictionary.Add(10, "ten");
|
||||
dictionary.Add(10, "zehn");
|
||||
dictionary.Add(20, "twenty");
|
||||
|
||||
ICollection<string> values = dictionary.Values;
|
||||
Assert.IsNotNull(values);
|
||||
Assert.AreEqual(3, values.Count);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that TryGetValue() returns false and doesn't throw if a key
|
||||
/// is not found in the collection
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TryGetValueReturnsFalseOnMissingKey() {
|
||||
var dictionary = new MultiDictionary<int, string>();
|
||||
ICollection<string> values;
|
||||
Assert.IsFalse(dictionary.TryGetValue(123, out values));
|
||||
}
|
||||
|
||||
/// <summary>Verifies that keys can be looked up via TryGetValue()</summary>
|
||||
[Test]
|
||||
public void TryGetValueCanLookUpValues() {
|
||||
var dictionary = new MultiDictionary<int, string>();
|
||||
dictionary.Add(10, "ten");
|
||||
dictionary.Add(10, "zehn");
|
||||
ICollection<string> values;
|
||||
Assert.IsTrue(dictionary.TryGetValue(10, out values));
|
||||
Assert.AreEqual(2, values.Count);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that assigning null to a key deletes all the values stored
|
||||
/// under it
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void AssigningNullToKeyRemovesAllValues() {
|
||||
var dictionary = new MultiDictionary<int, string>();
|
||||
dictionary.Add(10, "ten");
|
||||
dictionary.Add(10, "zehn");
|
||||
dictionary.Add(20, "twenty");
|
||||
|
||||
Assert.AreEqual(3, dictionary.Count);
|
||||
dictionary[10] = null;
|
||||
Assert.AreEqual(1, dictionary.Count);
|
||||
Assert.IsFalse(dictionary.ContainsKey(10));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that assigning null to a key deletes all the values stored
|
||||
/// under it
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void ValueListCanBeAssignedToNewKey() {
|
||||
var dictionary = new MultiDictionary<int, string>();
|
||||
dictionary[3] = new List<string>() { "three", "drei" };
|
||||
|
||||
Assert.AreEqual(2, dictionary.Count);
|
||||
Assert.IsTrue(dictionary.Contains(new KeyValuePair<int, string>(3, "three")));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that assigning null to a key deletes all the values stored
|
||||
/// under it
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void ValueListCanOverwriteExistingKey() {
|
||||
var dictionary = new MultiDictionary<int, string>();
|
||||
dictionary.Add(10, "dix");
|
||||
|
||||
Assert.AreEqual(1, dictionary.Count);
|
||||
|
||||
dictionary[10] = new List<string>() { "ten", "zehn" };
|
||||
|
||||
Assert.AreEqual(2, dictionary.Count);
|
||||
Assert.IsFalse(dictionary.Contains(new KeyValuePair<int, string>(10, "dix")));
|
||||
Assert.IsTrue(dictionary.Contains(new KeyValuePair<int, string>(10, "ten")));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that nothing bad happens when a key is removed from the dictionary
|
||||
/// that it doesn't contain
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void NonExistingKeyCanBeRemoved() {
|
||||
var dictionary = new MultiDictionary<int, string>();
|
||||
Assert.AreEqual(0, dictionary.RemoveKey(123));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that the remove method returns the number of values that have
|
||||
/// been removed from the dictionary
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void RemoveReturnsNumberOfValuesRemoved() {
|
||||
var dictionary = new MultiDictionary<int, string>();
|
||||
dictionary.Add(10, "ten");
|
||||
dictionary.Add(10, "zehn");
|
||||
Assert.AreEqual(2, dictionary.RemoveKey(10));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that the dictionary becomes empty after clearing it
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void DictionaryIsEmptyAfterClear() {
|
||||
var dictionary = new MultiDictionary<int, string>();
|
||||
dictionary.Add(10, "ten");
|
||||
dictionary.Add(10, "zehn");
|
||||
dictionary.Add(20, "twenty");
|
||||
Assert.AreEqual(3, dictionary.Count);
|
||||
dictionary.Clear();
|
||||
Assert.AreEqual(0, dictionary.Count);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that non-existing values can be removed from the dictionary
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void NonExistingValueCanBeRemoved() {
|
||||
var dictionary = new MultiDictionary<int, string>();
|
||||
Assert.IsFalse(dictionary.Remove(123, "test"));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that nothing bad happens when the last value under a key is removed
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void LastValueOfKeyCanBeRemoved() {
|
||||
var dictionary = new MultiDictionary<int, string>();
|
||||
dictionary.Add(123, "test");
|
||||
dictionary.Remove(123, "test");
|
||||
Assert.AreEqual(0, dictionary.CountValues(123));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that the dictionary can be copied into an array
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void DictionaryCanBeCopiedIntoArray() {
|
||||
var expected = new List<KeyValuePair<int, string>>() {
|
||||
new KeyValuePair<int, string>(1, "one"),
|
||||
new KeyValuePair<int, string>(1, "eins"),
|
||||
new KeyValuePair<int, string>(2, "two"),
|
||||
new KeyValuePair<int, string>(2, "zwei")
|
||||
};
|
||||
|
||||
var dictionary = new MultiDictionary<int, string>();
|
||||
foreach(KeyValuePair<int, string> entry in expected) {
|
||||
dictionary.Add(entry.Key, entry.Value);
|
||||
}
|
||||
|
||||
var actual = new KeyValuePair<int, string>[4];
|
||||
dictionary.CopyTo(actual, 0);
|
||||
|
||||
CollectionAssert.AreEquivalent(expected, actual);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} // namespace Nuclex.Support.Collections
|
||||
|
||||
#endif // UNITTEST
|
||||
|
|
|
|||
|
|
@ -1,269 +1,268 @@
|
|||
#region CPL License
|
||||
/*
|
||||
Nuclex Framework
|
||||
Copyright (C) 2002-2017 Nuclex Development Labs
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the IBM Common Public License as
|
||||
published by the IBM Corporation; either version 1.0 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
IBM Common Public License for more details.
|
||||
|
||||
You should have received a copy of the IBM Common Public
|
||||
License along with this library
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections;
|
||||
|
||||
namespace Nuclex.Support.Collections {
|
||||
|
||||
partial class MultiDictionary<TKey, TValue> {
|
||||
|
||||
/// <summary>
|
||||
/// Provides access to the values stored in a multi dictionary as a collection
|
||||
/// </summary>
|
||||
private class ValueCollection : ICollection<TValue>, ICollection {
|
||||
|
||||
#region class Enumerator
|
||||
|
||||
/// <summary>Enumerates the values stored in a multi dictionary</summary>
|
||||
private class Enumerator : IEnumerator<TValue> {
|
||||
|
||||
/// <summary>Initializes a new enumerator</summary>
|
||||
/// <param name="valueCollections">Value collections being enumerated</param>
|
||||
public Enumerator(ICollection<ICollection<TValue>> valueCollections) {
|
||||
this.valueCollections = valueCollections;
|
||||
|
||||
Reset();
|
||||
}
|
||||
|
||||
/// <summary>Immediately releases all resources owned by the instance</summary>
|
||||
public void Dispose() {
|
||||
if(this.currentValue != null) {
|
||||
this.currentValue.Dispose();
|
||||
this.currentValue = null;
|
||||
}
|
||||
if(this.currentCollection != null) {
|
||||
this.currentCollection.Dispose();
|
||||
this.currentCollection = null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>The current value the enumerator is pointing at</summary>
|
||||
public TValue Current {
|
||||
get {
|
||||
if(this.currentValue == null) {
|
||||
throw new InvalidOperationException("Enumerator is not on a valid position");
|
||||
}
|
||||
|
||||
return this.currentValue.Current;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Advances the enumerator to the next item</summary>
|
||||
/// <returns>
|
||||
/// True if there was a next item, false if the enumerator reached the end
|
||||
/// </returns>
|
||||
public bool MoveNext() {
|
||||
if(this.currentCollection == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for(; ; ) {
|
||||
|
||||
// Try to move the enumerator in the current key's list to the next item
|
||||
if(this.currentValue != null) {
|
||||
if(this.currentValue.MoveNext()) {
|
||||
return true; // We found the next item
|
||||
} else {
|
||||
this.currentValue.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
// Enumerator for the current key's list reached the end, go to the next key
|
||||
if(this.currentCollection.MoveNext()) {
|
||||
this.currentValue = this.currentCollection.Current.GetEnumerator();
|
||||
} else {
|
||||
this.currentValue = null; // Guaranteed to be disposed already
|
||||
this.currentCollection.Dispose();
|
||||
this.currentCollection = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Resets the enumerator to its initial position</summary>
|
||||
public void Reset() {
|
||||
if(this.currentValue != null) {
|
||||
this.currentValue.Dispose();
|
||||
this.currentValue = null;
|
||||
}
|
||||
if(this.currentCollection != null) {
|
||||
this.currentCollection.Dispose();
|
||||
}
|
||||
this.currentCollection = valueCollections.GetEnumerator();
|
||||
}
|
||||
|
||||
#region IEnumerator implementation
|
||||
|
||||
/// <summary>The current entry the enumerator is pointing at</summary>
|
||||
object IEnumerator.Current {
|
||||
get { return Current; }
|
||||
}
|
||||
|
||||
#endregion // IEnumerator implementation
|
||||
|
||||
/// <summary>Value collections being enumerated</summary>
|
||||
private ICollection<ICollection<TValue>> valueCollections;
|
||||
/// <summary>The current value collection the enumerator is in</summary>
|
||||
private IEnumerator<ICollection<TValue>> currentCollection;
|
||||
/// <summary>Current value in the collection the enumerator is in</summary>
|
||||
private IEnumerator<TValue> currentValue;
|
||||
|
||||
}
|
||||
|
||||
#endregion // class Enumerator
|
||||
|
||||
/// <summary>Initializes a new multi dictionary value collection</summary>
|
||||
/// <param name="dictionary">Dictionary whose values the collection represents</param>
|
||||
public ValueCollection(MultiDictionary<TKey, TValue> dictionary) {
|
||||
this.dictionary = dictionary;
|
||||
this.dictionaryAsICollection = (ICollection)dictionary;
|
||||
}
|
||||
|
||||
/// <summary>Determines whether the collection contains a specific value</summary>
|
||||
/// <param name="item">Value for which the collection will be checked</param>
|
||||
/// <returns>True if the collection contains the specified value</returns>
|
||||
public bool Contains(TValue item) {
|
||||
foreach(ICollection<TValue> values in this.dictionary.Values) {
|
||||
if(values.Contains(item)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>Copies the contents of the collection into an array</summary>
|
||||
/// <param name="array">Array the collection contents will be copied into</param>
|
||||
/// <param name="arrayIndex">
|
||||
/// Starting index in the array where writing will begin
|
||||
/// </param>
|
||||
public void CopyTo(TValue[] array, int arrayIndex) {
|
||||
foreach(ICollection<TValue> values in this.dictionary.Values) {
|
||||
foreach(TValue value in values) {
|
||||
array[arrayIndex] = value;
|
||||
++arrayIndex;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>The number of values in the collection</summary>
|
||||
public int Count {
|
||||
get { return this.dictionary.count; }
|
||||
}
|
||||
|
||||
/// <summary>Always true since the value collection is read-only</summary>
|
||||
public bool IsReadOnly {
|
||||
get { return true; }
|
||||
}
|
||||
|
||||
/// <summary>Returns a new enumerator for the value collection</summary>
|
||||
/// <returns>A new enumerator for the value collection</returns>
|
||||
public IEnumerator<TValue> GetEnumerator() {
|
||||
return new Enumerator(this.dictionary.typedDictionary.Values);
|
||||
}
|
||||
|
||||
#region IEnumerator implementation
|
||||
|
||||
/// <summary>Returns a non-typesafe enumerator for the collection</summary>
|
||||
/// <returns>The non-typesafe collection enumerator</returns>
|
||||
IEnumerator IEnumerable.GetEnumerator() {
|
||||
return GetEnumerator();
|
||||
}
|
||||
|
||||
#endregion // IEnumerator implementation
|
||||
|
||||
#region ICollection<> implementation
|
||||
|
||||
/// <summary>Throws a NotSupportedException</summary>
|
||||
/// <param name="item">Not used</param>
|
||||
void ICollection<TValue>.Add(TValue item) {
|
||||
throw new NotSupportedException(
|
||||
"Items cannot be added to a dictionary through its values collection"
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>Throws a NotSupportedException</summary>
|
||||
void ICollection<TValue>.Clear() {
|
||||
throw new NotSupportedException(
|
||||
"The values collection of a dictionary cannot be cleared"
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>Throws a NotSupportedException</summary>
|
||||
/// <param name="item">Not used</param>
|
||||
/// <returns>Nothing, since the method always throws an exception</returns>
|
||||
bool ICollection<TValue>.Contains(TValue item) {
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
/// <summary>Not supported</summary>
|
||||
/// <param name="item">Item that will not be removed</param>
|
||||
/// <returns>Nothing because the method throws an exception</returns>
|
||||
bool ICollection<TValue>.Remove(TValue item) {
|
||||
throw new NotSupportedException(
|
||||
"Items cannot be removed from a dictionary through its values collection"
|
||||
);
|
||||
}
|
||||
|
||||
#endregion ICollection<> implementation
|
||||
|
||||
#region ICollection implementation
|
||||
|
||||
/// <summary>Copies the contents of the collection into an array</summary>
|
||||
/// <param name="array">Array the collection's contents are copied into</param>
|
||||
/// <param name="arrayIndex">
|
||||
/// Starting index in the array where writing will begin
|
||||
/// </param>
|
||||
void ICollection.CopyTo(Array array, int arrayIndex) {
|
||||
foreach(ICollection<TValue> values in this.dictionary.Values) {
|
||||
foreach(TValue value in values) {
|
||||
array.SetValue(value, arrayIndex);
|
||||
++arrayIndex;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Whether the dictionary is thread-safe</summary>
|
||||
bool ICollection.IsSynchronized {
|
||||
get { return this.dictionaryAsICollection.IsSynchronized; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The synchronization root used by the dictionary for thread synchronization
|
||||
/// </summary>
|
||||
object ICollection.SyncRoot {
|
||||
get { return this.dictionaryAsICollection.SyncRoot; }
|
||||
}
|
||||
|
||||
#endregion // ICollection implementation
|
||||
|
||||
/// <summary>Dictionary whose values the collection represents</summary>
|
||||
private MultiDictionary<TKey, TValue> dictionary;
|
||||
/// <summary>The dictionary under its ICollection interface</summary>
|
||||
private ICollection dictionaryAsICollection;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} // namespace Nuclex.Support.Collections
|
||||
#region Apache License 2.0
|
||||
/*
|
||||
Nuclex .NET Framework
|
||||
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
#endregion // Apache License 2.0
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections;
|
||||
|
||||
namespace Nuclex.Support.Collections {
|
||||
|
||||
partial class MultiDictionary<TKey, TValue> {
|
||||
|
||||
/// <summary>
|
||||
/// Provides access to the values stored in a multi dictionary as a collection
|
||||
/// </summary>
|
||||
private class ValueCollection : ICollection<TValue>, ICollection {
|
||||
|
||||
#region class Enumerator
|
||||
|
||||
/// <summary>Enumerates the values stored in a multi dictionary</summary>
|
||||
private class Enumerator : IEnumerator<TValue> {
|
||||
|
||||
/// <summary>Initializes a new enumerator</summary>
|
||||
/// <param name="valueCollections">Value collections being enumerated</param>
|
||||
public Enumerator(ICollection<ICollection<TValue>> valueCollections) {
|
||||
this.valueCollections = valueCollections;
|
||||
|
||||
Reset();
|
||||
}
|
||||
|
||||
/// <summary>Immediately releases all resources owned by the instance</summary>
|
||||
public void Dispose() {
|
||||
if(this.currentValue != null) {
|
||||
this.currentValue.Dispose();
|
||||
this.currentValue = null;
|
||||
}
|
||||
if(this.currentCollection != null) {
|
||||
this.currentCollection.Dispose();
|
||||
this.currentCollection = null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>The current value the enumerator is pointing at</summary>
|
||||
public TValue Current {
|
||||
get {
|
||||
if(this.currentValue == null) {
|
||||
throw new InvalidOperationException("Enumerator is not on a valid position");
|
||||
}
|
||||
|
||||
return this.currentValue.Current;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Advances the enumerator to the next item</summary>
|
||||
/// <returns>
|
||||
/// True if there was a next item, false if the enumerator reached the end
|
||||
/// </returns>
|
||||
public bool MoveNext() {
|
||||
if(this.currentCollection == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for(; ; ) {
|
||||
|
||||
// Try to move the enumerator in the current key's list to the next item
|
||||
if(this.currentValue != null) {
|
||||
if(this.currentValue.MoveNext()) {
|
||||
return true; // We found the next item
|
||||
} else {
|
||||
this.currentValue.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
// Enumerator for the current key's list reached the end, go to the next key
|
||||
if(this.currentCollection.MoveNext()) {
|
||||
this.currentValue = this.currentCollection.Current.GetEnumerator();
|
||||
} else {
|
||||
this.currentValue = null; // Guaranteed to be disposed already
|
||||
this.currentCollection.Dispose();
|
||||
this.currentCollection = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Resets the enumerator to its initial position</summary>
|
||||
public void Reset() {
|
||||
if(this.currentValue != null) {
|
||||
this.currentValue.Dispose();
|
||||
this.currentValue = null;
|
||||
}
|
||||
if(this.currentCollection != null) {
|
||||
this.currentCollection.Dispose();
|
||||
}
|
||||
this.currentCollection = valueCollections.GetEnumerator();
|
||||
}
|
||||
|
||||
#region IEnumerator implementation
|
||||
|
||||
/// <summary>The current entry the enumerator is pointing at</summary>
|
||||
object IEnumerator.Current {
|
||||
get { return Current; }
|
||||
}
|
||||
|
||||
#endregion // IEnumerator implementation
|
||||
|
||||
/// <summary>Value collections being enumerated</summary>
|
||||
private ICollection<ICollection<TValue>> valueCollections;
|
||||
/// <summary>The current value collection the enumerator is in</summary>
|
||||
private IEnumerator<ICollection<TValue>> currentCollection;
|
||||
/// <summary>Current value in the collection the enumerator is in</summary>
|
||||
private IEnumerator<TValue> currentValue;
|
||||
|
||||
}
|
||||
|
||||
#endregion // class Enumerator
|
||||
|
||||
/// <summary>Initializes a new multi dictionary value collection</summary>
|
||||
/// <param name="dictionary">Dictionary whose values the collection represents</param>
|
||||
public ValueCollection(MultiDictionary<TKey, TValue> dictionary) {
|
||||
this.dictionary = dictionary;
|
||||
this.dictionaryAsICollection = (ICollection)dictionary;
|
||||
}
|
||||
|
||||
/// <summary>Determines whether the collection contains a specific value</summary>
|
||||
/// <param name="item">Value for which the collection will be checked</param>
|
||||
/// <returns>True if the collection contains the specified value</returns>
|
||||
public bool Contains(TValue item) {
|
||||
foreach(ICollection<TValue> values in this.dictionary.Values) {
|
||||
if(values.Contains(item)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>Copies the contents of the collection into an array</summary>
|
||||
/// <param name="array">Array the collection contents will be copied into</param>
|
||||
/// <param name="arrayIndex">
|
||||
/// Starting index in the array where writing will begin
|
||||
/// </param>
|
||||
public void CopyTo(TValue[] array, int arrayIndex) {
|
||||
foreach(ICollection<TValue> values in this.dictionary.Values) {
|
||||
foreach(TValue value in values) {
|
||||
array[arrayIndex] = value;
|
||||
++arrayIndex;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>The number of values in the collection</summary>
|
||||
public int Count {
|
||||
get { return this.dictionary.count; }
|
||||
}
|
||||
|
||||
/// <summary>Always true since the value collection is read-only</summary>
|
||||
public bool IsReadOnly {
|
||||
get { return true; }
|
||||
}
|
||||
|
||||
/// <summary>Returns a new enumerator for the value collection</summary>
|
||||
/// <returns>A new enumerator for the value collection</returns>
|
||||
public IEnumerator<TValue> GetEnumerator() {
|
||||
return new Enumerator(this.dictionary.typedDictionary.Values);
|
||||
}
|
||||
|
||||
#region IEnumerator implementation
|
||||
|
||||
/// <summary>Returns a non-typesafe enumerator for the collection</summary>
|
||||
/// <returns>The non-typesafe collection enumerator</returns>
|
||||
IEnumerator IEnumerable.GetEnumerator() {
|
||||
return GetEnumerator();
|
||||
}
|
||||
|
||||
#endregion // IEnumerator implementation
|
||||
|
||||
#region ICollection<> implementation
|
||||
|
||||
/// <summary>Throws a NotSupportedException</summary>
|
||||
/// <param name="item">Not used</param>
|
||||
void ICollection<TValue>.Add(TValue item) {
|
||||
throw new NotSupportedException(
|
||||
"Items cannot be added to a dictionary through its values collection"
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>Throws a NotSupportedException</summary>
|
||||
void ICollection<TValue>.Clear() {
|
||||
throw new NotSupportedException(
|
||||
"The values collection of a dictionary cannot be cleared"
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>Throws a NotSupportedException</summary>
|
||||
/// <param name="item">Not used</param>
|
||||
/// <returns>Nothing, since the method always throws an exception</returns>
|
||||
bool ICollection<TValue>.Contains(TValue item) {
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
/// <summary>Not supported</summary>
|
||||
/// <param name="item">Item that will not be removed</param>
|
||||
/// <returns>Nothing because the method throws an exception</returns>
|
||||
bool ICollection<TValue>.Remove(TValue item) {
|
||||
throw new NotSupportedException(
|
||||
"Items cannot be removed from a dictionary through its values collection"
|
||||
);
|
||||
}
|
||||
|
||||
#endregion ICollection<> implementation
|
||||
|
||||
#region ICollection implementation
|
||||
|
||||
/// <summary>Copies the contents of the collection into an array</summary>
|
||||
/// <param name="array">Array the collection's contents are copied into</param>
|
||||
/// <param name="arrayIndex">
|
||||
/// Starting index in the array where writing will begin
|
||||
/// </param>
|
||||
void ICollection.CopyTo(Array array, int arrayIndex) {
|
||||
foreach(ICollection<TValue> values in this.dictionary.Values) {
|
||||
foreach(TValue value in values) {
|
||||
array.SetValue(value, arrayIndex);
|
||||
++arrayIndex;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Whether the dictionary is thread-safe</summary>
|
||||
bool ICollection.IsSynchronized {
|
||||
get { return this.dictionaryAsICollection.IsSynchronized; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The synchronization root used by the dictionary for thread synchronization
|
||||
/// </summary>
|
||||
object ICollection.SyncRoot {
|
||||
get { return this.dictionaryAsICollection.SyncRoot; }
|
||||
}
|
||||
|
||||
#endregion // ICollection implementation
|
||||
|
||||
/// <summary>Dictionary whose values the collection represents</summary>
|
||||
private MultiDictionary<TKey, TValue> dictionary;
|
||||
/// <summary>The dictionary under its ICollection interface</summary>
|
||||
private ICollection dictionaryAsICollection;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} // namespace Nuclex.Support.Collections
|
||||
|
|
|
|||
|
|
@ -1,415 +1,414 @@
|
|||
#region CPL License
|
||||
/*
|
||||
Nuclex Framework
|
||||
Copyright (C) 2002-2017 Nuclex Development Labs
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the IBM Common Public License as
|
||||
published by the IBM Corporation; either version 1.0 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
IBM Common Public License for more details.
|
||||
|
||||
You should have received a copy of the IBM Common Public
|
||||
License along with this library
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
|
||||
namespace Nuclex.Support.Collections {
|
||||
|
||||
/// <summary>Dictionary that can contain multiple values under the same key</summary>
|
||||
/// <typeparam name="TKey">Type of keys used within the dictionary</typeparam>
|
||||
/// <typeparam name="TValue">Type of values used within the dictionary</typeparam>
|
||||
public partial class MultiDictionary<TKey, TValue> : IMultiDictionary<TKey, TValue> {
|
||||
|
||||
#region class Enumerator
|
||||
|
||||
/// <summary>Enumerates the values stored in a multi dictionary</summary>
|
||||
private class Enumerator :
|
||||
IDictionaryEnumerator,
|
||||
IEnumerator<KeyValuePair<TKey, TValue>> {
|
||||
|
||||
/// <summary>Initializes a new multi dictionary enumerator</summary>
|
||||
/// <param name="dictionary">Dictionary that will be enumerated</param>
|
||||
public Enumerator(MultiDictionary<TKey, TValue> dictionary) {
|
||||
this.dictionary = dictionary;
|
||||
|
||||
Reset();
|
||||
}
|
||||
|
||||
/// <summary>The current entry the enumerator is pointing at</summary>
|
||||
public KeyValuePair<TKey, TValue> Current {
|
||||
get {
|
||||
if(this.currentValue == null) {
|
||||
throw new InvalidOperationException("Enumerator is not on a valid position");
|
||||
}
|
||||
|
||||
return new KeyValuePair<TKey, TValue>(
|
||||
this.currentCollection.Current.Key, this.currentValue.Current
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Immediately releases all resources owned by the instance</summary>
|
||||
public void Dispose() {
|
||||
if(this.currentValue != null) {
|
||||
this.currentValue.Dispose();
|
||||
this.currentValue = null;
|
||||
}
|
||||
if(this.currentCollection != null) {
|
||||
this.currentCollection.Dispose();
|
||||
this.currentCollection = null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Advances the enumerator to the entry</summary>
|
||||
/// <returns>
|
||||
/// True if there was a next entry, false if the end of the set has been reached
|
||||
/// </returns>
|
||||
public bool MoveNext() {
|
||||
if(this.currentCollection == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for(; ; ) {
|
||||
|
||||
// Try to move the enumerator in the current key's list to the next item
|
||||
if(this.currentValue != null) {
|
||||
if(this.currentValue.MoveNext()) {
|
||||
return true; // We found the next item
|
||||
} else {
|
||||
this.currentValue.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
// Enumerator for the current key's list reached the end, go to the next key
|
||||
if(this.currentCollection.MoveNext()) {
|
||||
this.currentValue = this.currentCollection.Current.Value.GetEnumerator();
|
||||
} else {
|
||||
this.currentValue = null; // Guaranteed to be disposed already
|
||||
this.currentCollection.Dispose();
|
||||
this.currentCollection = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Resets the enumerator to its initial position</summary>
|
||||
public void Reset() {
|
||||
if(this.currentValue != null) {
|
||||
this.currentValue.Dispose();
|
||||
this.currentValue = null;
|
||||
}
|
||||
if(this.currentCollection != null) {
|
||||
this.currentCollection.Dispose();
|
||||
}
|
||||
this.currentCollection = this.dictionary.GetEnumerator();
|
||||
}
|
||||
|
||||
#region IEnumerator implementation
|
||||
|
||||
/// <summary>The item the enumerator is currently pointing at</summary>
|
||||
object IEnumerator.Current {
|
||||
get { return Current; }
|
||||
}
|
||||
|
||||
#endregion // IEnumerator implementation
|
||||
|
||||
#region IDictionaryEnumerator implementation
|
||||
|
||||
/// <summary>The current entry the enumerator is pointing to</summary>
|
||||
DictionaryEntry IDictionaryEnumerator.Entry {
|
||||
get {
|
||||
enforceEnumeratorOnValidPosition();
|
||||
|
||||
return new DictionaryEntry(
|
||||
this.currentCollection.Current.Key, this.currentValue.Current
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>The current dictionary key</summary>
|
||||
object IDictionaryEnumerator.Key {
|
||||
get {
|
||||
enforceEnumeratorOnValidPosition();
|
||||
return this.currentCollection.Current.Key;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>The current dictionary value</summary>
|
||||
object IDictionaryEnumerator.Value {
|
||||
get {
|
||||
enforceEnumeratorOnValidPosition();
|
||||
return this.currentValue.Current;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion // IDictionaryEnumerator implementation
|
||||
|
||||
/// <summary>
|
||||
/// Throws an exception if the enumerator is not on a valid position
|
||||
/// </summary>
|
||||
private void enforceEnumeratorOnValidPosition() {
|
||||
if(this.currentValue == null) {
|
||||
throw new InvalidOperationException("Enumerator is not on a valid position");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Dictionary over whose entries the enumerator is enumerating</summary>
|
||||
private IDictionary<TKey, ICollection<TValue>> dictionary;
|
||||
/// <summary>Current key the enumerator is at</summary>
|
||||
private IEnumerator<KeyValuePair<TKey, ICollection<TValue>>> currentCollection;
|
||||
/// <summary>Current value in the current key the enumerator is at</summary>
|
||||
private IEnumerator<TValue> currentValue;
|
||||
|
||||
}
|
||||
|
||||
#endregion // class Enumerator
|
||||
|
||||
#region class ValueList
|
||||
|
||||
/// <summary>Stores the list of values for a dictionary key</summary>
|
||||
private class ValueList : Collection<TValue> {
|
||||
|
||||
/// <summary>Initializes a new value list</summary>
|
||||
/// <param name="dictionary">Dictionary the value list belongs to</param>
|
||||
public ValueList(MultiDictionary<TKey, TValue> dictionary) {
|
||||
this.dictionary = dictionary;
|
||||
}
|
||||
|
||||
/// <summary>Called when the value list is being cleared</summary>
|
||||
protected override void ClearItems() {
|
||||
this.dictionary.count -= Count;
|
||||
base.ClearItems();
|
||||
}
|
||||
|
||||
/// <summary>Called when an item is inserted into the value list</summary>
|
||||
/// <param name="index">Index at which the item is being inserted</param>
|
||||
/// <param name="item">Item that is being inserted</param>
|
||||
protected override void InsertItem(int index, TValue item) {
|
||||
base.InsertItem(index, item);
|
||||
++this.dictionary.count;
|
||||
}
|
||||
|
||||
/// <summary>Called when an item is removed from the value list</summary>
|
||||
/// <param name="index">Index at which the item is being removed</param>
|
||||
protected override void RemoveItem(int index) {
|
||||
base.RemoveItem(index);
|
||||
--this.dictionary.count;
|
||||
}
|
||||
|
||||
/// <summary>The dictionary the value list belongs to</summary>
|
||||
private MultiDictionary<TKey, TValue> dictionary;
|
||||
|
||||
}
|
||||
|
||||
#endregion // class ValueList
|
||||
|
||||
/// <summary>Initializes a new multi dictionary</summary>
|
||||
public MultiDictionary() : this(new Dictionary<TKey, ICollection<TValue>>()) { }
|
||||
|
||||
/// <summary>Initializes a new multi dictionary</summary>
|
||||
/// <param name="dictionary">Dictionary the multi dictionary will be based on</param>
|
||||
internal MultiDictionary(IDictionary<TKey, ICollection<TValue>> dictionary) {
|
||||
this.typedDictionary = dictionary;
|
||||
this.objectDictionary = (this.typedDictionary as IDictionary);
|
||||
|
||||
foreach(ICollection<TValue> values in dictionary.Values) {
|
||||
this.count += values.Count;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Whether the dictionary is write-protected</summary>
|
||||
public bool IsReadOnly {
|
||||
get { return this.typedDictionary.IsReadOnly; }
|
||||
}
|
||||
|
||||
/// <summary>Determines the number of values stored under the specified key</summary>
|
||||
/// <param name="key">Key whose values will be counted</param>
|
||||
/// <returns>The number of values stored under the specified key</returns>
|
||||
public int CountValues(TKey key) {
|
||||
ICollection<TValue> values;
|
||||
if(this.typedDictionary.TryGetValue(key, out values)) {
|
||||
return values.Count;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified KeyValuePair is contained in the dictionary
|
||||
/// </summary>
|
||||
/// <param name="item">KeyValuePair that will be checked for</param>
|
||||
/// <returns>True if the provided KeyValuePair was contained in the dictionary</returns>
|
||||
public bool Contains(KeyValuePair<TKey, TValue> item) {
|
||||
ICollection<TValue> values;
|
||||
if(this.typedDictionary.TryGetValue(item.Key, out values)) {
|
||||
return values.Contains(item.Value);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Determines whether the Dictionary contains the specified key</summary>
|
||||
/// <param name="key">Key that will be checked for</param>
|
||||
/// <returns>
|
||||
/// True if an entry with the specified key was contained in the Dictionary
|
||||
/// </returns>
|
||||
public bool ContainsKey(TKey key) {
|
||||
return this.typedDictionary.ContainsKey(key);
|
||||
}
|
||||
|
||||
/// <summary>Copies the contents of the Dictionary into an array</summary>
|
||||
/// <param name="array">Array the Dictionary will be copied into</param>
|
||||
/// <param name="arrayIndex">
|
||||
/// Starting index at which to begin filling the destination array
|
||||
/// </param>
|
||||
public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex) {
|
||||
foreach(KeyValuePair<TKey, ICollection<TValue>> item in this.typedDictionary) {
|
||||
foreach(TValue value in item.Value) {
|
||||
array[arrayIndex] = new KeyValuePair<TKey, TValue>(item.Key, value);
|
||||
++arrayIndex;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Number of elements contained in the multi dictionary</summary>
|
||||
public int Count {
|
||||
get { return this.count; }
|
||||
}
|
||||
|
||||
/// <summary>Creates a new enumerator for the dictionary</summary>
|
||||
/// <returns>The new dictionary enumerator</returns>
|
||||
public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator() {
|
||||
return new Enumerator(this);
|
||||
}
|
||||
|
||||
/// <summary>Collection of all keys contained in the dictionary</summary>
|
||||
public ICollection<TKey> Keys {
|
||||
get { return this.typedDictionary.Keys; }
|
||||
}
|
||||
|
||||
/// <summary>Collection of all values contained in the dictionary</summary>
|
||||
public ICollection<TValue> Values {
|
||||
get {
|
||||
if(this.valueCollection == null) {
|
||||
this.valueCollection = new ValueCollection(this);
|
||||
}
|
||||
|
||||
return this.valueCollection;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to retrieve the item with the specified key from the dictionary
|
||||
/// </summary>
|
||||
/// <param name="key">Key of the item to attempt to retrieve</param>
|
||||
/// <param name="values">
|
||||
/// Output parameter that will receive the values upon successful completion
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// True if the item was found and has been placed in the output parameter
|
||||
/// </returns>
|
||||
public bool TryGetValue(TKey key, out ICollection<TValue> values) {
|
||||
return this.typedDictionary.TryGetValue(key, out values);
|
||||
}
|
||||
|
||||
/// <summary>Accesses an item in the dictionary by its key</summary>
|
||||
/// <param name="key">Key of the item that will be accessed</param>
|
||||
public ICollection<TValue> this[TKey key] {
|
||||
get { return this.typedDictionary[key]; }
|
||||
set {
|
||||
if(value == null) {
|
||||
RemoveKey(key);
|
||||
} else {
|
||||
ICollection<TValue> currentValues;
|
||||
if(this.typedDictionary.TryGetValue(key, out currentValues)) {
|
||||
currentValues.Clear();
|
||||
} else {
|
||||
currentValues = new ValueList(this);
|
||||
this.typedDictionary.Add(key, currentValues);
|
||||
}
|
||||
foreach(TValue addedValue in value) {
|
||||
currentValues.Add(addedValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Inserts an item into the dictionary</summary>
|
||||
/// <param name="key">Key under which to add the new item</param>
|
||||
/// <param name="value">Item that will be added to the dictionary</param>
|
||||
public void Add(TKey key, TValue value) {
|
||||
ICollection<TValue> values;
|
||||
if(!this.typedDictionary.TryGetValue(key, out values)) {
|
||||
values = new ValueList(this);
|
||||
this.typedDictionary.Add(key, values);
|
||||
}
|
||||
|
||||
values.Add(value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes the item with the specified key and value from the dictionary
|
||||
/// </summary>
|
||||
/// <param name="key">Key of the item that will be removed</param>
|
||||
/// <param name="value">Value of the item that will be removed</param>
|
||||
/// <returns>
|
||||
/// True if the specified item was contained in the dictionary and was removed
|
||||
/// </returns>
|
||||
/// <exception cref="NotSupportedException">If the dictionary is read-only</exception>
|
||||
public bool Remove(TKey key, TValue value) {
|
||||
ICollection<TValue> values;
|
||||
if(this.typedDictionary.TryGetValue(key, out values)) {
|
||||
values.Remove(value);
|
||||
if(values.Count == 0) {
|
||||
this.typedDictionary.Remove(key);
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Removes all items with the specified key from the dictionary</summary>
|
||||
/// <param name="key">Key of the item that will be removed</param>
|
||||
/// <returns>The number of items that have been removed from the dictionary</returns>
|
||||
/// <exception cref="NotSupportedException">If the dictionary is read-only</exception>
|
||||
public int RemoveKey(TKey key) {
|
||||
ICollection<TValue> values;
|
||||
if(this.typedDictionary.TryGetValue(key, out values)) {
|
||||
this.count -= values.Count;
|
||||
this.typedDictionary.Remove(key);
|
||||
return values.Count;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Removes all items from the Dictionary</summary>
|
||||
public void Clear() {
|
||||
this.typedDictionary.Clear();
|
||||
this.count = 0;
|
||||
}
|
||||
|
||||
/// <summary>The wrapped Dictionary under its type-safe interface</summary>
|
||||
private IDictionary<TKey, ICollection<TValue>> typedDictionary;
|
||||
/// <summary>The wrapped Dictionary under its object interface</summary>
|
||||
private IDictionary objectDictionary;
|
||||
/// <summary>The number of items currently in the multi dictionary</summary>
|
||||
private int count;
|
||||
/// <summary>Provides the values stores in the dictionary in sequence</summary>
|
||||
private ValueCollection valueCollection;
|
||||
|
||||
}
|
||||
|
||||
} // namespace Nuclex.Support.Collections
|
||||
#region Apache License 2.0
|
||||
/*
|
||||
Nuclex .NET Framework
|
||||
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
#endregion // Apache License 2.0
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
|
||||
namespace Nuclex.Support.Collections {
|
||||
|
||||
/// <summary>Dictionary that can contain multiple values under the same key</summary>
|
||||
/// <typeparam name="TKey">Type of keys used within the dictionary</typeparam>
|
||||
/// <typeparam name="TValue">Type of values used within the dictionary</typeparam>
|
||||
public partial class MultiDictionary<TKey, TValue> : IMultiDictionary<TKey, TValue> {
|
||||
|
||||
#region class Enumerator
|
||||
|
||||
/// <summary>Enumerates the values stored in a multi dictionary</summary>
|
||||
private class Enumerator :
|
||||
IDictionaryEnumerator,
|
||||
IEnumerator<KeyValuePair<TKey, TValue>> {
|
||||
|
||||
/// <summary>Initializes a new multi dictionary enumerator</summary>
|
||||
/// <param name="dictionary">Dictionary that will be enumerated</param>
|
||||
public Enumerator(MultiDictionary<TKey, TValue> dictionary) {
|
||||
this.dictionary = dictionary;
|
||||
|
||||
Reset();
|
||||
}
|
||||
|
||||
/// <summary>The current entry the enumerator is pointing at</summary>
|
||||
public KeyValuePair<TKey, TValue> Current {
|
||||
get {
|
||||
if(this.currentValue == null) {
|
||||
throw new InvalidOperationException("Enumerator is not on a valid position");
|
||||
}
|
||||
|
||||
return new KeyValuePair<TKey, TValue>(
|
||||
this.currentCollection.Current.Key, this.currentValue.Current
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Immediately releases all resources owned by the instance</summary>
|
||||
public void Dispose() {
|
||||
if(this.currentValue != null) {
|
||||
this.currentValue.Dispose();
|
||||
this.currentValue = null;
|
||||
}
|
||||
if(this.currentCollection != null) {
|
||||
this.currentCollection.Dispose();
|
||||
this.currentCollection = null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Advances the enumerator to the entry</summary>
|
||||
/// <returns>
|
||||
/// True if there was a next entry, false if the end of the set has been reached
|
||||
/// </returns>
|
||||
public bool MoveNext() {
|
||||
if(this.currentCollection == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for(; ; ) {
|
||||
|
||||
// Try to move the enumerator in the current key's list to the next item
|
||||
if(this.currentValue != null) {
|
||||
if(this.currentValue.MoveNext()) {
|
||||
return true; // We found the next item
|
||||
} else {
|
||||
this.currentValue.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
// Enumerator for the current key's list reached the end, go to the next key
|
||||
if(this.currentCollection.MoveNext()) {
|
||||
this.currentValue = this.currentCollection.Current.Value.GetEnumerator();
|
||||
} else {
|
||||
this.currentValue = null; // Guaranteed to be disposed already
|
||||
this.currentCollection.Dispose();
|
||||
this.currentCollection = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Resets the enumerator to its initial position</summary>
|
||||
public void Reset() {
|
||||
if(this.currentValue != null) {
|
||||
this.currentValue.Dispose();
|
||||
this.currentValue = null;
|
||||
}
|
||||
if(this.currentCollection != null) {
|
||||
this.currentCollection.Dispose();
|
||||
}
|
||||
this.currentCollection = this.dictionary.GetEnumerator();
|
||||
}
|
||||
|
||||
#region IEnumerator implementation
|
||||
|
||||
/// <summary>The item the enumerator is currently pointing at</summary>
|
||||
object IEnumerator.Current {
|
||||
get { return Current; }
|
||||
}
|
||||
|
||||
#endregion // IEnumerator implementation
|
||||
|
||||
#region IDictionaryEnumerator implementation
|
||||
|
||||
/// <summary>The current entry the enumerator is pointing to</summary>
|
||||
DictionaryEntry IDictionaryEnumerator.Entry {
|
||||
get {
|
||||
enforceEnumeratorOnValidPosition();
|
||||
|
||||
return new DictionaryEntry(
|
||||
this.currentCollection.Current.Key, this.currentValue.Current
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>The current dictionary key</summary>
|
||||
object IDictionaryEnumerator.Key {
|
||||
get {
|
||||
enforceEnumeratorOnValidPosition();
|
||||
return this.currentCollection.Current.Key;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>The current dictionary value</summary>
|
||||
object IDictionaryEnumerator.Value {
|
||||
get {
|
||||
enforceEnumeratorOnValidPosition();
|
||||
return this.currentValue.Current;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion // IDictionaryEnumerator implementation
|
||||
|
||||
/// <summary>
|
||||
/// Throws an exception if the enumerator is not on a valid position
|
||||
/// </summary>
|
||||
private void enforceEnumeratorOnValidPosition() {
|
||||
if(this.currentValue == null) {
|
||||
throw new InvalidOperationException("Enumerator is not on a valid position");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Dictionary over whose entries the enumerator is enumerating</summary>
|
||||
private IDictionary<TKey, ICollection<TValue>> dictionary;
|
||||
/// <summary>Current key the enumerator is at</summary>
|
||||
private IEnumerator<KeyValuePair<TKey, ICollection<TValue>>> currentCollection;
|
||||
/// <summary>Current value in the current key the enumerator is at</summary>
|
||||
private IEnumerator<TValue> currentValue;
|
||||
|
||||
}
|
||||
|
||||
#endregion // class Enumerator
|
||||
|
||||
#region class ValueList
|
||||
|
||||
/// <summary>Stores the list of values for a dictionary key</summary>
|
||||
private class ValueList : Collection<TValue> {
|
||||
|
||||
/// <summary>Initializes a new value list</summary>
|
||||
/// <param name="dictionary">Dictionary the value list belongs to</param>
|
||||
public ValueList(MultiDictionary<TKey, TValue> dictionary) {
|
||||
this.dictionary = dictionary;
|
||||
}
|
||||
|
||||
/// <summary>Called when the value list is being cleared</summary>
|
||||
protected override void ClearItems() {
|
||||
this.dictionary.count -= Count;
|
||||
base.ClearItems();
|
||||
}
|
||||
|
||||
/// <summary>Called when an item is inserted into the value list</summary>
|
||||
/// <param name="index">Index at which the item is being inserted</param>
|
||||
/// <param name="item">Item that is being inserted</param>
|
||||
protected override void InsertItem(int index, TValue item) {
|
||||
base.InsertItem(index, item);
|
||||
++this.dictionary.count;
|
||||
}
|
||||
|
||||
/// <summary>Called when an item is removed from the value list</summary>
|
||||
/// <param name="index">Index at which the item is being removed</param>
|
||||
protected override void RemoveItem(int index) {
|
||||
base.RemoveItem(index);
|
||||
--this.dictionary.count;
|
||||
}
|
||||
|
||||
/// <summary>The dictionary the value list belongs to</summary>
|
||||
private MultiDictionary<TKey, TValue> dictionary;
|
||||
|
||||
}
|
||||
|
||||
#endregion // class ValueList
|
||||
|
||||
/// <summary>Initializes a new multi dictionary</summary>
|
||||
public MultiDictionary() : this(new Dictionary<TKey, ICollection<TValue>>()) { }
|
||||
|
||||
/// <summary>Initializes a new multi dictionary</summary>
|
||||
/// <param name="dictionary">Dictionary the multi dictionary will be based on</param>
|
||||
internal MultiDictionary(IDictionary<TKey, ICollection<TValue>> dictionary) {
|
||||
this.typedDictionary = dictionary;
|
||||
this.objectDictionary = (this.typedDictionary as IDictionary);
|
||||
|
||||
foreach(ICollection<TValue> values in dictionary.Values) {
|
||||
this.count += values.Count;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Whether the dictionary is write-protected</summary>
|
||||
public bool IsReadOnly {
|
||||
get { return this.typedDictionary.IsReadOnly; }
|
||||
}
|
||||
|
||||
/// <summary>Determines the number of values stored under the specified key</summary>
|
||||
/// <param name="key">Key whose values will be counted</param>
|
||||
/// <returns>The number of values stored under the specified key</returns>
|
||||
public int CountValues(TKey key) {
|
||||
ICollection<TValue> values;
|
||||
if(this.typedDictionary.TryGetValue(key, out values)) {
|
||||
return values.Count;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified KeyValuePair is contained in the dictionary
|
||||
/// </summary>
|
||||
/// <param name="item">KeyValuePair that will be checked for</param>
|
||||
/// <returns>True if the provided KeyValuePair was contained in the dictionary</returns>
|
||||
public bool Contains(KeyValuePair<TKey, TValue> item) {
|
||||
ICollection<TValue> values;
|
||||
if(this.typedDictionary.TryGetValue(item.Key, out values)) {
|
||||
return values.Contains(item.Value);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Determines whether the Dictionary contains the specified key</summary>
|
||||
/// <param name="key">Key that will be checked for</param>
|
||||
/// <returns>
|
||||
/// True if an entry with the specified key was contained in the Dictionary
|
||||
/// </returns>
|
||||
public bool ContainsKey(TKey key) {
|
||||
return this.typedDictionary.ContainsKey(key);
|
||||
}
|
||||
|
||||
/// <summary>Copies the contents of the Dictionary into an array</summary>
|
||||
/// <param name="array">Array the Dictionary will be copied into</param>
|
||||
/// <param name="arrayIndex">
|
||||
/// Starting index at which to begin filling the destination array
|
||||
/// </param>
|
||||
public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex) {
|
||||
foreach(KeyValuePair<TKey, ICollection<TValue>> item in this.typedDictionary) {
|
||||
foreach(TValue value in item.Value) {
|
||||
array[arrayIndex] = new KeyValuePair<TKey, TValue>(item.Key, value);
|
||||
++arrayIndex;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Number of elements contained in the multi dictionary</summary>
|
||||
public int Count {
|
||||
get { return this.count; }
|
||||
}
|
||||
|
||||
/// <summary>Creates a new enumerator for the dictionary</summary>
|
||||
/// <returns>The new dictionary enumerator</returns>
|
||||
public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator() {
|
||||
return new Enumerator(this);
|
||||
}
|
||||
|
||||
/// <summary>Collection of all keys contained in the dictionary</summary>
|
||||
public ICollection<TKey> Keys {
|
||||
get { return this.typedDictionary.Keys; }
|
||||
}
|
||||
|
||||
/// <summary>Collection of all values contained in the dictionary</summary>
|
||||
public ICollection<TValue> Values {
|
||||
get {
|
||||
if(this.valueCollection == null) {
|
||||
this.valueCollection = new ValueCollection(this);
|
||||
}
|
||||
|
||||
return this.valueCollection;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to retrieve the item with the specified key from the dictionary
|
||||
/// </summary>
|
||||
/// <param name="key">Key of the item to attempt to retrieve</param>
|
||||
/// <param name="values">
|
||||
/// Output parameter that will receive the values upon successful completion
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// True if the item was found and has been placed in the output parameter
|
||||
/// </returns>
|
||||
public bool TryGetValue(TKey key, out ICollection<TValue> values) {
|
||||
return this.typedDictionary.TryGetValue(key, out values);
|
||||
}
|
||||
|
||||
/// <summary>Accesses an item in the dictionary by its key</summary>
|
||||
/// <param name="key">Key of the item that will be accessed</param>
|
||||
public ICollection<TValue> this[TKey key] {
|
||||
get { return this.typedDictionary[key]; }
|
||||
set {
|
||||
if(value == null) {
|
||||
RemoveKey(key);
|
||||
} else {
|
||||
ICollection<TValue> currentValues;
|
||||
if(this.typedDictionary.TryGetValue(key, out currentValues)) {
|
||||
currentValues.Clear();
|
||||
} else {
|
||||
currentValues = new ValueList(this);
|
||||
this.typedDictionary.Add(key, currentValues);
|
||||
}
|
||||
foreach(TValue addedValue in value) {
|
||||
currentValues.Add(addedValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Inserts an item into the dictionary</summary>
|
||||
/// <param name="key">Key under which to add the new item</param>
|
||||
/// <param name="value">Item that will be added to the dictionary</param>
|
||||
public void Add(TKey key, TValue value) {
|
||||
ICollection<TValue> values;
|
||||
if(!this.typedDictionary.TryGetValue(key, out values)) {
|
||||
values = new ValueList(this);
|
||||
this.typedDictionary.Add(key, values);
|
||||
}
|
||||
|
||||
values.Add(value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes the item with the specified key and value from the dictionary
|
||||
/// </summary>
|
||||
/// <param name="key">Key of the item that will be removed</param>
|
||||
/// <param name="value">Value of the item that will be removed</param>
|
||||
/// <returns>
|
||||
/// True if the specified item was contained in the dictionary and was removed
|
||||
/// </returns>
|
||||
/// <exception cref="NotSupportedException">If the dictionary is read-only</exception>
|
||||
public bool Remove(TKey key, TValue value) {
|
||||
ICollection<TValue> values;
|
||||
if(this.typedDictionary.TryGetValue(key, out values)) {
|
||||
values.Remove(value);
|
||||
if(values.Count == 0) {
|
||||
this.typedDictionary.Remove(key);
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Removes all items with the specified key from the dictionary</summary>
|
||||
/// <param name="key">Key of the item that will be removed</param>
|
||||
/// <returns>The number of items that have been removed from the dictionary</returns>
|
||||
/// <exception cref="NotSupportedException">If the dictionary is read-only</exception>
|
||||
public int RemoveKey(TKey key) {
|
||||
ICollection<TValue> values;
|
||||
if(this.typedDictionary.TryGetValue(key, out values)) {
|
||||
this.count -= values.Count;
|
||||
this.typedDictionary.Remove(key);
|
||||
return values.Count;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Removes all items from the Dictionary</summary>
|
||||
public void Clear() {
|
||||
this.typedDictionary.Clear();
|
||||
this.count = 0;
|
||||
}
|
||||
|
||||
/// <summary>The wrapped Dictionary under its type-safe interface</summary>
|
||||
private IDictionary<TKey, ICollection<TValue>> typedDictionary;
|
||||
/// <summary>The wrapped Dictionary under its object interface</summary>
|
||||
private IDictionary objectDictionary;
|
||||
/// <summary>The number of items currently in the multi dictionary</summary>
|
||||
private int count;
|
||||
/// <summary>Provides the values stores in the dictionary in sequence</summary>
|
||||
private ValueCollection valueCollection;
|
||||
|
||||
}
|
||||
|
||||
} // namespace Nuclex.Support.Collections
|
||||
|
|
|
|||
|
|
@ -1,145 +1,144 @@
|
|||
#region CPL License
|
||||
/*
|
||||
Nuclex Framework
|
||||
Copyright (C) 2002-2017 Nuclex Development Labs
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the IBM Common Public License as
|
||||
published by the IBM Corporation; either version 1.0 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
IBM Common Public License for more details.
|
||||
|
||||
You should have received a copy of the IBM Common Public
|
||||
License along with this library
|
||||
*/
|
||||
#endregion
|
||||
|
||||
#if !NO_NMOCK
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
#if UNITTEST
|
||||
|
||||
using NUnit.Framework;
|
||||
using NMock;
|
||||
|
||||
namespace Nuclex.Support.Collections {
|
||||
|
||||
/// <summary>Unit Test for the observable collection class</summary>
|
||||
[TestFixture]
|
||||
internal class ObservableCollectionTest {
|
||||
|
||||
#region interface IObservableCollectionSubscriber
|
||||
|
||||
/// <summary>Interface used to test the observable collection</summary>
|
||||
public interface IObservableCollectionSubscriber {
|
||||
|
||||
/// <summary>Called when the collection is about to clear its contents</summary>
|
||||
/// <param name="sender">Collection that is clearing its contents</param>
|
||||
/// <param name="arguments">Not used</param>
|
||||
void Clearing(object sender, EventArgs arguments);
|
||||
|
||||
/// <summary>Called when the collection has been cleared of its contents</summary>
|
||||
/// <param name="sender">Collection that was cleared of its contents</param>
|
||||
/// <param name="arguments">Not used</param>
|
||||
void Cleared(object sender, EventArgs arguments);
|
||||
|
||||
/// <summary>Called when an item is added to the collection</summary>
|
||||
/// <param name="sender">Collection to which an item is being added</param>
|
||||
/// <param name="arguments">Contains the item that is being added</param>
|
||||
void ItemAdded(object sender, ItemEventArgs<int> arguments);
|
||||
|
||||
/// <summary>Called when an item is removed from the collection</summary>
|
||||
/// <param name="sender">Collection from which an item is being removed</param>
|
||||
/// <param name="arguments">Contains the item that is being removed</param>
|
||||
void ItemRemoved(object sender, ItemEventArgs<int> arguments);
|
||||
|
||||
}
|
||||
|
||||
#endregion // interface IObservableCollectionSubscriber
|
||||
|
||||
/// <summary>Initialization routine executed before each test is run</summary>
|
||||
[SetUp]
|
||||
public void Setup() {
|
||||
this.mockery = new MockFactory();
|
||||
|
||||
this.mockedSubscriber = this.mockery.CreateMock<IObservableCollectionSubscriber>();
|
||||
|
||||
this.observedCollection = new ObservableCollection<int>();
|
||||
this.observedCollection.Clearing += new EventHandler(
|
||||
this.mockedSubscriber.MockObject.Clearing
|
||||
);
|
||||
this.observedCollection.Cleared += new EventHandler(
|
||||
this.mockedSubscriber.MockObject.Cleared
|
||||
);
|
||||
this.observedCollection.ItemAdded += new EventHandler<ItemEventArgs<int>>(
|
||||
this.mockedSubscriber.MockObject.ItemAdded
|
||||
);
|
||||
this.observedCollection.ItemRemoved += new EventHandler<ItemEventArgs<int>>(
|
||||
this.mockedSubscriber.MockObject.ItemRemoved
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>Tests whether the Clearing event is fired</summary>
|
||||
[Test]
|
||||
public void TestClearingEvent() {
|
||||
this.mockedSubscriber.Expects.One.Method(m => m.Clearing(null, null)).WithAnyArguments();
|
||||
this.mockedSubscriber.Expects.One.Method(m => m.Cleared(null, null)).WithAnyArguments();
|
||||
this.observedCollection.Clear();
|
||||
|
||||
this.mockery.VerifyAllExpectationsHaveBeenMet();
|
||||
}
|
||||
|
||||
/// <summary>Tests whether the ItemAdded event is fired</summary>
|
||||
[Test]
|
||||
public void TestItemAddedEvent() {
|
||||
this.mockedSubscriber.Expects.One.Method(m => m.ItemAdded(null, null)).WithAnyArguments();
|
||||
|
||||
this.observedCollection.Add(123);
|
||||
|
||||
this.mockery.VerifyAllExpectationsHaveBeenMet();
|
||||
}
|
||||
|
||||
/// <summary>Tests whether the ItemRemoved event is fired</summary>
|
||||
[Test]
|
||||
public void TestItemRemovedEvent() {
|
||||
this.mockedSubscriber.Expects.One.Method(m => m.ItemAdded(null, null)).WithAnyArguments();
|
||||
|
||||
this.observedCollection.Add(123);
|
||||
|
||||
this.mockedSubscriber.Expects.One.Method(m => m.ItemRemoved(null, null)).WithAnyArguments();
|
||||
|
||||
this.observedCollection.Remove(123);
|
||||
|
||||
this.mockery.VerifyAllExpectationsHaveBeenMet();
|
||||
}
|
||||
|
||||
/// <summary>Tests whether a the list constructor is working</summary>
|
||||
[Test]
|
||||
public void TestListConstructor() {
|
||||
int[] integers = new int[] { 12, 34, 56, 78 };
|
||||
|
||||
var testCollection = new ObservableCollection<int>(integers);
|
||||
|
||||
CollectionAssert.AreEqual(integers, testCollection);
|
||||
}
|
||||
|
||||
/// <summary>Mock object factory</summary>
|
||||
private MockFactory mockery;
|
||||
/// <summary>The mocked observable collection subscriber</summary>
|
||||
private Mock<IObservableCollectionSubscriber> mockedSubscriber;
|
||||
/// <summary>An observable collection to which a mock will be subscribed</summary>
|
||||
private ObservableCollection<int> observedCollection;
|
||||
|
||||
}
|
||||
|
||||
} // namespace Nuclex.Support.Collections
|
||||
|
||||
#endif // UNITTEST
|
||||
|
||||
#endif // !NO_NMOCK
|
||||
#region Apache License 2.0
|
||||
/*
|
||||
Nuclex .NET Framework
|
||||
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
#endregion // Apache License 2.0
|
||||
|
||||
#if !NO_NMOCK
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
#if UNITTEST
|
||||
|
||||
using NUnit.Framework;
|
||||
using NMock;
|
||||
|
||||
namespace Nuclex.Support.Collections {
|
||||
|
||||
/// <summary>Unit Test for the observable collection class</summary>
|
||||
[TestFixture]
|
||||
internal class ObservableCollectionTest {
|
||||
|
||||
#region interface IObservableCollectionSubscriber
|
||||
|
||||
/// <summary>Interface used to test the observable collection</summary>
|
||||
public interface IObservableCollectionSubscriber {
|
||||
|
||||
/// <summary>Called when the collection is about to clear its contents</summary>
|
||||
/// <param name="sender">Collection that is clearing its contents</param>
|
||||
/// <param name="arguments">Not used</param>
|
||||
void Clearing(object sender, EventArgs arguments);
|
||||
|
||||
/// <summary>Called when the collection has been cleared of its contents</summary>
|
||||
/// <param name="sender">Collection that was cleared of its contents</param>
|
||||
/// <param name="arguments">Not used</param>
|
||||
void Cleared(object sender, EventArgs arguments);
|
||||
|
||||
/// <summary>Called when an item is added to the collection</summary>
|
||||
/// <param name="sender">Collection to which an item is being added</param>
|
||||
/// <param name="arguments">Contains the item that is being added</param>
|
||||
void ItemAdded(object sender, ItemEventArgs<int> arguments);
|
||||
|
||||
/// <summary>Called when an item is removed from the collection</summary>
|
||||
/// <param name="sender">Collection from which an item is being removed</param>
|
||||
/// <param name="arguments">Contains the item that is being removed</param>
|
||||
void ItemRemoved(object sender, ItemEventArgs<int> arguments);
|
||||
|
||||
}
|
||||
|
||||
#endregion // interface IObservableCollectionSubscriber
|
||||
|
||||
/// <summary>Initialization routine executed before each test is run</summary>
|
||||
[SetUp]
|
||||
public void Setup() {
|
||||
this.mockery = new MockFactory();
|
||||
|
||||
this.mockedSubscriber = this.mockery.CreateMock<IObservableCollectionSubscriber>();
|
||||
|
||||
this.observedCollection = new ObservableCollection<int>();
|
||||
this.observedCollection.Clearing += new EventHandler(
|
||||
this.mockedSubscriber.MockObject.Clearing
|
||||
);
|
||||
this.observedCollection.Cleared += new EventHandler(
|
||||
this.mockedSubscriber.MockObject.Cleared
|
||||
);
|
||||
this.observedCollection.ItemAdded += new EventHandler<ItemEventArgs<int>>(
|
||||
this.mockedSubscriber.MockObject.ItemAdded
|
||||
);
|
||||
this.observedCollection.ItemRemoved += new EventHandler<ItemEventArgs<int>>(
|
||||
this.mockedSubscriber.MockObject.ItemRemoved
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>Tests whether the Clearing event is fired</summary>
|
||||
[Test]
|
||||
public void TestClearingEvent() {
|
||||
this.mockedSubscriber.Expects.One.Method(m => m.Clearing(null, null)).WithAnyArguments();
|
||||
this.mockedSubscriber.Expects.One.Method(m => m.Cleared(null, null)).WithAnyArguments();
|
||||
this.observedCollection.Clear();
|
||||
|
||||
this.mockery.VerifyAllExpectationsHaveBeenMet();
|
||||
}
|
||||
|
||||
/// <summary>Tests whether the ItemAdded event is fired</summary>
|
||||
[Test]
|
||||
public void TestItemAddedEvent() {
|
||||
this.mockedSubscriber.Expects.One.Method(m => m.ItemAdded(null, null)).WithAnyArguments();
|
||||
|
||||
this.observedCollection.Add(123);
|
||||
|
||||
this.mockery.VerifyAllExpectationsHaveBeenMet();
|
||||
}
|
||||
|
||||
/// <summary>Tests whether the ItemRemoved event is fired</summary>
|
||||
[Test]
|
||||
public void TestItemRemovedEvent() {
|
||||
this.mockedSubscriber.Expects.One.Method(m => m.ItemAdded(null, null)).WithAnyArguments();
|
||||
|
||||
this.observedCollection.Add(123);
|
||||
|
||||
this.mockedSubscriber.Expects.One.Method(m => m.ItemRemoved(null, null)).WithAnyArguments();
|
||||
|
||||
this.observedCollection.Remove(123);
|
||||
|
||||
this.mockery.VerifyAllExpectationsHaveBeenMet();
|
||||
}
|
||||
|
||||
/// <summary>Tests whether a the list constructor is working</summary>
|
||||
[Test]
|
||||
public void TestListConstructor() {
|
||||
int[] integers = new int[] { 12, 34, 56, 78 };
|
||||
|
||||
var testCollection = new ObservableCollection<int>(integers);
|
||||
|
||||
CollectionAssert.AreEqual(integers, testCollection);
|
||||
}
|
||||
|
||||
/// <summary>Mock object factory</summary>
|
||||
private MockFactory mockery;
|
||||
/// <summary>The mocked observable collection subscriber</summary>
|
||||
private Mock<IObservableCollectionSubscriber> mockedSubscriber;
|
||||
/// <summary>An observable collection to which a mock will be subscribed</summary>
|
||||
private ObservableCollection<int> observedCollection;
|
||||
|
||||
}
|
||||
|
||||
} // namespace Nuclex.Support.Collections
|
||||
|
||||
#endif // UNITTEST
|
||||
|
||||
#endif // !NO_NMOCK
|
||||
|
|
|
|||
|
|
@ -1,231 +1,230 @@
|
|||
#region CPL License
|
||||
/*
|
||||
Nuclex Framework
|
||||
Copyright (C) 2002-2017 Nuclex Development Labs
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the IBM Common Public License as
|
||||
published by the IBM Corporation; either version 1.0 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
IBM Common Public License for more details.
|
||||
|
||||
You should have received a copy of the IBM Common Public
|
||||
License along with this library
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
#if !NO_SPECIALIZED_COLLECTIONS
|
||||
using System.Collections.Specialized;
|
||||
#endif
|
||||
|
||||
namespace Nuclex.Support.Collections {
|
||||
|
||||
/// <summary>Collection which fires events when items are added or removed</summary>
|
||||
/// <typeparam name="TItem">Type of items the collection manages</typeparam>
|
||||
public class ObservableCollection<TItem> :
|
||||
ICollection<TItem>,
|
||||
ICollection,
|
||||
#if !NO_SPECIALIZED_COLLECTIONS
|
||||
INotifyCollectionChanged,
|
||||
#endif
|
||||
IObservableCollection<TItem> {
|
||||
|
||||
/// <summary>Raised when an item has been added to the collection</summary>
|
||||
public event EventHandler<ItemEventArgs<TItem>> ItemAdded;
|
||||
/// <summary>Raised when an item is removed from the collection</summary>
|
||||
public event EventHandler<ItemEventArgs<TItem>> ItemRemoved;
|
||||
/// <summary>Raised when an item is replaced in the collection</summary>
|
||||
public event EventHandler<ItemReplaceEventArgs<TItem>> ItemReplaced {
|
||||
add { }
|
||||
remove { }
|
||||
}
|
||||
/// <summary>Raised when the collection is about to be cleared</summary>
|
||||
/// <remarks>
|
||||
/// This could be covered by calling ItemRemoved for each item currently
|
||||
/// contained in the collection, but it is often simpler and more efficient
|
||||
/// to process the clearing of the entire collection as a special operation.
|
||||
/// </remarks>
|
||||
public event EventHandler Clearing;
|
||||
/// <summary>Raised when the collection has been cleared</summary>
|
||||
public event EventHandler Cleared;
|
||||
|
||||
#if !NO_SPECIALIZED_COLLECTIONS
|
||||
/// <summary>Called when the collection has changed</summary>
|
||||
public event NotifyCollectionChangedEventHandler CollectionChanged;
|
||||
#endif
|
||||
|
||||
/// <summary>Initializes a new ObservableCollection with no items</summary>
|
||||
public ObservableCollection() : this(new Collection<TItem>()) { }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new ObservableCollection as a wrapper for an existing collection
|
||||
/// </summary>
|
||||
/// <param name="collection">Collection that will be wrapped</param>
|
||||
/// <exception cref="System.ArgumentNullException">List is null</exception>
|
||||
public ObservableCollection(ICollection<TItem> collection) {
|
||||
this.typedCollection = collection;
|
||||
this.objectCollection = (collection as ICollection);
|
||||
}
|
||||
|
||||
/// <summary>Removes all elements from the Collection</summary>
|
||||
public void Clear() {
|
||||
OnClearing();
|
||||
this.typedCollection.Clear();
|
||||
OnCleared();
|
||||
}
|
||||
|
||||
/// <summary>Adds an item to the collection</summary>
|
||||
/// <param name="item">Collection an item will be added to</param>
|
||||
public void Add(TItem item) {
|
||||
this.typedCollection.Add(item);
|
||||
OnAdded(item);
|
||||
}
|
||||
|
||||
/// <summary>Determines whether the collection contains the specified item</summary>
|
||||
/// <param name="item">Item the collection will be searched for</param>
|
||||
/// <returns>
|
||||
/// True if the collection contains the specified item, false otherwise
|
||||
/// </returns>
|
||||
public bool Contains(TItem item) {
|
||||
return this.typedCollection.Contains(item);
|
||||
}
|
||||
|
||||
/// <summary>Copies the contents of the collection into an array</summary>
|
||||
/// <param name="array">Array the collection's contents will be copied into</param>
|
||||
/// <param name="arrayIndex">
|
||||
/// Index in the array where the collection's first item will be placed
|
||||
/// </param>
|
||||
public void CopyTo(TItem[] array, int arrayIndex) {
|
||||
this.typedCollection.CopyTo(array, arrayIndex);
|
||||
}
|
||||
|
||||
/// <summary>The total number of items currently in the collection</summary>
|
||||
public int Count {
|
||||
get { return this.typedCollection.Count; }
|
||||
}
|
||||
|
||||
/// <summary>Whether the collection is read-only</summary>
|
||||
public bool IsReadOnly {
|
||||
get { return this.typedCollection.IsReadOnly; }
|
||||
}
|
||||
|
||||
/// <summary>Removes an item from the collection</summary>
|
||||
/// <param name="item">Item that will be removed from the collection</param>
|
||||
/// <returns>True if the item was found and removed, false otherwise</returns>
|
||||
public bool Remove(TItem item) {
|
||||
bool wasRemoved = this.typedCollection.Remove(item);
|
||||
if(wasRemoved) {
|
||||
OnRemoved(item);
|
||||
}
|
||||
|
||||
return wasRemoved;
|
||||
}
|
||||
|
||||
/// <summary>Returns an enumerator for the items in the collection</summary>
|
||||
/// <returns>An enumeration for the items in the collection</returns>
|
||||
public IEnumerator<TItem> GetEnumerator() {
|
||||
return this.typedCollection.GetEnumerator();
|
||||
}
|
||||
|
||||
/// <summary>Fires the 'ItemAdded' event</summary>
|
||||
/// <param name="item">Item that has been added to the collection</param>
|
||||
protected virtual void OnAdded(TItem item) {
|
||||
if(ItemAdded != null) {
|
||||
ItemAdded(this, new ItemEventArgs<TItem>(item));
|
||||
}
|
||||
#if !NO_SPECIALIZED_COLLECTIONS
|
||||
if(CollectionChanged != null) {
|
||||
CollectionChanged(
|
||||
this,
|
||||
new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item)
|
||||
);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>Fires the 'ItemRemoved' event</summary>
|
||||
/// <param name="item">Item that has been removed from the collection</param>
|
||||
protected virtual void OnRemoved(TItem item) {
|
||||
if(ItemRemoved != null) {
|
||||
ItemRemoved(this, new ItemEventArgs<TItem>(item));
|
||||
}
|
||||
#if !NO_SPECIALIZED_COLLECTIONS
|
||||
if(CollectionChanged != null) {
|
||||
CollectionChanged(
|
||||
this,
|
||||
new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, item)
|
||||
);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>Fires the 'Clearing' event</summary>
|
||||
protected virtual void OnClearing() {
|
||||
if(Clearing != null) {
|
||||
Clearing(this, EventArgs.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Fires the 'Cleared' event</summary>
|
||||
protected virtual void OnCleared() {
|
||||
if(Cleared != null) {
|
||||
Cleared(this, EventArgs.Empty);
|
||||
}
|
||||
#if !NO_SPECIALIZED_COLLECTIONS
|
||||
if(CollectionChanged != null) {
|
||||
CollectionChanged(this, Constants.NotifyCollectionResetEventArgs);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#region IEnumerable implementation
|
||||
|
||||
/// <summary>Returns an enumerator for the items in the collection</summary>
|
||||
/// <returns>An enumeration for the items in the collection</returns>
|
||||
IEnumerator IEnumerable.GetEnumerator() {
|
||||
return this.objectCollection.GetEnumerator();
|
||||
}
|
||||
|
||||
#endregion // IEnumerable implementation
|
||||
|
||||
#region ICollection implementation
|
||||
|
||||
/// <summary>Copies the contents of the collection into an array</summary>
|
||||
/// <param name="array">Array the collection's contents will be copied into</param>
|
||||
/// <param name="arrayIndex">
|
||||
/// Index in the array where the collection's first item will be placed
|
||||
/// </param>
|
||||
void ICollection.CopyTo(Array array, int arrayIndex) {
|
||||
this.objectCollection.CopyTo(array, arrayIndex);
|
||||
}
|
||||
|
||||
/// <summary>Whether the collection synchronizes accesses from multiple threads</summary>
|
||||
bool ICollection.IsSynchronized {
|
||||
get { return this.objectCollection.IsSynchronized; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Synchronization root used to synchronize threads accessing the collection
|
||||
/// </summary>
|
||||
object ICollection.SyncRoot {
|
||||
get { return this.objectCollection.SyncRoot; }
|
||||
}
|
||||
|
||||
#endregion // IEnumerable implementation
|
||||
|
||||
/// <summary>The wrapped collection under its type-safe interface</summary>
|
||||
private ICollection<TItem> typedCollection;
|
||||
/// <summary>The wrapped collection under its object interface</summary>
|
||||
private ICollection objectCollection;
|
||||
|
||||
}
|
||||
|
||||
} // namespace Nuclex.Support.Collections
|
||||
#region Apache License 2.0
|
||||
/*
|
||||
Nuclex .NET Framework
|
||||
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
#endregion // Apache License 2.0
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
#if !NO_SPECIALIZED_COLLECTIONS
|
||||
using System.Collections.Specialized;
|
||||
#endif
|
||||
|
||||
namespace Nuclex.Support.Collections {
|
||||
|
||||
/// <summary>Collection which fires events when items are added or removed</summary>
|
||||
/// <typeparam name="TItem">Type of items the collection manages</typeparam>
|
||||
public class ObservableCollection<TItem> :
|
||||
ICollection<TItem>,
|
||||
ICollection,
|
||||
#if !NO_SPECIALIZED_COLLECTIONS
|
||||
INotifyCollectionChanged,
|
||||
#endif
|
||||
IObservableCollection<TItem> {
|
||||
|
||||
/// <summary>Raised when an item has been added to the collection</summary>
|
||||
public event EventHandler<ItemEventArgs<TItem>> ItemAdded;
|
||||
/// <summary>Raised when an item is removed from the collection</summary>
|
||||
public event EventHandler<ItemEventArgs<TItem>> ItemRemoved;
|
||||
/// <summary>Raised when an item is replaced in the collection</summary>
|
||||
public event EventHandler<ItemReplaceEventArgs<TItem>> ItemReplaced {
|
||||
add { }
|
||||
remove { }
|
||||
}
|
||||
/// <summary>Raised when the collection is about to be cleared</summary>
|
||||
/// <remarks>
|
||||
/// This could be covered by calling ItemRemoved for each item currently
|
||||
/// contained in the collection, but it is often simpler and more efficient
|
||||
/// to process the clearing of the entire collection as a special operation.
|
||||
/// </remarks>
|
||||
public event EventHandler Clearing;
|
||||
/// <summary>Raised when the collection has been cleared</summary>
|
||||
public event EventHandler Cleared;
|
||||
|
||||
#if !NO_SPECIALIZED_COLLECTIONS
|
||||
/// <summary>Called when the collection has changed</summary>
|
||||
public event NotifyCollectionChangedEventHandler CollectionChanged;
|
||||
#endif
|
||||
|
||||
/// <summary>Initializes a new ObservableCollection with no items</summary>
|
||||
public ObservableCollection() : this(new Collection<TItem>()) { }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new ObservableCollection as a wrapper for an existing collection
|
||||
/// </summary>
|
||||
/// <param name="collection">Collection that will be wrapped</param>
|
||||
/// <exception cref="System.ArgumentNullException">List is null</exception>
|
||||
public ObservableCollection(ICollection<TItem> collection) {
|
||||
this.typedCollection = collection;
|
||||
this.objectCollection = (collection as ICollection);
|
||||
}
|
||||
|
||||
/// <summary>Removes all elements from the Collection</summary>
|
||||
public void Clear() {
|
||||
OnClearing();
|
||||
this.typedCollection.Clear();
|
||||
OnCleared();
|
||||
}
|
||||
|
||||
/// <summary>Adds an item to the collection</summary>
|
||||
/// <param name="item">Collection an item will be added to</param>
|
||||
public void Add(TItem item) {
|
||||
this.typedCollection.Add(item);
|
||||
OnAdded(item);
|
||||
}
|
||||
|
||||
/// <summary>Determines whether the collection contains the specified item</summary>
|
||||
/// <param name="item">Item the collection will be searched for</param>
|
||||
/// <returns>
|
||||
/// True if the collection contains the specified item, false otherwise
|
||||
/// </returns>
|
||||
public bool Contains(TItem item) {
|
||||
return this.typedCollection.Contains(item);
|
||||
}
|
||||
|
||||
/// <summary>Copies the contents of the collection into an array</summary>
|
||||
/// <param name="array">Array the collection's contents will be copied into</param>
|
||||
/// <param name="arrayIndex">
|
||||
/// Index in the array where the collection's first item will be placed
|
||||
/// </param>
|
||||
public void CopyTo(TItem[] array, int arrayIndex) {
|
||||
this.typedCollection.CopyTo(array, arrayIndex);
|
||||
}
|
||||
|
||||
/// <summary>The total number of items currently in the collection</summary>
|
||||
public int Count {
|
||||
get { return this.typedCollection.Count; }
|
||||
}
|
||||
|
||||
/// <summary>Whether the collection is read-only</summary>
|
||||
public bool IsReadOnly {
|
||||
get { return this.typedCollection.IsReadOnly; }
|
||||
}
|
||||
|
||||
/// <summary>Removes an item from the collection</summary>
|
||||
/// <param name="item">Item that will be removed from the collection</param>
|
||||
/// <returns>True if the item was found and removed, false otherwise</returns>
|
||||
public bool Remove(TItem item) {
|
||||
bool wasRemoved = this.typedCollection.Remove(item);
|
||||
if(wasRemoved) {
|
||||
OnRemoved(item);
|
||||
}
|
||||
|
||||
return wasRemoved;
|
||||
}
|
||||
|
||||
/// <summary>Returns an enumerator for the items in the collection</summary>
|
||||
/// <returns>An enumeration for the items in the collection</returns>
|
||||
public IEnumerator<TItem> GetEnumerator() {
|
||||
return this.typedCollection.GetEnumerator();
|
||||
}
|
||||
|
||||
/// <summary>Fires the 'ItemAdded' event</summary>
|
||||
/// <param name="item">Item that has been added to the collection</param>
|
||||
protected virtual void OnAdded(TItem item) {
|
||||
if(ItemAdded != null) {
|
||||
ItemAdded(this, new ItemEventArgs<TItem>(item));
|
||||
}
|
||||
#if !NO_SPECIALIZED_COLLECTIONS
|
||||
if(CollectionChanged != null) {
|
||||
CollectionChanged(
|
||||
this,
|
||||
new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item)
|
||||
);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>Fires the 'ItemRemoved' event</summary>
|
||||
/// <param name="item">Item that has been removed from the collection</param>
|
||||
protected virtual void OnRemoved(TItem item) {
|
||||
if(ItemRemoved != null) {
|
||||
ItemRemoved(this, new ItemEventArgs<TItem>(item));
|
||||
}
|
||||
#if !NO_SPECIALIZED_COLLECTIONS
|
||||
if(CollectionChanged != null) {
|
||||
CollectionChanged(
|
||||
this,
|
||||
new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, item)
|
||||
);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>Fires the 'Clearing' event</summary>
|
||||
protected virtual void OnClearing() {
|
||||
if(Clearing != null) {
|
||||
Clearing(this, EventArgs.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Fires the 'Cleared' event</summary>
|
||||
protected virtual void OnCleared() {
|
||||
if(Cleared != null) {
|
||||
Cleared(this, EventArgs.Empty);
|
||||
}
|
||||
#if !NO_SPECIALIZED_COLLECTIONS
|
||||
if(CollectionChanged != null) {
|
||||
CollectionChanged(this, Constants.NotifyCollectionResetEventArgs);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#region IEnumerable implementation
|
||||
|
||||
/// <summary>Returns an enumerator for the items in the collection</summary>
|
||||
/// <returns>An enumeration for the items in the collection</returns>
|
||||
IEnumerator IEnumerable.GetEnumerator() {
|
||||
return this.objectCollection.GetEnumerator();
|
||||
}
|
||||
|
||||
#endregion // IEnumerable implementation
|
||||
|
||||
#region ICollection implementation
|
||||
|
||||
/// <summary>Copies the contents of the collection into an array</summary>
|
||||
/// <param name="array">Array the collection's contents will be copied into</param>
|
||||
/// <param name="arrayIndex">
|
||||
/// Index in the array where the collection's first item will be placed
|
||||
/// </param>
|
||||
void ICollection.CopyTo(Array array, int arrayIndex) {
|
||||
this.objectCollection.CopyTo(array, arrayIndex);
|
||||
}
|
||||
|
||||
/// <summary>Whether the collection synchronizes accesses from multiple threads</summary>
|
||||
bool ICollection.IsSynchronized {
|
||||
get { return this.objectCollection.IsSynchronized; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Synchronization root used to synchronize threads accessing the collection
|
||||
/// </summary>
|
||||
object ICollection.SyncRoot {
|
||||
get { return this.objectCollection.SyncRoot; }
|
||||
}
|
||||
|
||||
#endregion // IEnumerable implementation
|
||||
|
||||
/// <summary>The wrapped collection under its type-safe interface</summary>
|
||||
private ICollection<TItem> typedCollection;
|
||||
/// <summary>The wrapped collection under its object interface</summary>
|
||||
private ICollection objectCollection;
|
||||
|
||||
}
|
||||
|
||||
} // namespace Nuclex.Support.Collections
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -1,489 +1,488 @@
|
|||
#region CPL License
|
||||
/*
|
||||
Nuclex Framework
|
||||
Copyright (C) 2002-2017 Nuclex Development Labs
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the IBM Common Public License as
|
||||
published by the IBM Corporation; either version 1.0 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
IBM Common Public License for more details.
|
||||
|
||||
You should have received a copy of the IBM Common Public
|
||||
License along with this library
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
#if !NO_SPECIALIZED_COLLECTIONS
|
||||
using System.Collections.Specialized;
|
||||
#endif
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace Nuclex.Support.Collections {
|
||||
|
||||
/// <summary>A dictionary that sneds out change notifications</summary>
|
||||
/// <typeparam name="TKey">Type of the keys used in the dictionary</typeparam>
|
||||
/// <typeparam name="TValue">Type of the values used in the dictionary</typeparam>
|
||||
#if !NO_SERIALIZATION
|
||||
[Serializable]
|
||||
#endif
|
||||
public class ObservableDictionary<TKey, TValue> :
|
||||
#if !NO_SERIALIZATION
|
||||
ISerializable,
|
||||
IDeserializationCallback,
|
||||
#endif
|
||||
IDictionary<TKey, TValue>,
|
||||
IDictionary,
|
||||
#if !NO_SPECIALIZED_COLLECTIONS
|
||||
INotifyCollectionChanged,
|
||||
#endif
|
||||
IObservableCollection<KeyValuePair<TKey, TValue>> {
|
||||
|
||||
#if !NO_SERIALIZATION
|
||||
#region class SerializedDictionary
|
||||
|
||||
/// <summary>
|
||||
/// Dictionary wrapped used to reconstruct a serialized read only dictionary
|
||||
/// </summary>
|
||||
private class SerializedDictionary : Dictionary<TKey, TValue> {
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the System.WeakReference class, using deserialized
|
||||
/// data from the specified serialization and stream objects.
|
||||
/// </summary>
|
||||
/// <param name="info">
|
||||
/// An object that holds all the data needed to serialize or deserialize the
|
||||
/// current System.WeakReference object.
|
||||
/// </param>
|
||||
/// <param name="context">
|
||||
/// (Reserved) Describes the source and destination of the serialized stream
|
||||
/// specified by info.
|
||||
/// </param>
|
||||
/// <exception cref="System.ArgumentNullException">
|
||||
/// The info parameter is null.
|
||||
/// </exception>
|
||||
public SerializedDictionary(SerializationInfo info, StreamingContext context) :
|
||||
base(info, context) { }
|
||||
|
||||
}
|
||||
|
||||
#endregion // class SerializedDictionary
|
||||
#endif // !NO_SERIALIZATION
|
||||
|
||||
/// <summary>Raised when an item has been added to the dictionary</summary>
|
||||
public event EventHandler<ItemEventArgs<KeyValuePair<TKey, TValue>>> ItemAdded;
|
||||
/// <summary>Raised when an item is removed from the dictionary</summary>
|
||||
public event EventHandler<ItemEventArgs<KeyValuePair<TKey, TValue>>> ItemRemoved;
|
||||
/// <summary>Raised when an item is replaced in the collection</summary>
|
||||
public event EventHandler<ItemReplaceEventArgs<KeyValuePair<TKey, TValue>>> ItemReplaced;
|
||||
/// <summary>Raised when the dictionary is about to be cleared</summary>
|
||||
public event EventHandler Clearing;
|
||||
/// <summary>Raised when the dictionary has been cleared</summary>
|
||||
public event EventHandler Cleared;
|
||||
|
||||
#if !NO_SPECIALIZED_COLLECTIONS
|
||||
/// <summary>Called when the collection has changed</summary>
|
||||
public event NotifyCollectionChangedEventHandler CollectionChanged;
|
||||
#endif
|
||||
|
||||
/// <summary>Initializes a new observable dictionary</summary>
|
||||
public ObservableDictionary() : this(new Dictionary<TKey, TValue>()) { }
|
||||
|
||||
/// <summary>Initializes a new observable Dictionary wrapper</summary>
|
||||
/// <param name="dictionary">Dictionary that will be wrapped</param>
|
||||
public ObservableDictionary(IDictionary<TKey, TValue> dictionary) {
|
||||
this.typedDictionary = dictionary;
|
||||
this.objectDictionary = (this.typedDictionary as IDictionary);
|
||||
}
|
||||
|
||||
#if !NO_SERIALIZATION
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the System.WeakReference class, using deserialized
|
||||
/// data from the specified serialization and stream objects.
|
||||
/// </summary>
|
||||
/// <param name="info">
|
||||
/// An object that holds all the data needed to serialize or deserialize the
|
||||
/// current System.WeakReference object.
|
||||
/// </param>
|
||||
/// <param name="context">
|
||||
/// (Reserved) Describes the source and destination of the serialized stream
|
||||
/// specified by info.
|
||||
/// </param>
|
||||
/// <exception cref="System.ArgumentNullException">
|
||||
/// The info parameter is null.
|
||||
/// </exception>
|
||||
protected ObservableDictionary(SerializationInfo info, StreamingContext context) :
|
||||
this(new SerializedDictionary(info, context)) { }
|
||||
|
||||
#endif // !NO_SERIALIZATION
|
||||
|
||||
/// <summary>Whether the directory is write-protected</summary>
|
||||
public bool IsReadOnly {
|
||||
get { return this.typedDictionary.IsReadOnly; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified KeyValuePair is contained in the Dictionary
|
||||
/// </summary>
|
||||
/// <param name="item">KeyValuePair that will be checked for</param>
|
||||
/// <returns>True if the provided KeyValuePair was contained in the Dictionary</returns>
|
||||
public bool Contains(KeyValuePair<TKey, TValue> item) {
|
||||
return this.typedDictionary.Contains(item);
|
||||
}
|
||||
|
||||
/// <summary>Determines whether the Dictionary contains the specified key</summary>
|
||||
/// <param name="key">Key that will be checked for</param>
|
||||
/// <returns>
|
||||
/// True if an entry with the specified key was contained in the Dictionary
|
||||
/// </returns>
|
||||
public bool ContainsKey(TKey key) {
|
||||
return this.typedDictionary.ContainsKey(key);
|
||||
}
|
||||
|
||||
/// <summary>Copies the contents of the Dictionary into an array</summary>
|
||||
/// <param name="array">Array the Dictionary will be copied into</param>
|
||||
/// <param name="arrayIndex">
|
||||
/// Starting index at which to begin filling the destination array
|
||||
/// </param>
|
||||
public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex) {
|
||||
this.typedDictionary.CopyTo(array, arrayIndex);
|
||||
}
|
||||
|
||||
/// <summary>Number of elements contained in the Dictionary</summary>
|
||||
public int Count {
|
||||
get { return this.typedDictionary.Count; }
|
||||
}
|
||||
|
||||
/// <summary>Creates a new enumerator for the Dictionary</summary>
|
||||
/// <returns>The new Dictionary enumerator</returns>
|
||||
public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator() {
|
||||
return this.typedDictionary.GetEnumerator();
|
||||
}
|
||||
|
||||
/// <summary>Collection of all keys contained in the Dictionary</summary>
|
||||
public ICollection<TKey> Keys {
|
||||
get { return this.typedDictionary.Keys; }
|
||||
}
|
||||
|
||||
/// <summary>Collection of all values contained in the Dictionary</summary>
|
||||
public ICollection<TValue> Values {
|
||||
get { return this.typedDictionary.Values; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to retrieve the item with the specified key from the Dictionary
|
||||
/// </summary>
|
||||
/// <param name="key">Key of the item to attempt to retrieve</param>
|
||||
/// <param name="value">
|
||||
/// Output parameter that will receive the key upon successful completion
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// True if the item was found and has been placed in the output parameter
|
||||
/// </returns>
|
||||
public bool TryGetValue(TKey key, out TValue value) {
|
||||
return this.typedDictionary.TryGetValue(key, out value);
|
||||
}
|
||||
|
||||
/// <summary>Accesses an item in the Dictionary by its key</summary>
|
||||
/// <param name="key">Key of the item that will be accessed</param>
|
||||
public TValue this[TKey key] {
|
||||
get { return this.typedDictionary[key]; }
|
||||
set {
|
||||
bool removed;
|
||||
TValue oldValue;
|
||||
removed = this.typedDictionary.TryGetValue(key, out oldValue);
|
||||
|
||||
this.typedDictionary[key] = value;
|
||||
|
||||
if(removed) {
|
||||
OnReplaced(
|
||||
new KeyValuePair<TKey, TValue>(key, oldValue),
|
||||
new KeyValuePair<TKey, TValue>(key, value)
|
||||
);
|
||||
} else {
|
||||
OnAdded(new KeyValuePair<TKey, TValue>(key, value));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Inserts an item into the Dictionary</summary>
|
||||
/// <param name="key">Key under which to add the new item</param>
|
||||
/// <param name="value">Item that will be added to the Dictionary</param>
|
||||
public void Add(TKey key, TValue value) {
|
||||
this.typedDictionary.Add(key, value);
|
||||
OnAdded(new KeyValuePair<TKey, TValue>(key, value));
|
||||
}
|
||||
|
||||
/// <summary>Removes the item with the specified key from the Dictionary</summary>
|
||||
/// <param name="key">Key of the elementes that will be removed</param>
|
||||
/// <returns>True if an item with the specified key was found and removed</returns>
|
||||
public bool Remove(TKey key) {
|
||||
TValue oldValue;
|
||||
this.typedDictionary.TryGetValue(key, out oldValue);
|
||||
|
||||
bool removed = this.typedDictionary.Remove(key);
|
||||
if(removed) {
|
||||
OnRemoved(new KeyValuePair<TKey, TValue>(key, oldValue));
|
||||
}
|
||||
return removed;
|
||||
}
|
||||
|
||||
/// <summary>Removes all items from the Dictionary</summary>
|
||||
public void Clear() {
|
||||
OnClearing();
|
||||
this.typedDictionary.Clear();
|
||||
OnCleared();
|
||||
}
|
||||
|
||||
/// <summary>Fires the 'ItemAdded' event</summary>
|
||||
/// <param name="item">Item that has been added to the collection</param>
|
||||
protected virtual void OnAdded(KeyValuePair<TKey, TValue> item) {
|
||||
if(ItemAdded != null)
|
||||
ItemAdded(this, new ItemEventArgs<KeyValuePair<TKey, TValue>>(item));
|
||||
|
||||
#if !NO_SPECIALIZED_COLLECTIONS
|
||||
if(CollectionChanged != null) {
|
||||
CollectionChanged(
|
||||
this,
|
||||
new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item)
|
||||
);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>Fires the 'ItemRemoved' event</summary>
|
||||
/// <param name="item">Item that has been removed from the collection</param>
|
||||
protected virtual void OnRemoved(KeyValuePair<TKey, TValue> item) {
|
||||
if(ItemRemoved != null) {
|
||||
ItemRemoved(this, new ItemEventArgs<KeyValuePair<TKey, TValue>>(item));
|
||||
}
|
||||
#if !NO_SPECIALIZED_COLLECTIONS
|
||||
if(CollectionChanged != null) {
|
||||
CollectionChanged(
|
||||
this,
|
||||
new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, item)
|
||||
);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>Fires the 'ItemReplaced' event</summary>
|
||||
/// <param name="oldItem">Item that has been replaced in the collection</param>
|
||||
/// <param name="newItem">Item with which the original item was replaced</param>
|
||||
protected virtual void OnReplaced(
|
||||
KeyValuePair<TKey, TValue> oldItem, KeyValuePair<TKey, TValue> newItem
|
||||
) {
|
||||
if(ItemReplaced != null) {
|
||||
ItemReplaced(
|
||||
this,
|
||||
new ItemReplaceEventArgs<KeyValuePair<TKey, TValue>>(oldItem, newItem)
|
||||
);
|
||||
}
|
||||
#if !NO_SPECIALIZED_COLLECTIONS
|
||||
if(CollectionChanged != null) {
|
||||
CollectionChanged(
|
||||
this, new NotifyCollectionChangedEventArgs(
|
||||
NotifyCollectionChangedAction.Replace, newItem, oldItem
|
||||
)
|
||||
);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>Fires the 'Clearing' event</summary>
|
||||
protected virtual void OnClearing() {
|
||||
if(Clearing != null) {
|
||||
Clearing(this, EventArgs.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Fires the 'Cleared' event</summary>
|
||||
protected virtual void OnCleared() {
|
||||
if(Cleared != null) {
|
||||
Cleared(this, EventArgs.Empty);
|
||||
}
|
||||
#if !NO_SPECIALIZED_COLLECTIONS
|
||||
if(CollectionChanged != null) {
|
||||
CollectionChanged(this, Constants.NotifyCollectionResetEventArgs);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#region IEnumerable implementation
|
||||
|
||||
/// <summary>Returns a new object enumerator for the Dictionary</summary>
|
||||
/// <returns>The new object enumerator</returns>
|
||||
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() {
|
||||
return (this.typedDictionary as IEnumerable).GetEnumerator();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IDictionary implementation
|
||||
|
||||
/// <summary>Adds an item into the Dictionary</summary>
|
||||
/// <param name="key">Key under which the item will be added</param>
|
||||
/// <param name="value">Item that will be added</param>
|
||||
void IDictionary.Add(object key, object value) {
|
||||
this.objectDictionary.Add(key, value);
|
||||
OnAdded(new KeyValuePair<TKey, TValue>((TKey)key, (TValue)value));
|
||||
}
|
||||
|
||||
/// <summary>Determines whether the specified key exists in the Dictionary</summary>
|
||||
/// <param name="key">Key that will be checked for</param>
|
||||
/// <returns>True if an item with the specified key exists in the Dictionary</returns>
|
||||
bool IDictionary.Contains(object key) {
|
||||
return this.objectDictionary.Contains(key);
|
||||
}
|
||||
|
||||
/// <summary>Whether the size of the Dictionary is fixed</summary>
|
||||
bool IDictionary.IsFixedSize {
|
||||
get { return this.objectDictionary.IsFixedSize; }
|
||||
}
|
||||
|
||||
/// <summary>Returns a collection of all keys in the Dictionary</summary>
|
||||
ICollection IDictionary.Keys {
|
||||
get { return this.objectDictionary.Keys; }
|
||||
}
|
||||
|
||||
/// <summary>Returns a collection of all values stored in the Dictionary</summary>
|
||||
ICollection IDictionary.Values {
|
||||
get { return this.objectDictionary.Values; }
|
||||
}
|
||||
|
||||
/// <summary>Removes an item from the Dictionary</summary>
|
||||
/// <param name="key">Key of the item that will be removed</param>
|
||||
void IDictionary.Remove(object key) {
|
||||
TValue value;
|
||||
bool removed = this.typedDictionary.TryGetValue((TKey)key, out value);
|
||||
this.objectDictionary.Remove(key);
|
||||
if(removed) {
|
||||
OnRemoved(new KeyValuePair<TKey, TValue>((TKey)key, (TValue)value));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Accesses an item in the Dictionary by its key</summary>
|
||||
/// <param name="key">Key of the item that will be accessed</param>
|
||||
/// <returns>The item with the specified key</returns>
|
||||
object IDictionary.this[object key] {
|
||||
get { return this.objectDictionary[key]; }
|
||||
set {
|
||||
bool removed;
|
||||
TValue oldValue;
|
||||
removed = this.typedDictionary.TryGetValue((TKey)key, out oldValue);
|
||||
|
||||
this.objectDictionary[key] = value;
|
||||
|
||||
if(removed) {
|
||||
OnRemoved(new KeyValuePair<TKey, TValue>((TKey)key, oldValue));
|
||||
}
|
||||
OnAdded(new KeyValuePair<TKey, TValue>((TKey)key, (TValue)value));
|
||||
}
|
||||
}
|
||||
|
||||
#endregion // IDictionary implementation
|
||||
|
||||
#region IDictionaryEnumerator implementation
|
||||
|
||||
/// <summary>Returns a new entry enumerator for the dictionary</summary>
|
||||
/// <returns>The new entry enumerator</returns>
|
||||
IDictionaryEnumerator IDictionary.GetEnumerator() {
|
||||
return this.objectDictionary.GetEnumerator();
|
||||
}
|
||||
|
||||
#endregion // IDictionaryEnumerator implementation
|
||||
|
||||
#region ICollection<> implementation
|
||||
|
||||
/// <summary>Inserts an already prepared element into the Dictionary</summary>
|
||||
/// <param name="item">Prepared element that will be added to the Dictionary</param>
|
||||
void ICollection<KeyValuePair<TKey, TValue>>.Add(
|
||||
KeyValuePair<TKey, TValue> item
|
||||
) {
|
||||
this.typedDictionary.Add(item);
|
||||
OnAdded(item);
|
||||
}
|
||||
|
||||
/// <summary>Removes all items from the Dictionary</summary>
|
||||
void ICollection<KeyValuePair<TKey, TValue>>.Clear() {
|
||||
OnClearing();
|
||||
this.typedDictionary.Clear();
|
||||
OnCleared();
|
||||
}
|
||||
|
||||
/// <summary>Removes all items from the Dictionary</summary>
|
||||
/// <param name="itemToRemove">Item that will be removed from the Dictionary</param>
|
||||
bool ICollection<KeyValuePair<TKey, TValue>>.Remove(
|
||||
KeyValuePair<TKey, TValue> itemToRemove
|
||||
) {
|
||||
bool removed = this.typedDictionary.Remove(itemToRemove);
|
||||
if(removed) {
|
||||
OnRemoved(itemToRemove);
|
||||
}
|
||||
return removed;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region ICollection implementation
|
||||
|
||||
/// <summary>Copies the contents of the Dictionary into an array</summary>
|
||||
/// <param name="array">Array the Dictionary contents will be copied into</param>
|
||||
/// <param name="index">
|
||||
/// Starting index at which to begin filling the destination array
|
||||
/// </param>
|
||||
void ICollection.CopyTo(Array array, int index) {
|
||||
this.objectDictionary.CopyTo(array, index);
|
||||
}
|
||||
|
||||
/// <summary>Whether the Dictionary is synchronized for multi-threaded usage</summary>
|
||||
bool ICollection.IsSynchronized {
|
||||
get { return this.objectDictionary.IsSynchronized; }
|
||||
}
|
||||
|
||||
/// <summary>Synchronization root on which the Dictionary locks</summary>
|
||||
object ICollection.SyncRoot {
|
||||
get { return this.objectDictionary.SyncRoot; }
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#if !NO_SERIALIZATION
|
||||
#region ISerializable implementation
|
||||
|
||||
/// <summary>Serializes the Dictionary</summary>
|
||||
/// <param name="info">
|
||||
/// Provides the container into which the Dictionary will serialize itself
|
||||
/// </param>
|
||||
/// <param name="context">
|
||||
/// Contextual informations about the serialization environment
|
||||
/// </param>
|
||||
void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context) {
|
||||
(this.typedDictionary as ISerializable).GetObjectData(info, context);
|
||||
}
|
||||
|
||||
/// <summary>Called after all objects have been successfully deserialized</summary>
|
||||
/// <param name="sender">Nicht unterstützt</param>
|
||||
void IDeserializationCallback.OnDeserialization(object sender) {
|
||||
(this.typedDictionary as IDeserializationCallback).OnDeserialization(sender);
|
||||
}
|
||||
|
||||
#endregion
|
||||
#endif //!NO_SERIALIZATION
|
||||
|
||||
/// <summary>The wrapped Dictionary under its type-safe interface</summary>
|
||||
private IDictionary<TKey, TValue> typedDictionary;
|
||||
/// <summary>The wrapped Dictionary under its object interface</summary>
|
||||
private IDictionary objectDictionary;
|
||||
|
||||
}
|
||||
|
||||
} // namespace Nuclex.Support.Collections
|
||||
#region Apache License 2.0
|
||||
/*
|
||||
Nuclex .NET Framework
|
||||
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
#endregion // Apache License 2.0
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
#if !NO_SPECIALIZED_COLLECTIONS
|
||||
using System.Collections.Specialized;
|
||||
#endif
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace Nuclex.Support.Collections {
|
||||
|
||||
/// <summary>A dictionary that sneds out change notifications</summary>
|
||||
/// <typeparam name="TKey">Type of the keys used in the dictionary</typeparam>
|
||||
/// <typeparam name="TValue">Type of the values used in the dictionary</typeparam>
|
||||
#if !NO_SERIALIZATION
|
||||
[Serializable]
|
||||
#endif
|
||||
public class ObservableDictionary<TKey, TValue> :
|
||||
#if !NO_SERIALIZATION
|
||||
ISerializable,
|
||||
IDeserializationCallback,
|
||||
#endif
|
||||
IDictionary<TKey, TValue>,
|
||||
IDictionary,
|
||||
#if !NO_SPECIALIZED_COLLECTIONS
|
||||
INotifyCollectionChanged,
|
||||
#endif
|
||||
IObservableCollection<KeyValuePair<TKey, TValue>> {
|
||||
|
||||
#if !NO_SERIALIZATION
|
||||
#region class SerializedDictionary
|
||||
|
||||
/// <summary>
|
||||
/// Dictionary wrapped used to reconstruct a serialized read only dictionary
|
||||
/// </summary>
|
||||
private class SerializedDictionary : Dictionary<TKey, TValue> {
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the System.WeakReference class, using deserialized
|
||||
/// data from the specified serialization and stream objects.
|
||||
/// </summary>
|
||||
/// <param name="info">
|
||||
/// An object that holds all the data needed to serialize or deserialize the
|
||||
/// current System.WeakReference object.
|
||||
/// </param>
|
||||
/// <param name="context">
|
||||
/// (Reserved) Describes the source and destination of the serialized stream
|
||||
/// specified by info.
|
||||
/// </param>
|
||||
/// <exception cref="System.ArgumentNullException">
|
||||
/// The info parameter is null.
|
||||
/// </exception>
|
||||
public SerializedDictionary(SerializationInfo info, StreamingContext context) :
|
||||
base(info, context) { }
|
||||
|
||||
}
|
||||
|
||||
#endregion // class SerializedDictionary
|
||||
#endif // !NO_SERIALIZATION
|
||||
|
||||
/// <summary>Raised when an item has been added to the dictionary</summary>
|
||||
public event EventHandler<ItemEventArgs<KeyValuePair<TKey, TValue>>> ItemAdded;
|
||||
/// <summary>Raised when an item is removed from the dictionary</summary>
|
||||
public event EventHandler<ItemEventArgs<KeyValuePair<TKey, TValue>>> ItemRemoved;
|
||||
/// <summary>Raised when an item is replaced in the collection</summary>
|
||||
public event EventHandler<ItemReplaceEventArgs<KeyValuePair<TKey, TValue>>> ItemReplaced;
|
||||
/// <summary>Raised when the dictionary is about to be cleared</summary>
|
||||
public event EventHandler Clearing;
|
||||
/// <summary>Raised when the dictionary has been cleared</summary>
|
||||
public event EventHandler Cleared;
|
||||
|
||||
#if !NO_SPECIALIZED_COLLECTIONS
|
||||
/// <summary>Called when the collection has changed</summary>
|
||||
public event NotifyCollectionChangedEventHandler CollectionChanged;
|
||||
#endif
|
||||
|
||||
/// <summary>Initializes a new observable dictionary</summary>
|
||||
public ObservableDictionary() : this(new Dictionary<TKey, TValue>()) { }
|
||||
|
||||
/// <summary>Initializes a new observable Dictionary wrapper</summary>
|
||||
/// <param name="dictionary">Dictionary that will be wrapped</param>
|
||||
public ObservableDictionary(IDictionary<TKey, TValue> dictionary) {
|
||||
this.typedDictionary = dictionary;
|
||||
this.objectDictionary = (this.typedDictionary as IDictionary);
|
||||
}
|
||||
|
||||
#if !NO_SERIALIZATION
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the System.WeakReference class, using deserialized
|
||||
/// data from the specified serialization and stream objects.
|
||||
/// </summary>
|
||||
/// <param name="info">
|
||||
/// An object that holds all the data needed to serialize or deserialize the
|
||||
/// current System.WeakReference object.
|
||||
/// </param>
|
||||
/// <param name="context">
|
||||
/// (Reserved) Describes the source and destination of the serialized stream
|
||||
/// specified by info.
|
||||
/// </param>
|
||||
/// <exception cref="System.ArgumentNullException">
|
||||
/// The info parameter is null.
|
||||
/// </exception>
|
||||
protected ObservableDictionary(SerializationInfo info, StreamingContext context) :
|
||||
this(new SerializedDictionary(info, context)) { }
|
||||
|
||||
#endif // !NO_SERIALIZATION
|
||||
|
||||
/// <summary>Whether the directory is write-protected</summary>
|
||||
public bool IsReadOnly {
|
||||
get { return this.typedDictionary.IsReadOnly; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified KeyValuePair is contained in the Dictionary
|
||||
/// </summary>
|
||||
/// <param name="item">KeyValuePair that will be checked for</param>
|
||||
/// <returns>True if the provided KeyValuePair was contained in the Dictionary</returns>
|
||||
public bool Contains(KeyValuePair<TKey, TValue> item) {
|
||||
return this.typedDictionary.Contains(item);
|
||||
}
|
||||
|
||||
/// <summary>Determines whether the Dictionary contains the specified key</summary>
|
||||
/// <param name="key">Key that will be checked for</param>
|
||||
/// <returns>
|
||||
/// True if an entry with the specified key was contained in the Dictionary
|
||||
/// </returns>
|
||||
public bool ContainsKey(TKey key) {
|
||||
return this.typedDictionary.ContainsKey(key);
|
||||
}
|
||||
|
||||
/// <summary>Copies the contents of the Dictionary into an array</summary>
|
||||
/// <param name="array">Array the Dictionary will be copied into</param>
|
||||
/// <param name="arrayIndex">
|
||||
/// Starting index at which to begin filling the destination array
|
||||
/// </param>
|
||||
public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex) {
|
||||
this.typedDictionary.CopyTo(array, arrayIndex);
|
||||
}
|
||||
|
||||
/// <summary>Number of elements contained in the Dictionary</summary>
|
||||
public int Count {
|
||||
get { return this.typedDictionary.Count; }
|
||||
}
|
||||
|
||||
/// <summary>Creates a new enumerator for the Dictionary</summary>
|
||||
/// <returns>The new Dictionary enumerator</returns>
|
||||
public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator() {
|
||||
return this.typedDictionary.GetEnumerator();
|
||||
}
|
||||
|
||||
/// <summary>Collection of all keys contained in the Dictionary</summary>
|
||||
public ICollection<TKey> Keys {
|
||||
get { return this.typedDictionary.Keys; }
|
||||
}
|
||||
|
||||
/// <summary>Collection of all values contained in the Dictionary</summary>
|
||||
public ICollection<TValue> Values {
|
||||
get { return this.typedDictionary.Values; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to retrieve the item with the specified key from the Dictionary
|
||||
/// </summary>
|
||||
/// <param name="key">Key of the item to attempt to retrieve</param>
|
||||
/// <param name="value">
|
||||
/// Output parameter that will receive the key upon successful completion
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// True if the item was found and has been placed in the output parameter
|
||||
/// </returns>
|
||||
public bool TryGetValue(TKey key, out TValue value) {
|
||||
return this.typedDictionary.TryGetValue(key, out value);
|
||||
}
|
||||
|
||||
/// <summary>Accesses an item in the Dictionary by its key</summary>
|
||||
/// <param name="key">Key of the item that will be accessed</param>
|
||||
public TValue this[TKey key] {
|
||||
get { return this.typedDictionary[key]; }
|
||||
set {
|
||||
bool removed;
|
||||
TValue oldValue;
|
||||
removed = this.typedDictionary.TryGetValue(key, out oldValue);
|
||||
|
||||
this.typedDictionary[key] = value;
|
||||
|
||||
if(removed) {
|
||||
OnReplaced(
|
||||
new KeyValuePair<TKey, TValue>(key, oldValue),
|
||||
new KeyValuePair<TKey, TValue>(key, value)
|
||||
);
|
||||
} else {
|
||||
OnAdded(new KeyValuePair<TKey, TValue>(key, value));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Inserts an item into the Dictionary</summary>
|
||||
/// <param name="key">Key under which to add the new item</param>
|
||||
/// <param name="value">Item that will be added to the Dictionary</param>
|
||||
public void Add(TKey key, TValue value) {
|
||||
this.typedDictionary.Add(key, value);
|
||||
OnAdded(new KeyValuePair<TKey, TValue>(key, value));
|
||||
}
|
||||
|
||||
/// <summary>Removes the item with the specified key from the Dictionary</summary>
|
||||
/// <param name="key">Key of the elementes that will be removed</param>
|
||||
/// <returns>True if an item with the specified key was found and removed</returns>
|
||||
public bool Remove(TKey key) {
|
||||
TValue oldValue;
|
||||
this.typedDictionary.TryGetValue(key, out oldValue);
|
||||
|
||||
bool removed = this.typedDictionary.Remove(key);
|
||||
if(removed) {
|
||||
OnRemoved(new KeyValuePair<TKey, TValue>(key, oldValue));
|
||||
}
|
||||
return removed;
|
||||
}
|
||||
|
||||
/// <summary>Removes all items from the Dictionary</summary>
|
||||
public void Clear() {
|
||||
OnClearing();
|
||||
this.typedDictionary.Clear();
|
||||
OnCleared();
|
||||
}
|
||||
|
||||
/// <summary>Fires the 'ItemAdded' event</summary>
|
||||
/// <param name="item">Item that has been added to the collection</param>
|
||||
protected virtual void OnAdded(KeyValuePair<TKey, TValue> item) {
|
||||
if(ItemAdded != null)
|
||||
ItemAdded(this, new ItemEventArgs<KeyValuePair<TKey, TValue>>(item));
|
||||
|
||||
#if !NO_SPECIALIZED_COLLECTIONS
|
||||
if(CollectionChanged != null) {
|
||||
CollectionChanged(
|
||||
this,
|
||||
new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item)
|
||||
);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>Fires the 'ItemRemoved' event</summary>
|
||||
/// <param name="item">Item that has been removed from the collection</param>
|
||||
protected virtual void OnRemoved(KeyValuePair<TKey, TValue> item) {
|
||||
if(ItemRemoved != null) {
|
||||
ItemRemoved(this, new ItemEventArgs<KeyValuePair<TKey, TValue>>(item));
|
||||
}
|
||||
#if !NO_SPECIALIZED_COLLECTIONS
|
||||
if(CollectionChanged != null) {
|
||||
CollectionChanged(
|
||||
this,
|
||||
new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, item)
|
||||
);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>Fires the 'ItemReplaced' event</summary>
|
||||
/// <param name="oldItem">Item that has been replaced in the collection</param>
|
||||
/// <param name="newItem">Item with which the original item was replaced</param>
|
||||
protected virtual void OnReplaced(
|
||||
KeyValuePair<TKey, TValue> oldItem, KeyValuePair<TKey, TValue> newItem
|
||||
) {
|
||||
if(ItemReplaced != null) {
|
||||
ItemReplaced(
|
||||
this,
|
||||
new ItemReplaceEventArgs<KeyValuePair<TKey, TValue>>(oldItem, newItem)
|
||||
);
|
||||
}
|
||||
#if !NO_SPECIALIZED_COLLECTIONS
|
||||
if(CollectionChanged != null) {
|
||||
CollectionChanged(
|
||||
this, new NotifyCollectionChangedEventArgs(
|
||||
NotifyCollectionChangedAction.Replace, newItem, oldItem
|
||||
)
|
||||
);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>Fires the 'Clearing' event</summary>
|
||||
protected virtual void OnClearing() {
|
||||
if(Clearing != null) {
|
||||
Clearing(this, EventArgs.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Fires the 'Cleared' event</summary>
|
||||
protected virtual void OnCleared() {
|
||||
if(Cleared != null) {
|
||||
Cleared(this, EventArgs.Empty);
|
||||
}
|
||||
#if !NO_SPECIALIZED_COLLECTIONS
|
||||
if(CollectionChanged != null) {
|
||||
CollectionChanged(this, Constants.NotifyCollectionResetEventArgs);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#region IEnumerable implementation
|
||||
|
||||
/// <summary>Returns a new object enumerator for the Dictionary</summary>
|
||||
/// <returns>The new object enumerator</returns>
|
||||
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() {
|
||||
return (this.typedDictionary as IEnumerable).GetEnumerator();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IDictionary implementation
|
||||
|
||||
/// <summary>Adds an item into the Dictionary</summary>
|
||||
/// <param name="key">Key under which the item will be added</param>
|
||||
/// <param name="value">Item that will be added</param>
|
||||
void IDictionary.Add(object key, object value) {
|
||||
this.objectDictionary.Add(key, value);
|
||||
OnAdded(new KeyValuePair<TKey, TValue>((TKey)key, (TValue)value));
|
||||
}
|
||||
|
||||
/// <summary>Determines whether the specified key exists in the Dictionary</summary>
|
||||
/// <param name="key">Key that will be checked for</param>
|
||||
/// <returns>True if an item with the specified key exists in the Dictionary</returns>
|
||||
bool IDictionary.Contains(object key) {
|
||||
return this.objectDictionary.Contains(key);
|
||||
}
|
||||
|
||||
/// <summary>Whether the size of the Dictionary is fixed</summary>
|
||||
bool IDictionary.IsFixedSize {
|
||||
get { return this.objectDictionary.IsFixedSize; }
|
||||
}
|
||||
|
||||
/// <summary>Returns a collection of all keys in the Dictionary</summary>
|
||||
ICollection IDictionary.Keys {
|
||||
get { return this.objectDictionary.Keys; }
|
||||
}
|
||||
|
||||
/// <summary>Returns a collection of all values stored in the Dictionary</summary>
|
||||
ICollection IDictionary.Values {
|
||||
get { return this.objectDictionary.Values; }
|
||||
}
|
||||
|
||||
/// <summary>Removes an item from the Dictionary</summary>
|
||||
/// <param name="key">Key of the item that will be removed</param>
|
||||
void IDictionary.Remove(object key) {
|
||||
TValue value;
|
||||
bool removed = this.typedDictionary.TryGetValue((TKey)key, out value);
|
||||
this.objectDictionary.Remove(key);
|
||||
if(removed) {
|
||||
OnRemoved(new KeyValuePair<TKey, TValue>((TKey)key, (TValue)value));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Accesses an item in the Dictionary by its key</summary>
|
||||
/// <param name="key">Key of the item that will be accessed</param>
|
||||
/// <returns>The item with the specified key</returns>
|
||||
object IDictionary.this[object key] {
|
||||
get { return this.objectDictionary[key]; }
|
||||
set {
|
||||
bool removed;
|
||||
TValue oldValue;
|
||||
removed = this.typedDictionary.TryGetValue((TKey)key, out oldValue);
|
||||
|
||||
this.objectDictionary[key] = value;
|
||||
|
||||
if(removed) {
|
||||
OnRemoved(new KeyValuePair<TKey, TValue>((TKey)key, oldValue));
|
||||
}
|
||||
OnAdded(new KeyValuePair<TKey, TValue>((TKey)key, (TValue)value));
|
||||
}
|
||||
}
|
||||
|
||||
#endregion // IDictionary implementation
|
||||
|
||||
#region IDictionaryEnumerator implementation
|
||||
|
||||
/// <summary>Returns a new entry enumerator for the dictionary</summary>
|
||||
/// <returns>The new entry enumerator</returns>
|
||||
IDictionaryEnumerator IDictionary.GetEnumerator() {
|
||||
return this.objectDictionary.GetEnumerator();
|
||||
}
|
||||
|
||||
#endregion // IDictionaryEnumerator implementation
|
||||
|
||||
#region ICollection<> implementation
|
||||
|
||||
/// <summary>Inserts an already prepared element into the Dictionary</summary>
|
||||
/// <param name="item">Prepared element that will be added to the Dictionary</param>
|
||||
void ICollection<KeyValuePair<TKey, TValue>>.Add(
|
||||
KeyValuePair<TKey, TValue> item
|
||||
) {
|
||||
this.typedDictionary.Add(item);
|
||||
OnAdded(item);
|
||||
}
|
||||
|
||||
/// <summary>Removes all items from the Dictionary</summary>
|
||||
void ICollection<KeyValuePair<TKey, TValue>>.Clear() {
|
||||
OnClearing();
|
||||
this.typedDictionary.Clear();
|
||||
OnCleared();
|
||||
}
|
||||
|
||||
/// <summary>Removes all items from the Dictionary</summary>
|
||||
/// <param name="itemToRemove">Item that will be removed from the Dictionary</param>
|
||||
bool ICollection<KeyValuePair<TKey, TValue>>.Remove(
|
||||
KeyValuePair<TKey, TValue> itemToRemove
|
||||
) {
|
||||
bool removed = this.typedDictionary.Remove(itemToRemove);
|
||||
if(removed) {
|
||||
OnRemoved(itemToRemove);
|
||||
}
|
||||
return removed;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region ICollection implementation
|
||||
|
||||
/// <summary>Copies the contents of the Dictionary into an array</summary>
|
||||
/// <param name="array">Array the Dictionary contents will be copied into</param>
|
||||
/// <param name="index">
|
||||
/// Starting index at which to begin filling the destination array
|
||||
/// </param>
|
||||
void ICollection.CopyTo(Array array, int index) {
|
||||
this.objectDictionary.CopyTo(array, index);
|
||||
}
|
||||
|
||||
/// <summary>Whether the Dictionary is synchronized for multi-threaded usage</summary>
|
||||
bool ICollection.IsSynchronized {
|
||||
get { return this.objectDictionary.IsSynchronized; }
|
||||
}
|
||||
|
||||
/// <summary>Synchronization root on which the Dictionary locks</summary>
|
||||
object ICollection.SyncRoot {
|
||||
get { return this.objectDictionary.SyncRoot; }
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#if !NO_SERIALIZATION
|
||||
#region ISerializable implementation
|
||||
|
||||
/// <summary>Serializes the Dictionary</summary>
|
||||
/// <param name="info">
|
||||
/// Provides the container into which the Dictionary will serialize itself
|
||||
/// </param>
|
||||
/// <param name="context">
|
||||
/// Contextual informations about the serialization environment
|
||||
/// </param>
|
||||
void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context) {
|
||||
(this.typedDictionary as ISerializable).GetObjectData(info, context);
|
||||
}
|
||||
|
||||
/// <summary>Called after all objects have been successfully deserialized</summary>
|
||||
/// <param name="sender">Nicht unterstützt</param>
|
||||
void IDeserializationCallback.OnDeserialization(object sender) {
|
||||
(this.typedDictionary as IDeserializationCallback).OnDeserialization(sender);
|
||||
}
|
||||
|
||||
#endregion
|
||||
#endif //!NO_SERIALIZATION
|
||||
|
||||
/// <summary>The wrapped Dictionary under its type-safe interface</summary>
|
||||
private IDictionary<TKey, TValue> typedDictionary;
|
||||
/// <summary>The wrapped Dictionary under its object interface</summary>
|
||||
private IDictionary objectDictionary;
|
||||
|
||||
}
|
||||
|
||||
} // namespace Nuclex.Support.Collections
|
||||
|
|
|
|||
|
|
@ -1,176 +1,175 @@
|
|||
#region CPL License
|
||||
/*
|
||||
Nuclex Framework
|
||||
Copyright (C) 2002-2017 Nuclex Development Labs
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the IBM Common Public License as
|
||||
published by the IBM Corporation; either version 1.0 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
IBM Common Public License for more details.
|
||||
|
||||
You should have received a copy of the IBM Common Public
|
||||
License along with this library
|
||||
*/
|
||||
#endregion
|
||||
|
||||
#if !NO_NMOCK
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
#if UNITTEST
|
||||
|
||||
using NUnit.Framework;
|
||||
using NMock;
|
||||
|
||||
namespace Nuclex.Support.Collections {
|
||||
|
||||
/// <summary>Unit Test for the observable list class</summary>
|
||||
[TestFixture]
|
||||
internal class ObservableListTest {
|
||||
|
||||
#region interface IObservableCollectionSubscriber
|
||||
|
||||
/// <summary>Interface used to test the observable collection</summary>
|
||||
public interface IObservableCollectionSubscriber {
|
||||
|
||||
/// <summary>Called when the collection is about to clear its contents</summary>
|
||||
/// <param name="sender">Collection that is clearing its contents</param>
|
||||
/// <param name="arguments">Not used</param>
|
||||
void Clearing(object sender, EventArgs arguments);
|
||||
|
||||
/// <summary>Called when the collection has been cleared of its contents</summary>
|
||||
/// <param name="sender">Collection that was cleared of its contents</param>
|
||||
/// <param name="arguments">Not used</param>
|
||||
void Cleared(object sender, EventArgs arguments);
|
||||
|
||||
/// <summary>Called when an item is added to the collection</summary>
|
||||
/// <param name="sender">Collection to which an item is being added</param>
|
||||
/// <param name="arguments">Contains the item that is being added</param>
|
||||
void ItemAdded(object sender, ItemEventArgs<int> arguments);
|
||||
|
||||
/// <summary>Called when an item is removed from the collection</summary>
|
||||
/// <param name="sender">Collection from which an item is being removed</param>
|
||||
/// <param name="arguments">Contains the item that is being removed</param>
|
||||
void ItemRemoved(object sender, ItemEventArgs<int> arguments);
|
||||
|
||||
/// <summary>Called when an item is replaced in the dictionary</summary>
|
||||
/// <param name="sender">Dictionary in which an item is being replaced</param>
|
||||
/// <param name="arguments">Contains the replaced item and its replacement</param>
|
||||
void ItemReplaced(object sender, ItemReplaceEventArgs<int> arguments);
|
||||
|
||||
}
|
||||
|
||||
#endregion // interface IObservableCollectionSubscriber
|
||||
|
||||
/// <summary>Initialization routine executed before each test is run</summary>
|
||||
[SetUp]
|
||||
public void Setup() {
|
||||
this.mockery = new MockFactory();
|
||||
|
||||
this.mockedSubscriber = this.mockery.CreateMock<IObservableCollectionSubscriber>();
|
||||
|
||||
this.observedList = new ObservableList<int>();
|
||||
this.observedList.Clearing += new EventHandler(
|
||||
this.mockedSubscriber.MockObject.Clearing
|
||||
);
|
||||
this.observedList.Cleared += new EventHandler(
|
||||
this.mockedSubscriber.MockObject.Cleared
|
||||
);
|
||||
this.observedList.ItemAdded += new EventHandler<ItemEventArgs<int>>(
|
||||
this.mockedSubscriber.MockObject.ItemAdded
|
||||
);
|
||||
this.observedList.ItemRemoved += new EventHandler<ItemEventArgs<int>>(
|
||||
this.mockedSubscriber.MockObject.ItemRemoved
|
||||
);
|
||||
this.observedList.ItemReplaced += new EventHandler<ItemReplaceEventArgs<int>>(
|
||||
this.mockedSubscriber.MockObject.ItemReplaced
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>Tests whether the Clearing event is fired</summary>
|
||||
[Test]
|
||||
public void TestClearingEvent() {
|
||||
this.mockedSubscriber.Expects.One.Method(m => m.Clearing(null, null)).WithAnyArguments();
|
||||
this.mockedSubscriber.Expects.One.Method(m => m.Cleared(null, null)).WithAnyArguments();
|
||||
this.observedList.Clear();
|
||||
|
||||
this.mockery.VerifyAllExpectationsHaveBeenMet();
|
||||
}
|
||||
|
||||
/// <summary>Tests whether the ItemAdded event is fired</summary>
|
||||
[Test]
|
||||
public void TestItemAddedEvent() {
|
||||
this.mockedSubscriber.Expects.One.Method(m => m.ItemAdded(null, null)).WithAnyArguments();
|
||||
|
||||
this.observedList.Add(123);
|
||||
|
||||
this.mockery.VerifyAllExpectationsHaveBeenMet();
|
||||
}
|
||||
|
||||
/// <summary>Tests whether the ItemRemoved event is fired</summary>
|
||||
[Test]
|
||||
public void TestItemRemovedEvent() {
|
||||
this.mockedSubscriber.Expects.One.Method(m => m.ItemAdded(null, null)).WithAnyArguments();
|
||||
|
||||
this.observedList.Add(123);
|
||||
|
||||
this.mockedSubscriber.Expects.One.Method(m => m.ItemRemoved(null, null)).WithAnyArguments();
|
||||
|
||||
this.observedList.Remove(123);
|
||||
|
||||
this.mockery.VerifyAllExpectationsHaveBeenMet();
|
||||
}
|
||||
|
||||
/// <summary>Tests whether items in the collection can be replaced</summary>
|
||||
[Test]
|
||||
public void TestItemReplacement() {
|
||||
this.mockedSubscriber.Expects.Exactly(3).Method(
|
||||
m => m.ItemAdded(null, null)
|
||||
).WithAnyArguments();
|
||||
|
||||
this.observedList.Add(1);
|
||||
this.observedList.Add(2);
|
||||
this.observedList.Add(3);
|
||||
|
||||
this.mockedSubscriber.Expects.One.Method(m => m.ItemReplaced(null, null)).WithAnyArguments();
|
||||
|
||||
// Replace the middle item with something else
|
||||
this.observedList[1] = 4;
|
||||
|
||||
Assert.AreEqual(
|
||||
1, this.observedList.IndexOf(4)
|
||||
);
|
||||
|
||||
this.mockery.VerifyAllExpectationsHaveBeenMet();
|
||||
}
|
||||
|
||||
/// <summary>Tests whether a the list constructor is working</summary>
|
||||
[Test]
|
||||
public void TestListConstructor() {
|
||||
int[] integers = new int[] { 12, 34, 56, 78 };
|
||||
|
||||
var testList = new ObservableList<int>(integers);
|
||||
|
||||
CollectionAssert.AreEqual(integers, testList);
|
||||
}
|
||||
|
||||
/// <summary>Mock object factory</summary>
|
||||
private MockFactory mockery;
|
||||
/// <summary>The mocked observable collection subscriber</summary>
|
||||
private Mock<IObservableCollectionSubscriber> mockedSubscriber;
|
||||
/// <summary>An observable collection to which a mock will be subscribed</summary>
|
||||
private ObservableList<int> observedList;
|
||||
|
||||
}
|
||||
|
||||
} // namespace Nuclex.Support.Collections
|
||||
|
||||
#endif // UNITTEST
|
||||
|
||||
#endif // !NO_NMOCK
|
||||
#region Apache License 2.0
|
||||
/*
|
||||
Nuclex .NET Framework
|
||||
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
#endregion // Apache License 2.0
|
||||
|
||||
#if !NO_NMOCK
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
#if UNITTEST
|
||||
|
||||
using NUnit.Framework;
|
||||
using NMock;
|
||||
|
||||
namespace Nuclex.Support.Collections {
|
||||
|
||||
/// <summary>Unit Test for the observable list class</summary>
|
||||
[TestFixture]
|
||||
internal class ObservableListTest {
|
||||
|
||||
#region interface IObservableCollectionSubscriber
|
||||
|
||||
/// <summary>Interface used to test the observable collection</summary>
|
||||
public interface IObservableCollectionSubscriber {
|
||||
|
||||
/// <summary>Called when the collection is about to clear its contents</summary>
|
||||
/// <param name="sender">Collection that is clearing its contents</param>
|
||||
/// <param name="arguments">Not used</param>
|
||||
void Clearing(object sender, EventArgs arguments);
|
||||
|
||||
/// <summary>Called when the collection has been cleared of its contents</summary>
|
||||
/// <param name="sender">Collection that was cleared of its contents</param>
|
||||
/// <param name="arguments">Not used</param>
|
||||
void Cleared(object sender, EventArgs arguments);
|
||||
|
||||
/// <summary>Called when an item is added to the collection</summary>
|
||||
/// <param name="sender">Collection to which an item is being added</param>
|
||||
/// <param name="arguments">Contains the item that is being added</param>
|
||||
void ItemAdded(object sender, ItemEventArgs<int> arguments);
|
||||
|
||||
/// <summary>Called when an item is removed from the collection</summary>
|
||||
/// <param name="sender">Collection from which an item is being removed</param>
|
||||
/// <param name="arguments">Contains the item that is being removed</param>
|
||||
void ItemRemoved(object sender, ItemEventArgs<int> arguments);
|
||||
|
||||
/// <summary>Called when an item is replaced in the dictionary</summary>
|
||||
/// <param name="sender">Dictionary in which an item is being replaced</param>
|
||||
/// <param name="arguments">Contains the replaced item and its replacement</param>
|
||||
void ItemReplaced(object sender, ItemReplaceEventArgs<int> arguments);
|
||||
|
||||
}
|
||||
|
||||
#endregion // interface IObservableCollectionSubscriber
|
||||
|
||||
/// <summary>Initialization routine executed before each test is run</summary>
|
||||
[SetUp]
|
||||
public void Setup() {
|
||||
this.mockery = new MockFactory();
|
||||
|
||||
this.mockedSubscriber = this.mockery.CreateMock<IObservableCollectionSubscriber>();
|
||||
|
||||
this.observedList = new ObservableList<int>();
|
||||
this.observedList.Clearing += new EventHandler(
|
||||
this.mockedSubscriber.MockObject.Clearing
|
||||
);
|
||||
this.observedList.Cleared += new EventHandler(
|
||||
this.mockedSubscriber.MockObject.Cleared
|
||||
);
|
||||
this.observedList.ItemAdded += new EventHandler<ItemEventArgs<int>>(
|
||||
this.mockedSubscriber.MockObject.ItemAdded
|
||||
);
|
||||
this.observedList.ItemRemoved += new EventHandler<ItemEventArgs<int>>(
|
||||
this.mockedSubscriber.MockObject.ItemRemoved
|
||||
);
|
||||
this.observedList.ItemReplaced += new EventHandler<ItemReplaceEventArgs<int>>(
|
||||
this.mockedSubscriber.MockObject.ItemReplaced
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>Tests whether the Clearing event is fired</summary>
|
||||
[Test]
|
||||
public void TestClearingEvent() {
|
||||
this.mockedSubscriber.Expects.One.Method(m => m.Clearing(null, null)).WithAnyArguments();
|
||||
this.mockedSubscriber.Expects.One.Method(m => m.Cleared(null, null)).WithAnyArguments();
|
||||
this.observedList.Clear();
|
||||
|
||||
this.mockery.VerifyAllExpectationsHaveBeenMet();
|
||||
}
|
||||
|
||||
/// <summary>Tests whether the ItemAdded event is fired</summary>
|
||||
[Test]
|
||||
public void TestItemAddedEvent() {
|
||||
this.mockedSubscriber.Expects.One.Method(m => m.ItemAdded(null, null)).WithAnyArguments();
|
||||
|
||||
this.observedList.Add(123);
|
||||
|
||||
this.mockery.VerifyAllExpectationsHaveBeenMet();
|
||||
}
|
||||
|
||||
/// <summary>Tests whether the ItemRemoved event is fired</summary>
|
||||
[Test]
|
||||
public void TestItemRemovedEvent() {
|
||||
this.mockedSubscriber.Expects.One.Method(m => m.ItemAdded(null, null)).WithAnyArguments();
|
||||
|
||||
this.observedList.Add(123);
|
||||
|
||||
this.mockedSubscriber.Expects.One.Method(m => m.ItemRemoved(null, null)).WithAnyArguments();
|
||||
|
||||
this.observedList.Remove(123);
|
||||
|
||||
this.mockery.VerifyAllExpectationsHaveBeenMet();
|
||||
}
|
||||
|
||||
/// <summary>Tests whether items in the collection can be replaced</summary>
|
||||
[Test]
|
||||
public void TestItemReplacement() {
|
||||
this.mockedSubscriber.Expects.Exactly(3).Method(
|
||||
m => m.ItemAdded(null, null)
|
||||
).WithAnyArguments();
|
||||
|
||||
this.observedList.Add(1);
|
||||
this.observedList.Add(2);
|
||||
this.observedList.Add(3);
|
||||
|
||||
this.mockedSubscriber.Expects.One.Method(m => m.ItemReplaced(null, null)).WithAnyArguments();
|
||||
|
||||
// Replace the middle item with something else
|
||||
this.observedList[1] = 4;
|
||||
|
||||
Assert.AreEqual(
|
||||
1, this.observedList.IndexOf(4)
|
||||
);
|
||||
|
||||
this.mockery.VerifyAllExpectationsHaveBeenMet();
|
||||
}
|
||||
|
||||
/// <summary>Tests whether a the list constructor is working</summary>
|
||||
[Test]
|
||||
public void TestListConstructor() {
|
||||
int[] integers = new int[] { 12, 34, 56, 78 };
|
||||
|
||||
var testList = new ObservableList<int>(integers);
|
||||
|
||||
CollectionAssert.AreEqual(integers, testList);
|
||||
}
|
||||
|
||||
/// <summary>Mock object factory</summary>
|
||||
private MockFactory mockery;
|
||||
/// <summary>The mocked observable collection subscriber</summary>
|
||||
private Mock<IObservableCollectionSubscriber> mockedSubscriber;
|
||||
/// <summary>An observable collection to which a mock will be subscribed</summary>
|
||||
private ObservableList<int> observedList;
|
||||
|
||||
}
|
||||
|
||||
} // namespace Nuclex.Support.Collections
|
||||
|
||||
#endif // UNITTEST
|
||||
|
||||
#endif // !NO_NMOCK
|
||||
|
|
|
|||
|
|
@ -1,361 +1,360 @@
|
|||
#region CPL License
|
||||
/*
|
||||
Nuclex Framework
|
||||
Copyright (C) 2002-2017 Nuclex Development Labs
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the IBM Common Public License as
|
||||
published by the IBM Corporation; either version 1.0 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
IBM Common Public License for more details.
|
||||
|
||||
You should have received a copy of the IBM Common Public
|
||||
License along with this library
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
#if !NO_SPECIALIZED_COLLECTIONS
|
||||
using System.Collections.Specialized;
|
||||
#endif
|
||||
|
||||
namespace Nuclex.Support.Collections {
|
||||
|
||||
/// <summary>List which fires events when items are added or removed</summary>
|
||||
/// <typeparam name="TItem">Type of items the collection manages</typeparam>
|
||||
public class ObservableList<TItem> :
|
||||
IList<TItem>,
|
||||
IList,
|
||||
ICollection,
|
||||
#if !NO_SPECIALIZED_COLLECTIONS
|
||||
INotifyCollectionChanged,
|
||||
#endif
|
||||
IObservableCollection<TItem> {
|
||||
|
||||
/// <summary>Raised when an item has been added to the collection</summary>
|
||||
public event EventHandler<ItemEventArgs<TItem>> ItemAdded;
|
||||
/// <summary>Raised when an item is removed from the collection</summary>
|
||||
public event EventHandler<ItemEventArgs<TItem>> ItemRemoved;
|
||||
/// <summary>Raised when an item is replaced in the collection</summary>
|
||||
public event EventHandler<ItemReplaceEventArgs<TItem>> ItemReplaced;
|
||||
/// <summary>Raised when the collection is about to be cleared</summary>
|
||||
/// <remarks>
|
||||
/// This could be covered by calling ItemRemoved for each item currently
|
||||
/// contained in the collection, but it is often simpler and more efficient
|
||||
/// to process the clearing of the entire collection as a special operation.
|
||||
/// </remarks>
|
||||
public event EventHandler Clearing;
|
||||
/// <summary>Raised when the collection has been cleared</summary>
|
||||
public event EventHandler Cleared;
|
||||
|
||||
#if !NO_SPECIALIZED_COLLECTIONS
|
||||
/// <summary>Called when the collection has changed</summary>
|
||||
public event NotifyCollectionChangedEventHandler CollectionChanged;
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the ObservableList class that is empty.
|
||||
/// </summary>
|
||||
public ObservableList() : this(new List<TItem>()) { }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the ObservableList class as a wrapper
|
||||
/// for the specified list.
|
||||
/// </summary>
|
||||
/// <param name="list">The list that is wrapped by the new collection.</param>
|
||||
/// <exception cref="System.ArgumentNullException">List is null</exception>
|
||||
public ObservableList(IList<TItem> list) {
|
||||
this.typedList = list;
|
||||
this.objectList = list as IList; // Gah!
|
||||
}
|
||||
|
||||
/// <summary>Determines the index of the specified item in the list</summary>
|
||||
/// <param name="item">Item whose index will be determined</param>
|
||||
/// <returns>The index of the item in the list or -1 if not found</returns>
|
||||
public int IndexOf(TItem item) {
|
||||
return this.typedList.IndexOf(item);
|
||||
}
|
||||
|
||||
/// <summary>Inserts an item into the list at the specified index</summary>
|
||||
/// <param name="index">Index the item will be insertted at</param>
|
||||
/// <param name="item">Item that will be inserted into the list</param>
|
||||
public void Insert(int index, TItem item) {
|
||||
this.typedList.Insert(index, item);
|
||||
OnAdded(item, index);
|
||||
}
|
||||
|
||||
/// <summary>Removes the item at the specified index from the list</summary>
|
||||
/// <param name="index">Index at which the item will be removed</param>
|
||||
public void RemoveAt(int index) {
|
||||
TItem item = this.typedList[index];
|
||||
this.typedList.RemoveAt(index);
|
||||
OnRemoved(item, index);
|
||||
}
|
||||
|
||||
/// <summary>Accesses the item at the specified index in the list</summary>
|
||||
/// <param name="index">Index of the item that will be accessed</param>
|
||||
/// <returns>The item at the specified index</returns>
|
||||
public TItem this[int index] {
|
||||
get { return this.typedList[index]; }
|
||||
set {
|
||||
TItem oldItem = this.typedList[index];
|
||||
this.typedList[index] = value;
|
||||
OnReplaced(oldItem, value, index);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Adds an item to the end of the list</summary>
|
||||
/// <param name="item">Item that will be added to the list</param>
|
||||
public void Add(TItem item) {
|
||||
this.typedList.Add(item);
|
||||
OnAdded(item, this.typedList.Count - 1);
|
||||
}
|
||||
|
||||
/// <summary>Removes all items from the list</summary>
|
||||
public void Clear() {
|
||||
OnClearing();
|
||||
this.typedList.Clear();
|
||||
OnCleared();
|
||||
}
|
||||
|
||||
/// <summary>Checks whether the list contains the specified item</summary>
|
||||
/// <param name="item">Item the list will be checked for</param>
|
||||
/// <returns>True if the list contains the specified items</returns>
|
||||
public bool Contains(TItem item) {
|
||||
return this.typedList.Contains(item);
|
||||
}
|
||||
|
||||
/// <summary>Copies the contents of the list into an array</summary>
|
||||
/// <param name="array">Array the list will be copied into</param>
|
||||
/// <param name="arrayIndex">
|
||||
/// Index in the target array where the first item will be copied to
|
||||
/// </param>
|
||||
public void CopyTo(TItem[] array, int arrayIndex) {
|
||||
this.typedList.CopyTo(array, arrayIndex);
|
||||
}
|
||||
|
||||
/// <summary>Total number of items in the list</summary>
|
||||
public int Count {
|
||||
get { return this.typedList.Count; }
|
||||
}
|
||||
|
||||
/// <summary>Whether the list is a read-only list</summary>
|
||||
public bool IsReadOnly {
|
||||
get { return this.typedList.IsReadOnly; }
|
||||
}
|
||||
|
||||
/// <summary>Removes the specified item from the list</summary>
|
||||
/// <param name="item">Item that will be removed from the list</param>
|
||||
/// <returns>
|
||||
/// True if the item was found and removed from the list, false otherwise
|
||||
/// </returns>
|
||||
public bool Remove(TItem item) {
|
||||
int index = this.typedList.IndexOf(item);
|
||||
if(index == -1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
TItem removedItem = this.typedList[index];
|
||||
this.typedList.RemoveAt(index);
|
||||
OnRemoved(removedItem, index);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>Returns an enumerator for the items in the list</summary>
|
||||
/// <returns>An enumerator for the list's items</returns>
|
||||
public IEnumerator<TItem> GetEnumerator() {
|
||||
return this.typedList.GetEnumerator();
|
||||
}
|
||||
|
||||
#region IEnumerable implementation
|
||||
|
||||
/// <summary>Returns an enumerator for the items in the list</summary>
|
||||
/// <returns>An enumerator for the list's items</returns>
|
||||
IEnumerator IEnumerable.GetEnumerator() {
|
||||
return this.objectList.GetEnumerator();
|
||||
}
|
||||
|
||||
#endregion // IEnumerable implementation
|
||||
|
||||
#region ICollection implementation
|
||||
|
||||
/// <summary>Copies the contents of the list into an array</summary>
|
||||
/// <param name="array">Array the list will be copied into</param>
|
||||
/// <param name="arrayIndex">
|
||||
/// Index in the target array where the first item will be copied to
|
||||
/// </param>
|
||||
void ICollection.CopyTo(Array array, int arrayIndex) {
|
||||
this.objectList.CopyTo(array, arrayIndex);
|
||||
}
|
||||
|
||||
/// <summary>Whether this list performs thread synchronization</summary>
|
||||
bool ICollection.IsSynchronized {
|
||||
get { return this.objectList.IsSynchronized; }
|
||||
}
|
||||
|
||||
/// <summary>Synchronization root used by the list to synchronize threads</summary>
|
||||
object ICollection.SyncRoot {
|
||||
get { return this.objectList.SyncRoot; }
|
||||
}
|
||||
|
||||
#endregion // ICollection implementation
|
||||
|
||||
#region IList implementation
|
||||
|
||||
/// <summary>Adds an item to the list</summary>
|
||||
/// <param name="value">Item that will be added to the list</param>
|
||||
/// <returns>
|
||||
/// The position at which the item has been inserted or -1 if the item was not inserted
|
||||
/// </returns>
|
||||
int IList.Add(object value) {
|
||||
int index = this.objectList.Add(value);
|
||||
TItem addedItem = this.typedList[index];
|
||||
OnAdded(addedItem, index);
|
||||
return index;
|
||||
}
|
||||
|
||||
/// <summary>Checks whether the list contains the specified item</summary>
|
||||
/// <param name="item">Item the list will be checked for</param>
|
||||
/// <returns>True if the list contains the specified items</returns>
|
||||
bool IList.Contains(object item) {
|
||||
return this.objectList.Contains(item);
|
||||
}
|
||||
|
||||
/// <summary>Determines the index of the specified item in the list</summary>
|
||||
/// <param name="item">Item whose index will be determined</param>
|
||||
/// <returns>The index of the item in the list or -1 if not found</returns>
|
||||
int IList.IndexOf(object item) {
|
||||
return this.objectList.IndexOf(item);
|
||||
}
|
||||
|
||||
/// <summary>Inserts an item into the list at the specified index</summary>
|
||||
/// <param name="index">Index the item will be insertted at</param>
|
||||
/// <param name="item">Item that will be inserted into the list</param>
|
||||
void IList.Insert(int index, object item) {
|
||||
this.objectList.Insert(index, item);
|
||||
TItem addedItem = this.typedList[index];
|
||||
OnAdded(addedItem, index);
|
||||
}
|
||||
|
||||
/// <summary>Whether the list is of a fixed size</summary>
|
||||
bool IList.IsFixedSize {
|
||||
get { return this.objectList.IsFixedSize; }
|
||||
}
|
||||
|
||||
/// <summary>Removes the specified item from the list</summary>
|
||||
/// <param name="item">Item that will be removed from the list</param>
|
||||
void IList.Remove(object item) {
|
||||
int index = this.objectList.IndexOf(item);
|
||||
if(index == -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
TItem removedItem = this.typedList[index];
|
||||
this.objectList.RemoveAt(index);
|
||||
OnRemoved(removedItem, index);
|
||||
}
|
||||
|
||||
/// <summary>Accesses the item at the specified index in the list</summary>
|
||||
/// <param name="index">Index of the item that will be accessed</param>
|
||||
/// <returns>The item at the specified index</returns>
|
||||
object IList.this[int index] {
|
||||
get { return this.objectList[index]; }
|
||||
set {
|
||||
TItem oldItem = this.typedList[index];
|
||||
this.objectList[index] = value;
|
||||
TItem newItem = this.typedList[index];
|
||||
OnReplaced(oldItem, newItem, index);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion // IList implementation
|
||||
|
||||
/// <summary>Fires the 'ItemAdded' event</summary>
|
||||
/// <param name="item">Item that has been added to the collection</param>
|
||||
/// <param name="index">Index of the added item</param>
|
||||
protected virtual void OnAdded(TItem item, int index) {
|
||||
if(ItemAdded != null) {
|
||||
ItemAdded(this, new ItemEventArgs<TItem>(item));
|
||||
}
|
||||
#if !NO_SPECIALIZED_COLLECTIONS
|
||||
if(CollectionChanged != null) {
|
||||
CollectionChanged(
|
||||
this,
|
||||
new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item, index)
|
||||
);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>Fires the 'ItemRemoved' event</summary>
|
||||
/// <param name="item">Item that has been removed from the collection</param>
|
||||
/// <param name="index">Index the item has been removed from</param>
|
||||
protected virtual void OnRemoved(TItem item, int index) {
|
||||
if(ItemRemoved != null) {
|
||||
ItemRemoved(this, new ItemEventArgs<TItem>(item));
|
||||
}
|
||||
#if !NO_SPECIALIZED_COLLECTIONS
|
||||
if(CollectionChanged != null) {
|
||||
CollectionChanged(
|
||||
this,
|
||||
new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, item, index)
|
||||
);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>Fires the 'ItemReplaced' event</summary>
|
||||
/// <param name="oldItem">Item that has been replaced</param>
|
||||
/// <param name="newItem">New item the original item was replaced with</param>
|
||||
/// <param name="index">Index of the replaced item</param>
|
||||
protected virtual void OnReplaced(TItem oldItem, TItem newItem, int index) {
|
||||
if(ItemReplaced != null) {
|
||||
ItemReplaced(this, new ItemReplaceEventArgs<TItem>(oldItem, newItem));
|
||||
}
|
||||
#if !NO_SPECIALIZED_COLLECTIONS
|
||||
if(CollectionChanged != null) {
|
||||
CollectionChanged(
|
||||
this,
|
||||
new NotifyCollectionChangedEventArgs(
|
||||
NotifyCollectionChangedAction.Replace, newItem, oldItem, index
|
||||
)
|
||||
);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>Fires the 'Clearing' event</summary>
|
||||
protected virtual void OnClearing() {
|
||||
if(Clearing != null) {
|
||||
Clearing(this, EventArgs.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Fires the 'Cleared' event</summary>
|
||||
protected virtual void OnCleared() {
|
||||
if(Cleared != null) {
|
||||
Cleared(this, EventArgs.Empty);
|
||||
}
|
||||
#if !NO_SPECIALIZED_COLLECTIONS
|
||||
if(CollectionChanged != null) {
|
||||
CollectionChanged(this, Constants.NotifyCollectionResetEventArgs);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>The wrapped list under its type-safe interface</summary>
|
||||
private IList<TItem> typedList;
|
||||
/// <summary>The wrapped list under its object interface</summary>
|
||||
private IList objectList;
|
||||
|
||||
}
|
||||
|
||||
} // namespace Nuclex.Support.Collections
|
||||
#region Apache License 2.0
|
||||
/*
|
||||
Nuclex .NET Framework
|
||||
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
#endregion // Apache License 2.0
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
#if !NO_SPECIALIZED_COLLECTIONS
|
||||
using System.Collections.Specialized;
|
||||
#endif
|
||||
|
||||
namespace Nuclex.Support.Collections {
|
||||
|
||||
/// <summary>List which fires events when items are added or removed</summary>
|
||||
/// <typeparam name="TItem">Type of items the collection manages</typeparam>
|
||||
public class ObservableList<TItem> :
|
||||
IList<TItem>,
|
||||
IList,
|
||||
ICollection,
|
||||
#if !NO_SPECIALIZED_COLLECTIONS
|
||||
INotifyCollectionChanged,
|
||||
#endif
|
||||
IObservableCollection<TItem> {
|
||||
|
||||
/// <summary>Raised when an item has been added to the collection</summary>
|
||||
public event EventHandler<ItemEventArgs<TItem>> ItemAdded;
|
||||
/// <summary>Raised when an item is removed from the collection</summary>
|
||||
public event EventHandler<ItemEventArgs<TItem>> ItemRemoved;
|
||||
/// <summary>Raised when an item is replaced in the collection</summary>
|
||||
public event EventHandler<ItemReplaceEventArgs<TItem>> ItemReplaced;
|
||||
/// <summary>Raised when the collection is about to be cleared</summary>
|
||||
/// <remarks>
|
||||
/// This could be covered by calling ItemRemoved for each item currently
|
||||
/// contained in the collection, but it is often simpler and more efficient
|
||||
/// to process the clearing of the entire collection as a special operation.
|
||||
/// </remarks>
|
||||
public event EventHandler Clearing;
|
||||
/// <summary>Raised when the collection has been cleared</summary>
|
||||
public event EventHandler Cleared;
|
||||
|
||||
#if !NO_SPECIALIZED_COLLECTIONS
|
||||
/// <summary>Called when the collection has changed</summary>
|
||||
public event NotifyCollectionChangedEventHandler CollectionChanged;
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the ObservableList class that is empty.
|
||||
/// </summary>
|
||||
public ObservableList() : this(new List<TItem>()) { }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the ObservableList class as a wrapper
|
||||
/// for the specified list.
|
||||
/// </summary>
|
||||
/// <param name="list">The list that is wrapped by the new collection.</param>
|
||||
/// <exception cref="System.ArgumentNullException">List is null</exception>
|
||||
public ObservableList(IList<TItem> list) {
|
||||
this.typedList = list;
|
||||
this.objectList = list as IList; // Gah!
|
||||
}
|
||||
|
||||
/// <summary>Determines the index of the specified item in the list</summary>
|
||||
/// <param name="item">Item whose index will be determined</param>
|
||||
/// <returns>The index of the item in the list or -1 if not found</returns>
|
||||
public int IndexOf(TItem item) {
|
||||
return this.typedList.IndexOf(item);
|
||||
}
|
||||
|
||||
/// <summary>Inserts an item into the list at the specified index</summary>
|
||||
/// <param name="index">Index the item will be insertted at</param>
|
||||
/// <param name="item">Item that will be inserted into the list</param>
|
||||
public void Insert(int index, TItem item) {
|
||||
this.typedList.Insert(index, item);
|
||||
OnAdded(item, index);
|
||||
}
|
||||
|
||||
/// <summary>Removes the item at the specified index from the list</summary>
|
||||
/// <param name="index">Index at which the item will be removed</param>
|
||||
public void RemoveAt(int index) {
|
||||
TItem item = this.typedList[index];
|
||||
this.typedList.RemoveAt(index);
|
||||
OnRemoved(item, index);
|
||||
}
|
||||
|
||||
/// <summary>Accesses the item at the specified index in the list</summary>
|
||||
/// <param name="index">Index of the item that will be accessed</param>
|
||||
/// <returns>The item at the specified index</returns>
|
||||
public TItem this[int index] {
|
||||
get { return this.typedList[index]; }
|
||||
set {
|
||||
TItem oldItem = this.typedList[index];
|
||||
this.typedList[index] = value;
|
||||
OnReplaced(oldItem, value, index);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Adds an item to the end of the list</summary>
|
||||
/// <param name="item">Item that will be added to the list</param>
|
||||
public void Add(TItem item) {
|
||||
this.typedList.Add(item);
|
||||
OnAdded(item, this.typedList.Count - 1);
|
||||
}
|
||||
|
||||
/// <summary>Removes all items from the list</summary>
|
||||
public void Clear() {
|
||||
OnClearing();
|
||||
this.typedList.Clear();
|
||||
OnCleared();
|
||||
}
|
||||
|
||||
/// <summary>Checks whether the list contains the specified item</summary>
|
||||
/// <param name="item">Item the list will be checked for</param>
|
||||
/// <returns>True if the list contains the specified items</returns>
|
||||
public bool Contains(TItem item) {
|
||||
return this.typedList.Contains(item);
|
||||
}
|
||||
|
||||
/// <summary>Copies the contents of the list into an array</summary>
|
||||
/// <param name="array">Array the list will be copied into</param>
|
||||
/// <param name="arrayIndex">
|
||||
/// Index in the target array where the first item will be copied to
|
||||
/// </param>
|
||||
public void CopyTo(TItem[] array, int arrayIndex) {
|
||||
this.typedList.CopyTo(array, arrayIndex);
|
||||
}
|
||||
|
||||
/// <summary>Total number of items in the list</summary>
|
||||
public int Count {
|
||||
get { return this.typedList.Count; }
|
||||
}
|
||||
|
||||
/// <summary>Whether the list is a read-only list</summary>
|
||||
public bool IsReadOnly {
|
||||
get { return this.typedList.IsReadOnly; }
|
||||
}
|
||||
|
||||
/// <summary>Removes the specified item from the list</summary>
|
||||
/// <param name="item">Item that will be removed from the list</param>
|
||||
/// <returns>
|
||||
/// True if the item was found and removed from the list, false otherwise
|
||||
/// </returns>
|
||||
public bool Remove(TItem item) {
|
||||
int index = this.typedList.IndexOf(item);
|
||||
if(index == -1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
TItem removedItem = this.typedList[index];
|
||||
this.typedList.RemoveAt(index);
|
||||
OnRemoved(removedItem, index);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>Returns an enumerator for the items in the list</summary>
|
||||
/// <returns>An enumerator for the list's items</returns>
|
||||
public IEnumerator<TItem> GetEnumerator() {
|
||||
return this.typedList.GetEnumerator();
|
||||
}
|
||||
|
||||
#region IEnumerable implementation
|
||||
|
||||
/// <summary>Returns an enumerator for the items in the list</summary>
|
||||
/// <returns>An enumerator for the list's items</returns>
|
||||
IEnumerator IEnumerable.GetEnumerator() {
|
||||
return this.objectList.GetEnumerator();
|
||||
}
|
||||
|
||||
#endregion // IEnumerable implementation
|
||||
|
||||
#region ICollection implementation
|
||||
|
||||
/// <summary>Copies the contents of the list into an array</summary>
|
||||
/// <param name="array">Array the list will be copied into</param>
|
||||
/// <param name="arrayIndex">
|
||||
/// Index in the target array where the first item will be copied to
|
||||
/// </param>
|
||||
void ICollection.CopyTo(Array array, int arrayIndex) {
|
||||
this.objectList.CopyTo(array, arrayIndex);
|
||||
}
|
||||
|
||||
/// <summary>Whether this list performs thread synchronization</summary>
|
||||
bool ICollection.IsSynchronized {
|
||||
get { return this.objectList.IsSynchronized; }
|
||||
}
|
||||
|
||||
/// <summary>Synchronization root used by the list to synchronize threads</summary>
|
||||
object ICollection.SyncRoot {
|
||||
get { return this.objectList.SyncRoot; }
|
||||
}
|
||||
|
||||
#endregion // ICollection implementation
|
||||
|
||||
#region IList implementation
|
||||
|
||||
/// <summary>Adds an item to the list</summary>
|
||||
/// <param name="value">Item that will be added to the list</param>
|
||||
/// <returns>
|
||||
/// The position at which the item has been inserted or -1 if the item was not inserted
|
||||
/// </returns>
|
||||
int IList.Add(object value) {
|
||||
int index = this.objectList.Add(value);
|
||||
TItem addedItem = this.typedList[index];
|
||||
OnAdded(addedItem, index);
|
||||
return index;
|
||||
}
|
||||
|
||||
/// <summary>Checks whether the list contains the specified item</summary>
|
||||
/// <param name="item">Item the list will be checked for</param>
|
||||
/// <returns>True if the list contains the specified items</returns>
|
||||
bool IList.Contains(object item) {
|
||||
return this.objectList.Contains(item);
|
||||
}
|
||||
|
||||
/// <summary>Determines the index of the specified item in the list</summary>
|
||||
/// <param name="item">Item whose index will be determined</param>
|
||||
/// <returns>The index of the item in the list or -1 if not found</returns>
|
||||
int IList.IndexOf(object item) {
|
||||
return this.objectList.IndexOf(item);
|
||||
}
|
||||
|
||||
/// <summary>Inserts an item into the list at the specified index</summary>
|
||||
/// <param name="index">Index the item will be insertted at</param>
|
||||
/// <param name="item">Item that will be inserted into the list</param>
|
||||
void IList.Insert(int index, object item) {
|
||||
this.objectList.Insert(index, item);
|
||||
TItem addedItem = this.typedList[index];
|
||||
OnAdded(addedItem, index);
|
||||
}
|
||||
|
||||
/// <summary>Whether the list is of a fixed size</summary>
|
||||
bool IList.IsFixedSize {
|
||||
get { return this.objectList.IsFixedSize; }
|
||||
}
|
||||
|
||||
/// <summary>Removes the specified item from the list</summary>
|
||||
/// <param name="item">Item that will be removed from the list</param>
|
||||
void IList.Remove(object item) {
|
||||
int index = this.objectList.IndexOf(item);
|
||||
if(index == -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
TItem removedItem = this.typedList[index];
|
||||
this.objectList.RemoveAt(index);
|
||||
OnRemoved(removedItem, index);
|
||||
}
|
||||
|
||||
/// <summary>Accesses the item at the specified index in the list</summary>
|
||||
/// <param name="index">Index of the item that will be accessed</param>
|
||||
/// <returns>The item at the specified index</returns>
|
||||
object IList.this[int index] {
|
||||
get { return this.objectList[index]; }
|
||||
set {
|
||||
TItem oldItem = this.typedList[index];
|
||||
this.objectList[index] = value;
|
||||
TItem newItem = this.typedList[index];
|
||||
OnReplaced(oldItem, newItem, index);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion // IList implementation
|
||||
|
||||
/// <summary>Fires the 'ItemAdded' event</summary>
|
||||
/// <param name="item">Item that has been added to the collection</param>
|
||||
/// <param name="index">Index of the added item</param>
|
||||
protected virtual void OnAdded(TItem item, int index) {
|
||||
if(ItemAdded != null) {
|
||||
ItemAdded(this, new ItemEventArgs<TItem>(item));
|
||||
}
|
||||
#if !NO_SPECIALIZED_COLLECTIONS
|
||||
if(CollectionChanged != null) {
|
||||
CollectionChanged(
|
||||
this,
|
||||
new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item, index)
|
||||
);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>Fires the 'ItemRemoved' event</summary>
|
||||
/// <param name="item">Item that has been removed from the collection</param>
|
||||
/// <param name="index">Index the item has been removed from</param>
|
||||
protected virtual void OnRemoved(TItem item, int index) {
|
||||
if(ItemRemoved != null) {
|
||||
ItemRemoved(this, new ItemEventArgs<TItem>(item));
|
||||
}
|
||||
#if !NO_SPECIALIZED_COLLECTIONS
|
||||
if(CollectionChanged != null) {
|
||||
CollectionChanged(
|
||||
this,
|
||||
new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, item, index)
|
||||
);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>Fires the 'ItemReplaced' event</summary>
|
||||
/// <param name="oldItem">Item that has been replaced</param>
|
||||
/// <param name="newItem">New item the original item was replaced with</param>
|
||||
/// <param name="index">Index of the replaced item</param>
|
||||
protected virtual void OnReplaced(TItem oldItem, TItem newItem, int index) {
|
||||
if(ItemReplaced != null) {
|
||||
ItemReplaced(this, new ItemReplaceEventArgs<TItem>(oldItem, newItem));
|
||||
}
|
||||
#if !NO_SPECIALIZED_COLLECTIONS
|
||||
if(CollectionChanged != null) {
|
||||
CollectionChanged(
|
||||
this,
|
||||
new NotifyCollectionChangedEventArgs(
|
||||
NotifyCollectionChangedAction.Replace, newItem, oldItem, index
|
||||
)
|
||||
);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>Fires the 'Clearing' event</summary>
|
||||
protected virtual void OnClearing() {
|
||||
if(Clearing != null) {
|
||||
Clearing(this, EventArgs.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Fires the 'Cleared' event</summary>
|
||||
protected virtual void OnCleared() {
|
||||
if(Cleared != null) {
|
||||
Cleared(this, EventArgs.Empty);
|
||||
}
|
||||
#if !NO_SPECIALIZED_COLLECTIONS
|
||||
if(CollectionChanged != null) {
|
||||
CollectionChanged(this, Constants.NotifyCollectionResetEventArgs);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>The wrapped list under its type-safe interface</summary>
|
||||
private IList<TItem> typedList;
|
||||
/// <summary>The wrapped list under its object interface</summary>
|
||||
private IList objectList;
|
||||
|
||||
}
|
||||
|
||||
} // namespace Nuclex.Support.Collections
|
||||
|
|
|
|||
|
|
@ -1,301 +1,300 @@
|
|||
#region CPL License
|
||||
/*
|
||||
Nuclex Framework
|
||||
Copyright (C) 2002-2017 Nuclex Development Labs
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the IBM Common Public License as
|
||||
published by the IBM Corporation; either version 1.0 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
IBM Common Public License for more details.
|
||||
|
||||
You should have received a copy of the IBM Common Public
|
||||
License along with this library
|
||||
*/
|
||||
#endregion
|
||||
|
||||
#if !NO_SETS
|
||||
|
||||
#if UNITTEST
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Runtime.Serialization.Formatters.Binary;
|
||||
|
||||
using NUnit.Framework;
|
||||
using NMock;
|
||||
|
||||
namespace Nuclex.Support.Collections {
|
||||
|
||||
/// <summary>Unit Test for the observable set wrapper</summary>
|
||||
[TestFixture]
|
||||
internal class ObservableSetTest {
|
||||
|
||||
#region interface IObservableCollectionSubscriber<TItem>
|
||||
|
||||
public interface IObservableCollectionSubscriber<TItem> {
|
||||
|
||||
/// <summary>Called when an item has been added to the collection</summary>
|
||||
void ItemAdded(object sender, ItemEventArgs<TItem> arguments);
|
||||
/// <summary>Called when an item is removed from the collection</summary>
|
||||
void ItemRemoved(object sender, ItemEventArgs<TItem> arguments);
|
||||
/// <summary>Called when an item is replaced in the collection</summary>
|
||||
void ItemReplaced(object sender, ItemReplaceEventArgs<TItem> arguments);
|
||||
/// <summary>Called when the collection is about to be cleared</summary>
|
||||
void Clearing(object sender, EventArgs arguments);
|
||||
/// <summary>Called when the collection has been cleared</summary>
|
||||
void Cleared(object sender, EventArgs arguments);
|
||||
|
||||
}
|
||||
|
||||
#endregion // interface IObservableCollectionSubscriber<TItem>
|
||||
|
||||
/// <summary>Called before each test is run</summary>
|
||||
[SetUp]
|
||||
public void Setup() {
|
||||
this.mockFactory = new MockFactory();
|
||||
this.observableSet = new ObservableSet<int>();
|
||||
|
||||
this.subscriber = this.mockFactory.CreateMock<IObservableCollectionSubscriber<int>>();
|
||||
this.observableSet.ItemAdded += this.subscriber.MockObject.ItemAdded;
|
||||
this.observableSet.ItemRemoved += this.subscriber.MockObject.ItemRemoved;
|
||||
this.observableSet.ItemReplaced += this.subscriber.MockObject.ItemReplaced;
|
||||
this.observableSet.Clearing += this.subscriber.MockObject.Clearing;
|
||||
this.observableSet.Cleared += this.subscriber.MockObject.Cleared;
|
||||
}
|
||||
|
||||
/// <summary>Called after each test has run</summary>
|
||||
[TearDown]
|
||||
public void Teardown() {
|
||||
if(this.mockFactory != null) {
|
||||
this.mockFactory.VerifyAllExpectationsHaveBeenMet();
|
||||
|
||||
this.subscriber = null;
|
||||
this.mockFactory.Dispose();
|
||||
this.mockFactory = null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that the observable set has a default constructor
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void HasDefaultConstructor() {
|
||||
Assert.IsNotNull(new ObservableSet<int>());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that adding items to the set triggers the 'ItemAdded' event
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void AddingItemsTriggersEvent() {
|
||||
this.subscriber.Expects.One.Method((s) => s.ItemAdded(null, null)).WithAnyArguments();
|
||||
this.observableSet.Add(123);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that removing items from the set triggers the 'ItemRemoved' event
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void RemovingItemsTriggersEvent() {
|
||||
this.subscriber.Expects.One.Method((s) => s.ItemAdded(null, null)).WithAnyArguments();
|
||||
this.observableSet.Add(123);
|
||||
|
||||
this.subscriber.Expects.One.Method((s) => s.ItemRemoved(null, null)).WithAnyArguments();
|
||||
this.observableSet.Remove(123);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that adding items to the set triggers the 'ItemAdded' event
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void AddingAlreadyContainedItemDoesNotTriggerEvent() {
|
||||
this.subscriber.Expects.One.Method((s) => s.ItemAdded(null, null)).WithAnyArguments();
|
||||
this.observableSet.Add(123);
|
||||
|
||||
this.subscriber.Expects.No.Method((s) => s.ItemAdded(null, null)).WithAnyArguments();
|
||||
this.observableSet.Add(123);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that excepting the set with itself empties the set
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void ExceptWithSelfEmptiesSet() {
|
||||
this.subscriber.Expects.Exactly(3).Method(
|
||||
(s) => s.ItemAdded(null, null)
|
||||
).WithAnyArguments();
|
||||
|
||||
this.observableSet.Add(1);
|
||||
this.observableSet.Add(2);
|
||||
this.observableSet.Add(3);
|
||||
|
||||
Assert.AreEqual(3, this.observableSet.Count);
|
||||
|
||||
this.subscriber.Expects.One.Method((s) => s.Clearing(null, null)).WithAnyArguments();
|
||||
this.subscriber.Expects.One.Method((s) => s.Cleared(null, null)).WithAnyArguments();
|
||||
|
||||
this.observableSet.ExceptWith(this.observableSet);
|
||||
Assert.AreEqual(0, this.observableSet.Count);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that a set can be excepted with a collection
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void SetCanBeExceptedWithCollection() {
|
||||
this.subscriber.Expects.Exactly(2).Method(
|
||||
(s) => s.ItemAdded(null, null)
|
||||
).WithAnyArguments();
|
||||
|
||||
this.observableSet.Add(1);
|
||||
this.observableSet.Add(2);
|
||||
|
||||
var collection = new List<int>() { 1 };
|
||||
|
||||
this.subscriber.Expects.One.Method((s) => s.ItemRemoved(null, null)).WithAnyArguments();
|
||||
this.observableSet.ExceptWith(collection);
|
||||
Assert.AreEqual(1, this.observableSet.Count);
|
||||
Assert.IsTrue(this.observableSet.Contains(2));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that a set can be intersected with a collection
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void SetCanBeIntersectedWithCollection() {
|
||||
this.subscriber.Expects.Exactly(2).Method(
|
||||
(s) => s.ItemAdded(null, null)
|
||||
).WithAnyArguments();
|
||||
|
||||
this.observableSet.Add(1);
|
||||
this.observableSet.Add(2);
|
||||
|
||||
var collection = new List<int>() { 1 };
|
||||
|
||||
this.subscriber.Expects.One.Method((s) => s.ItemRemoved(null, null)).WithAnyArguments();
|
||||
this.observableSet.IntersectWith(collection);
|
||||
Assert.AreEqual(1, this.observableSet.Count);
|
||||
Assert.IsTrue(this.observableSet.Contains(1));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that it's possible to determine whether a set is a proper subset
|
||||
/// or superset of another set
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void CanDetermineProperSubsetAndSuperset() {
|
||||
var set1 = new ObservableSet<int>() { 1, 2, 3 };
|
||||
var set2 = new HashSet<int>() { 1, 3 };
|
||||
|
||||
Assert.IsTrue(set1.IsProperSupersetOf(set2));
|
||||
Assert.IsTrue(set2.IsProperSubsetOf(set1));
|
||||
|
||||
set2.Add(2);
|
||||
|
||||
Assert.IsFalse(set1.IsProperSupersetOf(set2));
|
||||
Assert.IsFalse(set2.IsProperSubsetOf(set1));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that it's possible to determine whether a set is a subset
|
||||
/// or a superset of another set
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void CanDetermineSubsetAndSuperset() {
|
||||
var set1 = new ObservableSet<int>() { 1, 2, 3 };
|
||||
var set2 = new HashSet<int>() { 1, 2, 3 };
|
||||
|
||||
Assert.IsTrue(set1.IsSupersetOf(set2));
|
||||
Assert.IsTrue(set2.IsSubsetOf(set1));
|
||||
|
||||
set2.Add(4);
|
||||
|
||||
Assert.IsFalse(set1.IsSupersetOf(set2));
|
||||
Assert.IsFalse(set2.IsSubsetOf(set1));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that a set can determine if another set overlaps with it
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void CanDetermineOverlap() {
|
||||
var set1 = new ObservableSet<int>() { 1, 3, 5 };
|
||||
var set2 = new HashSet<int>() { 3 };
|
||||
|
||||
Assert.IsTrue(set1.Overlaps(set2));
|
||||
Assert.IsTrue(set2.Overlaps(set1));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that a set can determine if another set contains the same elements
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void CanDetermineSetEquality() {
|
||||
var set1 = new ObservableSet<int>() { 1, 3, 5 };
|
||||
var set2 = new HashSet<int>() { 3, 1, 5 };
|
||||
|
||||
Assert.IsTrue(set1.SetEquals(set2));
|
||||
Assert.IsTrue(set2.SetEquals(set1));
|
||||
|
||||
set1.Add(7);
|
||||
|
||||
Assert.IsFalse(set1.SetEquals(set2));
|
||||
Assert.IsFalse(set2.SetEquals(set1));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that a set can be symmetrically excepted with another set
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void CanBeSymmetricallyExcepted() {
|
||||
var set1 = new ObservableSet<int>() { 1, 2, 3 };
|
||||
var set2 = new HashSet<int>() { 3, 4, 5 };
|
||||
|
||||
set1.SymmetricExceptWith(set2);
|
||||
|
||||
Assert.AreEqual(4, set1.Count);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that a union of two sets can be built
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void CanBeUnioned() {
|
||||
this.subscriber.Expects.Exactly(3).Method(
|
||||
(s) => s.ItemAdded(null, null)
|
||||
).WithAnyArguments();
|
||||
|
||||
this.observableSet.Add(1);
|
||||
this.observableSet.Add(2);
|
||||
this.observableSet.Add(3);
|
||||
|
||||
var set2 = new ObservableSet<int>() { 3, 4, 5 };
|
||||
|
||||
this.subscriber.Expects.Exactly(2).Method(
|
||||
(s) => s.ItemAdded(null, null)
|
||||
).WithAnyArguments();
|
||||
this.observableSet.UnionWith(set2);
|
||||
Assert.AreEqual(5, this.observableSet.Count);
|
||||
}
|
||||
|
||||
/// <summary>Creates mock object for the test</summary>
|
||||
private MockFactory mockFactory;
|
||||
/// <summary>Observable set being tested</summary>
|
||||
private ObservableSet<int> observableSet;
|
||||
/// <summary>Subscriber for the observable set's events</summary>
|
||||
private Mock<IObservableCollectionSubscriber<int>> subscriber;
|
||||
|
||||
}
|
||||
|
||||
} // namespace Nuclex.Support.Collections
|
||||
|
||||
#endif // UNITTEST
|
||||
|
||||
#endif // !NO_SETS
|
||||
#region Apache License 2.0
|
||||
/*
|
||||
Nuclex .NET Framework
|
||||
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
#endregion // Apache License 2.0
|
||||
|
||||
#if !NO_SETS
|
||||
|
||||
#if UNITTEST
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Runtime.Serialization.Formatters.Binary;
|
||||
|
||||
using NUnit.Framework;
|
||||
using NMock;
|
||||
|
||||
namespace Nuclex.Support.Collections {
|
||||
|
||||
/// <summary>Unit Test for the observable set wrapper</summary>
|
||||
[TestFixture]
|
||||
internal class ObservableSetTest {
|
||||
|
||||
#region interface IObservableCollectionSubscriber<TItem>
|
||||
|
||||
public interface IObservableCollectionSubscriber<TItem> {
|
||||
|
||||
/// <summary>Called when an item has been added to the collection</summary>
|
||||
void ItemAdded(object sender, ItemEventArgs<TItem> arguments);
|
||||
/// <summary>Called when an item is removed from the collection</summary>
|
||||
void ItemRemoved(object sender, ItemEventArgs<TItem> arguments);
|
||||
/// <summary>Called when an item is replaced in the collection</summary>
|
||||
void ItemReplaced(object sender, ItemReplaceEventArgs<TItem> arguments);
|
||||
/// <summary>Called when the collection is about to be cleared</summary>
|
||||
void Clearing(object sender, EventArgs arguments);
|
||||
/// <summary>Called when the collection has been cleared</summary>
|
||||
void Cleared(object sender, EventArgs arguments);
|
||||
|
||||
}
|
||||
|
||||
#endregion // interface IObservableCollectionSubscriber<TItem>
|
||||
|
||||
/// <summary>Called before each test is run</summary>
|
||||
[SetUp]
|
||||
public void Setup() {
|
||||
this.mockFactory = new MockFactory();
|
||||
this.observableSet = new ObservableSet<int>();
|
||||
|
||||
this.subscriber = this.mockFactory.CreateMock<IObservableCollectionSubscriber<int>>();
|
||||
this.observableSet.ItemAdded += this.subscriber.MockObject.ItemAdded;
|
||||
this.observableSet.ItemRemoved += this.subscriber.MockObject.ItemRemoved;
|
||||
this.observableSet.ItemReplaced += this.subscriber.MockObject.ItemReplaced;
|
||||
this.observableSet.Clearing += this.subscriber.MockObject.Clearing;
|
||||
this.observableSet.Cleared += this.subscriber.MockObject.Cleared;
|
||||
}
|
||||
|
||||
/// <summary>Called after each test has run</summary>
|
||||
[TearDown]
|
||||
public void Teardown() {
|
||||
if(this.mockFactory != null) {
|
||||
this.mockFactory.VerifyAllExpectationsHaveBeenMet();
|
||||
|
||||
this.subscriber = null;
|
||||
this.mockFactory.Dispose();
|
||||
this.mockFactory = null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that the observable set has a default constructor
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void HasDefaultConstructor() {
|
||||
Assert.IsNotNull(new ObservableSet<int>());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that adding items to the set triggers the 'ItemAdded' event
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void AddingItemsTriggersEvent() {
|
||||
this.subscriber.Expects.One.Method((s) => s.ItemAdded(null, null)).WithAnyArguments();
|
||||
this.observableSet.Add(123);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that removing items from the set triggers the 'ItemRemoved' event
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void RemovingItemsTriggersEvent() {
|
||||
this.subscriber.Expects.One.Method((s) => s.ItemAdded(null, null)).WithAnyArguments();
|
||||
this.observableSet.Add(123);
|
||||
|
||||
this.subscriber.Expects.One.Method((s) => s.ItemRemoved(null, null)).WithAnyArguments();
|
||||
this.observableSet.Remove(123);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that adding items to the set triggers the 'ItemAdded' event
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void AddingAlreadyContainedItemDoesNotTriggerEvent() {
|
||||
this.subscriber.Expects.One.Method((s) => s.ItemAdded(null, null)).WithAnyArguments();
|
||||
this.observableSet.Add(123);
|
||||
|
||||
this.subscriber.Expects.No.Method((s) => s.ItemAdded(null, null)).WithAnyArguments();
|
||||
this.observableSet.Add(123);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that excepting the set with itself empties the set
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void ExceptWithSelfEmptiesSet() {
|
||||
this.subscriber.Expects.Exactly(3).Method(
|
||||
(s) => s.ItemAdded(null, null)
|
||||
).WithAnyArguments();
|
||||
|
||||
this.observableSet.Add(1);
|
||||
this.observableSet.Add(2);
|
||||
this.observableSet.Add(3);
|
||||
|
||||
Assert.AreEqual(3, this.observableSet.Count);
|
||||
|
||||
this.subscriber.Expects.One.Method((s) => s.Clearing(null, null)).WithAnyArguments();
|
||||
this.subscriber.Expects.One.Method((s) => s.Cleared(null, null)).WithAnyArguments();
|
||||
|
||||
this.observableSet.ExceptWith(this.observableSet);
|
||||
Assert.AreEqual(0, this.observableSet.Count);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that a set can be excepted with a collection
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void SetCanBeExceptedWithCollection() {
|
||||
this.subscriber.Expects.Exactly(2).Method(
|
||||
(s) => s.ItemAdded(null, null)
|
||||
).WithAnyArguments();
|
||||
|
||||
this.observableSet.Add(1);
|
||||
this.observableSet.Add(2);
|
||||
|
||||
var collection = new List<int>() { 1 };
|
||||
|
||||
this.subscriber.Expects.One.Method((s) => s.ItemRemoved(null, null)).WithAnyArguments();
|
||||
this.observableSet.ExceptWith(collection);
|
||||
Assert.AreEqual(1, this.observableSet.Count);
|
||||
Assert.IsTrue(this.observableSet.Contains(2));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that a set can be intersected with a collection
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void SetCanBeIntersectedWithCollection() {
|
||||
this.subscriber.Expects.Exactly(2).Method(
|
||||
(s) => s.ItemAdded(null, null)
|
||||
).WithAnyArguments();
|
||||
|
||||
this.observableSet.Add(1);
|
||||
this.observableSet.Add(2);
|
||||
|
||||
var collection = new List<int>() { 1 };
|
||||
|
||||
this.subscriber.Expects.One.Method((s) => s.ItemRemoved(null, null)).WithAnyArguments();
|
||||
this.observableSet.IntersectWith(collection);
|
||||
Assert.AreEqual(1, this.observableSet.Count);
|
||||
Assert.IsTrue(this.observableSet.Contains(1));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that it's possible to determine whether a set is a proper subset
|
||||
/// or superset of another set
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void CanDetermineProperSubsetAndSuperset() {
|
||||
var set1 = new ObservableSet<int>() { 1, 2, 3 };
|
||||
var set2 = new HashSet<int>() { 1, 3 };
|
||||
|
||||
Assert.IsTrue(set1.IsProperSupersetOf(set2));
|
||||
Assert.IsTrue(set2.IsProperSubsetOf(set1));
|
||||
|
||||
set2.Add(2);
|
||||
|
||||
Assert.IsFalse(set1.IsProperSupersetOf(set2));
|
||||
Assert.IsFalse(set2.IsProperSubsetOf(set1));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that it's possible to determine whether a set is a subset
|
||||
/// or a superset of another set
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void CanDetermineSubsetAndSuperset() {
|
||||
var set1 = new ObservableSet<int>() { 1, 2, 3 };
|
||||
var set2 = new HashSet<int>() { 1, 2, 3 };
|
||||
|
||||
Assert.IsTrue(set1.IsSupersetOf(set2));
|
||||
Assert.IsTrue(set2.IsSubsetOf(set1));
|
||||
|
||||
set2.Add(4);
|
||||
|
||||
Assert.IsFalse(set1.IsSupersetOf(set2));
|
||||
Assert.IsFalse(set2.IsSubsetOf(set1));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that a set can determine if another set overlaps with it
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void CanDetermineOverlap() {
|
||||
var set1 = new ObservableSet<int>() { 1, 3, 5 };
|
||||
var set2 = new HashSet<int>() { 3 };
|
||||
|
||||
Assert.IsTrue(set1.Overlaps(set2));
|
||||
Assert.IsTrue(set2.Overlaps(set1));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that a set can determine if another set contains the same elements
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void CanDetermineSetEquality() {
|
||||
var set1 = new ObservableSet<int>() { 1, 3, 5 };
|
||||
var set2 = new HashSet<int>() { 3, 1, 5 };
|
||||
|
||||
Assert.IsTrue(set1.SetEquals(set2));
|
||||
Assert.IsTrue(set2.SetEquals(set1));
|
||||
|
||||
set1.Add(7);
|
||||
|
||||
Assert.IsFalse(set1.SetEquals(set2));
|
||||
Assert.IsFalse(set2.SetEquals(set1));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that a set can be symmetrically excepted with another set
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void CanBeSymmetricallyExcepted() {
|
||||
var set1 = new ObservableSet<int>() { 1, 2, 3 };
|
||||
var set2 = new HashSet<int>() { 3, 4, 5 };
|
||||
|
||||
set1.SymmetricExceptWith(set2);
|
||||
|
||||
Assert.AreEqual(4, set1.Count);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that a union of two sets can be built
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void CanBeUnioned() {
|
||||
this.subscriber.Expects.Exactly(3).Method(
|
||||
(s) => s.ItemAdded(null, null)
|
||||
).WithAnyArguments();
|
||||
|
||||
this.observableSet.Add(1);
|
||||
this.observableSet.Add(2);
|
||||
this.observableSet.Add(3);
|
||||
|
||||
var set2 = new ObservableSet<int>() { 3, 4, 5 };
|
||||
|
||||
this.subscriber.Expects.Exactly(2).Method(
|
||||
(s) => s.ItemAdded(null, null)
|
||||
).WithAnyArguments();
|
||||
this.observableSet.UnionWith(set2);
|
||||
Assert.AreEqual(5, this.observableSet.Count);
|
||||
}
|
||||
|
||||
/// <summary>Creates mock object for the test</summary>
|
||||
private MockFactory mockFactory;
|
||||
/// <summary>Observable set being tested</summary>
|
||||
private ObservableSet<int> observableSet;
|
||||
/// <summary>Subscriber for the observable set's events</summary>
|
||||
private Mock<IObservableCollectionSubscriber<int>> subscriber;
|
||||
|
||||
}
|
||||
|
||||
} // namespace Nuclex.Support.Collections
|
||||
|
||||
#endif // UNITTEST
|
||||
|
||||
#endif // !NO_SETS
|
||||
|
|
|
|||
|
|
@ -1,340 +1,339 @@
|
|||
#region CPL License
|
||||
/*
|
||||
Nuclex Framework
|
||||
Copyright (C) 2002-2017 Nuclex Development Labs
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the IBM Common Public License as
|
||||
published by the IBM Corporation; either version 1.0 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
IBM Common Public License for more details.
|
||||
|
||||
You should have received a copy of the IBM Common Public
|
||||
License along with this library
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections;
|
||||
|
||||
#if !NO_SPECIALIZED_COLLECTIONS
|
||||
using System.Collections.Specialized;
|
||||
#endif
|
||||
|
||||
#if !NO_SETS
|
||||
|
||||
namespace Nuclex.Support.Collections {
|
||||
|
||||
/// <summary>Set which fires events when items are removed or added to it</summary>
|
||||
/// <typeparam name="TItem">Type of items to manage in the set</typeparam>
|
||||
public class ObservableSet<TItem> :
|
||||
ISet<TItem>,
|
||||
ICollection<TItem>,
|
||||
#if !NO_SPECIALIZED_COLLECTIONS
|
||||
INotifyCollectionChanged,
|
||||
#endif
|
||||
IObservableCollection<TItem> {
|
||||
|
||||
/// <summary>Raised when an item has been added to the collection</summary>
|
||||
public event EventHandler<ItemEventArgs<TItem>> ItemAdded;
|
||||
/// <summary>Raised when an item is removed from the collection</summary>
|
||||
public event EventHandler<ItemEventArgs<TItem>> ItemRemoved;
|
||||
/// <summary>Raised when an item is replaced in the collection</summary>
|
||||
public event EventHandler<ItemReplaceEventArgs<TItem>> ItemReplaced {
|
||||
add { }
|
||||
remove { }
|
||||
}
|
||||
/// <summary>Raised when the collection is about to be cleared</summary>
|
||||
/// <remarks>
|
||||
/// This could be covered by calling ItemRemoved for each item currently
|
||||
/// contained in the collection, but it is often simpler and more efficient
|
||||
/// to process the clearing of the entire collection as a special operation.
|
||||
/// </remarks>
|
||||
public event EventHandler Clearing;
|
||||
/// <summary>Raised when the collection has been cleared</summary>
|
||||
public event EventHandler Cleared;
|
||||
|
||||
#if !NO_SPECIALIZED_COLLECTIONS
|
||||
/// <summary>Called when the collection has changed</summary>
|
||||
public event NotifyCollectionChangedEventHandler CollectionChanged;
|
||||
#endif
|
||||
|
||||
/// <summary>Initializes a new observable set based on a hashed set</summary>
|
||||
public ObservableSet() : this(new HashSet<TItem>()) { }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new observable set forwarding operations to the specified set
|
||||
/// </summary>
|
||||
/// <param name="set">Set operations will be forwarded to</param>
|
||||
public ObservableSet(ISet<TItem> set) {
|
||||
this.set = set;
|
||||
}
|
||||
|
||||
/// <summary>Adds an item to the set</summary>
|
||||
/// <param name="item">Item that will be added to the set</param>
|
||||
/// <returns>
|
||||
/// True if the element was added, false if it was already contained in the set
|
||||
/// </returns>
|
||||
public bool Add(TItem item) {
|
||||
bool wasAdded = this.set.Add(item);
|
||||
if(wasAdded) {
|
||||
OnAdded(item);
|
||||
}
|
||||
return wasAdded;
|
||||
}
|
||||
|
||||
/// <summary>Removes all elements that are contained in the collection</summary>
|
||||
/// <param name="other">Collection whose elements will be removed from this set</param>
|
||||
public void ExceptWith(IEnumerable<TItem> other) {
|
||||
if(other == this) {
|
||||
Clear();
|
||||
return;
|
||||
}
|
||||
|
||||
foreach(TItem item in other) {
|
||||
if(this.set.Remove(item)) {
|
||||
OnRemoved(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Only keeps those elements in this set that are contained in the collection
|
||||
/// </summary>
|
||||
/// <param name="other">Other set this set will be filtered by</param>
|
||||
public void IntersectWith(IEnumerable<TItem> other) {
|
||||
var otherSet = other as ISet<TItem>;
|
||||
if(otherSet == null) {
|
||||
otherSet = new HashSet<TItem>(other);
|
||||
}
|
||||
|
||||
var itemsToRemove = new List<TItem>();
|
||||
foreach(TItem item in this.set) {
|
||||
if(!otherSet.Contains(item)) {
|
||||
itemsToRemove.Add(item);
|
||||
}
|
||||
}
|
||||
|
||||
for(int index = 0; index < itemsToRemove.Count; ++index) {
|
||||
this.set.Remove(itemsToRemove[index]);
|
||||
OnRemoved(itemsToRemove[index]);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the current set is a proper (strict) subset of a collection
|
||||
/// </summary>
|
||||
/// <param name="other">Collection against which the set will be tested</param>
|
||||
/// <returns>True if the set is a proper subset of the specified collection</returns>
|
||||
public bool IsProperSubsetOf(IEnumerable<TItem> other) {
|
||||
return this.set.IsProperSubsetOf(other);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the current set is a proper (strict) superset of a collection
|
||||
/// </summary>
|
||||
/// <param name="other">Collection against which the set will be tested</param>
|
||||
/// <returns>True if the set is a proper superset of the specified collection</returns>
|
||||
public bool IsProperSupersetOf(IEnumerable<TItem> other) {
|
||||
return this.set.IsProperSupersetOf(other);
|
||||
}
|
||||
|
||||
/// <summary>Determines whether the current set is a subset of a collection</summary>
|
||||
/// <param name="other">Collection against which the set will be tested</param>
|
||||
/// <returns>True if the set is a subset of the specified collection</returns>
|
||||
public bool IsSubsetOf(IEnumerable<TItem> other) {
|
||||
return this.set.IsSubsetOf(other);
|
||||
}
|
||||
|
||||
/// <summary>Determines whether the current set is a superset of a collection</summary>
|
||||
/// <param name="other">Collection against which the set will be tested</param>
|
||||
/// <returns>True if the set is a superset of the specified collection</returns>
|
||||
public bool IsSupersetOf(IEnumerable<TItem> other) {
|
||||
return this.set.IsSupersetOf(other);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines if the set shares at least one common element with the collection
|
||||
/// </summary>
|
||||
/// <param name="other">Collection the set will be tested against</param>
|
||||
/// <returns>
|
||||
/// True if the set shares at least one common element with the collection
|
||||
/// </returns>
|
||||
public bool Overlaps(IEnumerable<TItem> other) {
|
||||
return this.set.Overlaps(other);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the set contains the same elements as the specified collection
|
||||
/// </summary>
|
||||
/// <param name="other">Collection the set will be tested against</param>
|
||||
/// <returns>True if the set contains the same elements as the collection</returns>
|
||||
public bool SetEquals(IEnumerable<TItem> other) {
|
||||
return this.set.SetEquals(other);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Modifies the current set so that it contains only elements that are present either
|
||||
/// in the current set or in the specified collection, but not both
|
||||
/// </summary>
|
||||
/// <param name="other">Collection the set will be excepted with</param>
|
||||
public void SymmetricExceptWith(IEnumerable<TItem> other) {
|
||||
foreach(TItem item in other) {
|
||||
if(this.set.Remove(item)) {
|
||||
OnRemoved(item);
|
||||
} else {
|
||||
this.Add(item);
|
||||
OnAdded(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Modifies the current set so that it contains all elements that are present in both
|
||||
/// the current set and in the specified collection
|
||||
/// </summary>
|
||||
/// <param name="other">Collection an union will be built with</param>
|
||||
public void UnionWith(IEnumerable<TItem> other) {
|
||||
foreach(TItem item in other) {
|
||||
if(this.set.Add(item)) {
|
||||
OnAdded(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Removes all items from the set</summary>
|
||||
public void Clear() {
|
||||
OnClearing();
|
||||
this.set.Clear();
|
||||
OnCleared();
|
||||
}
|
||||
|
||||
/// <summary>Determines whether the set contains the specified item</summary>
|
||||
/// <param name="item">Item the set will be tested for</param>
|
||||
/// <returns>True if the set contains the specified item</returns>
|
||||
public bool Contains(TItem item) {
|
||||
return this.set.Contains(item);
|
||||
}
|
||||
|
||||
/// <summary>Copies the contents of the set into an array</summary>
|
||||
/// <param name="array">Array the set's contents will be copied to</param>
|
||||
/// <param name="arrayIndex">
|
||||
/// Index in the array the first copied element will be written to
|
||||
/// </param>
|
||||
public void CopyTo(TItem[] array, int arrayIndex) {
|
||||
this.set.CopyTo(array, arrayIndex);
|
||||
}
|
||||
|
||||
/// <summary>Counts the number of items contained in the set</summary>
|
||||
public int Count {
|
||||
get { return this.set.Count; }
|
||||
}
|
||||
|
||||
/// <summary>Determines whether the set is readonly</summary>
|
||||
public bool IsReadOnly {
|
||||
get { return this.set.IsReadOnly; }
|
||||
}
|
||||
|
||||
/// <summary>Removes an item from the set</summary>
|
||||
/// <param name="item">Item that will be removed from the set</param>
|
||||
/// <returns>
|
||||
/// True if the item was contained in the set and is now removed
|
||||
/// </returns>
|
||||
public bool Remove(TItem item) {
|
||||
bool wasRemoved = this.set.Remove(item);
|
||||
if(wasRemoved) {
|
||||
OnRemoved(item);
|
||||
}
|
||||
return wasRemoved;
|
||||
}
|
||||
|
||||
/// <summary>Creates an enumerator for the set's contents</summary>
|
||||
/// <returns>A new enumerator for the sets contents</returns>
|
||||
public IEnumerator<TItem> GetEnumerator() {
|
||||
return this.set.GetEnumerator();
|
||||
}
|
||||
|
||||
/// <summary>Fires the 'ItemAdded' event</summary>
|
||||
/// <param name="item">Item that has been added to the collection</param>
|
||||
protected virtual void OnAdded(TItem item) {
|
||||
if(ItemAdded != null) {
|
||||
ItemAdded(this, new ItemEventArgs<TItem>(item));
|
||||
}
|
||||
#if !NO_SPECIALIZED_COLLECTIONS
|
||||
if(CollectionChanged != null) {
|
||||
CollectionChanged(
|
||||
this,
|
||||
new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item)
|
||||
);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>Fires the 'ItemRemoved' event</summary>
|
||||
/// <param name="item">Item that has been removed from the collection</param>
|
||||
protected virtual void OnRemoved(TItem item) {
|
||||
if(ItemRemoved != null) {
|
||||
ItemRemoved(this, new ItemEventArgs<TItem>(item));
|
||||
}
|
||||
#if !NO_SPECIALIZED_COLLECTIONS
|
||||
if(CollectionChanged != null) {
|
||||
CollectionChanged(
|
||||
this,
|
||||
new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, item)
|
||||
);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>Fires the 'Clearing' event</summary>
|
||||
protected virtual void OnClearing() {
|
||||
if(Clearing != null) {
|
||||
Clearing(this, EventArgs.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Fires the 'Cleared' event</summary>
|
||||
protected virtual void OnCleared() {
|
||||
if(Cleared != null) {
|
||||
Cleared(this, EventArgs.Empty);
|
||||
}
|
||||
#if !NO_SPECIALIZED_COLLECTIONS
|
||||
if(CollectionChanged != null) {
|
||||
CollectionChanged(this, Constants.NotifyCollectionResetEventArgs);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#region ICollection<T> implementation
|
||||
|
||||
/// <summary>Adds an item to the set</summary>
|
||||
/// <param name="item">Item that will be added to the set</param>
|
||||
void ICollection<TItem>.Add(TItem item) {
|
||||
this.set.Add(item);
|
||||
}
|
||||
|
||||
#endregion // ICollection<T> implementation
|
||||
|
||||
#region IEnumerable implementation
|
||||
|
||||
/// <summary>Creates an enumerator for the set's contents</summary>
|
||||
/// <returns>A new enumerator for the sets contents</returns>
|
||||
IEnumerator IEnumerable.GetEnumerator() {
|
||||
return this.set.GetEnumerator();
|
||||
}
|
||||
|
||||
#endregion // IEnumerable implementation
|
||||
|
||||
/// <summary>The set being wrapped</summary>
|
||||
private ISet<TItem> set;
|
||||
|
||||
}
|
||||
|
||||
} // namespace Nuclex.Support.Collections
|
||||
|
||||
#endif // !NO_SETS
|
||||
#region Apache License 2.0
|
||||
/*
|
||||
Nuclex .NET Framework
|
||||
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
#endregion // Apache License 2.0
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections;
|
||||
|
||||
#if !NO_SPECIALIZED_COLLECTIONS
|
||||
using System.Collections.Specialized;
|
||||
#endif
|
||||
|
||||
#if !NO_SETS
|
||||
|
||||
namespace Nuclex.Support.Collections {
|
||||
|
||||
/// <summary>Set which fires events when items are removed or added to it</summary>
|
||||
/// <typeparam name="TItem">Type of items to manage in the set</typeparam>
|
||||
public class ObservableSet<TItem> :
|
||||
ISet<TItem>,
|
||||
ICollection<TItem>,
|
||||
#if !NO_SPECIALIZED_COLLECTIONS
|
||||
INotifyCollectionChanged,
|
||||
#endif
|
||||
IObservableCollection<TItem> {
|
||||
|
||||
/// <summary>Raised when an item has been added to the collection</summary>
|
||||
public event EventHandler<ItemEventArgs<TItem>> ItemAdded;
|
||||
/// <summary>Raised when an item is removed from the collection</summary>
|
||||
public event EventHandler<ItemEventArgs<TItem>> ItemRemoved;
|
||||
/// <summary>Raised when an item is replaced in the collection</summary>
|
||||
public event EventHandler<ItemReplaceEventArgs<TItem>> ItemReplaced {
|
||||
add { }
|
||||
remove { }
|
||||
}
|
||||
/// <summary>Raised when the collection is about to be cleared</summary>
|
||||
/// <remarks>
|
||||
/// This could be covered by calling ItemRemoved for each item currently
|
||||
/// contained in the collection, but it is often simpler and more efficient
|
||||
/// to process the clearing of the entire collection as a special operation.
|
||||
/// </remarks>
|
||||
public event EventHandler Clearing;
|
||||
/// <summary>Raised when the collection has been cleared</summary>
|
||||
public event EventHandler Cleared;
|
||||
|
||||
#if !NO_SPECIALIZED_COLLECTIONS
|
||||
/// <summary>Called when the collection has changed</summary>
|
||||
public event NotifyCollectionChangedEventHandler CollectionChanged;
|
||||
#endif
|
||||
|
||||
/// <summary>Initializes a new observable set based on a hashed set</summary>
|
||||
public ObservableSet() : this(new HashSet<TItem>()) { }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new observable set forwarding operations to the specified set
|
||||
/// </summary>
|
||||
/// <param name="set">Set operations will be forwarded to</param>
|
||||
public ObservableSet(ISet<TItem> set) {
|
||||
this.set = set;
|
||||
}
|
||||
|
||||
/// <summary>Adds an item to the set</summary>
|
||||
/// <param name="item">Item that will be added to the set</param>
|
||||
/// <returns>
|
||||
/// True if the element was added, false if it was already contained in the set
|
||||
/// </returns>
|
||||
public bool Add(TItem item) {
|
||||
bool wasAdded = this.set.Add(item);
|
||||
if(wasAdded) {
|
||||
OnAdded(item);
|
||||
}
|
||||
return wasAdded;
|
||||
}
|
||||
|
||||
/// <summary>Removes all elements that are contained in the collection</summary>
|
||||
/// <param name="other">Collection whose elements will be removed from this set</param>
|
||||
public void ExceptWith(IEnumerable<TItem> other) {
|
||||
if(other == this) {
|
||||
Clear();
|
||||
return;
|
||||
}
|
||||
|
||||
foreach(TItem item in other) {
|
||||
if(this.set.Remove(item)) {
|
||||
OnRemoved(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Only keeps those elements in this set that are contained in the collection
|
||||
/// </summary>
|
||||
/// <param name="other">Other set this set will be filtered by</param>
|
||||
public void IntersectWith(IEnumerable<TItem> other) {
|
||||
var otherSet = other as ISet<TItem>;
|
||||
if(otherSet == null) {
|
||||
otherSet = new HashSet<TItem>(other);
|
||||
}
|
||||
|
||||
var itemsToRemove = new List<TItem>();
|
||||
foreach(TItem item in this.set) {
|
||||
if(!otherSet.Contains(item)) {
|
||||
itemsToRemove.Add(item);
|
||||
}
|
||||
}
|
||||
|
||||
for(int index = 0; index < itemsToRemove.Count; ++index) {
|
||||
this.set.Remove(itemsToRemove[index]);
|
||||
OnRemoved(itemsToRemove[index]);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the current set is a proper (strict) subset of a collection
|
||||
/// </summary>
|
||||
/// <param name="other">Collection against which the set will be tested</param>
|
||||
/// <returns>True if the set is a proper subset of the specified collection</returns>
|
||||
public bool IsProperSubsetOf(IEnumerable<TItem> other) {
|
||||
return this.set.IsProperSubsetOf(other);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the current set is a proper (strict) superset of a collection
|
||||
/// </summary>
|
||||
/// <param name="other">Collection against which the set will be tested</param>
|
||||
/// <returns>True if the set is a proper superset of the specified collection</returns>
|
||||
public bool IsProperSupersetOf(IEnumerable<TItem> other) {
|
||||
return this.set.IsProperSupersetOf(other);
|
||||
}
|
||||
|
||||
/// <summary>Determines whether the current set is a subset of a collection</summary>
|
||||
/// <param name="other">Collection against which the set will be tested</param>
|
||||
/// <returns>True if the set is a subset of the specified collection</returns>
|
||||
public bool IsSubsetOf(IEnumerable<TItem> other) {
|
||||
return this.set.IsSubsetOf(other);
|
||||
}
|
||||
|
||||
/// <summary>Determines whether the current set is a superset of a collection</summary>
|
||||
/// <param name="other">Collection against which the set will be tested</param>
|
||||
/// <returns>True if the set is a superset of the specified collection</returns>
|
||||
public bool IsSupersetOf(IEnumerable<TItem> other) {
|
||||
return this.set.IsSupersetOf(other);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines if the set shares at least one common element with the collection
|
||||
/// </summary>
|
||||
/// <param name="other">Collection the set will be tested against</param>
|
||||
/// <returns>
|
||||
/// True if the set shares at least one common element with the collection
|
||||
/// </returns>
|
||||
public bool Overlaps(IEnumerable<TItem> other) {
|
||||
return this.set.Overlaps(other);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the set contains the same elements as the specified collection
|
||||
/// </summary>
|
||||
/// <param name="other">Collection the set will be tested against</param>
|
||||
/// <returns>True if the set contains the same elements as the collection</returns>
|
||||
public bool SetEquals(IEnumerable<TItem> other) {
|
||||
return this.set.SetEquals(other);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Modifies the current set so that it contains only elements that are present either
|
||||
/// in the current set or in the specified collection, but not both
|
||||
/// </summary>
|
||||
/// <param name="other">Collection the set will be excepted with</param>
|
||||
public void SymmetricExceptWith(IEnumerable<TItem> other) {
|
||||
foreach(TItem item in other) {
|
||||
if(this.set.Remove(item)) {
|
||||
OnRemoved(item);
|
||||
} else {
|
||||
this.Add(item);
|
||||
OnAdded(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Modifies the current set so that it contains all elements that are present in both
|
||||
/// the current set and in the specified collection
|
||||
/// </summary>
|
||||
/// <param name="other">Collection an union will be built with</param>
|
||||
public void UnionWith(IEnumerable<TItem> other) {
|
||||
foreach(TItem item in other) {
|
||||
if(this.set.Add(item)) {
|
||||
OnAdded(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Removes all items from the set</summary>
|
||||
public void Clear() {
|
||||
OnClearing();
|
||||
this.set.Clear();
|
||||
OnCleared();
|
||||
}
|
||||
|
||||
/// <summary>Determines whether the set contains the specified item</summary>
|
||||
/// <param name="item">Item the set will be tested for</param>
|
||||
/// <returns>True if the set contains the specified item</returns>
|
||||
public bool Contains(TItem item) {
|
||||
return this.set.Contains(item);
|
||||
}
|
||||
|
||||
/// <summary>Copies the contents of the set into an array</summary>
|
||||
/// <param name="array">Array the set's contents will be copied to</param>
|
||||
/// <param name="arrayIndex">
|
||||
/// Index in the array the first copied element will be written to
|
||||
/// </param>
|
||||
public void CopyTo(TItem[] array, int arrayIndex) {
|
||||
this.set.CopyTo(array, arrayIndex);
|
||||
}
|
||||
|
||||
/// <summary>Counts the number of items contained in the set</summary>
|
||||
public int Count {
|
||||
get { return this.set.Count; }
|
||||
}
|
||||
|
||||
/// <summary>Determines whether the set is readonly</summary>
|
||||
public bool IsReadOnly {
|
||||
get { return this.set.IsReadOnly; }
|
||||
}
|
||||
|
||||
/// <summary>Removes an item from the set</summary>
|
||||
/// <param name="item">Item that will be removed from the set</param>
|
||||
/// <returns>
|
||||
/// True if the item was contained in the set and is now removed
|
||||
/// </returns>
|
||||
public bool Remove(TItem item) {
|
||||
bool wasRemoved = this.set.Remove(item);
|
||||
if(wasRemoved) {
|
||||
OnRemoved(item);
|
||||
}
|
||||
return wasRemoved;
|
||||
}
|
||||
|
||||
/// <summary>Creates an enumerator for the set's contents</summary>
|
||||
/// <returns>A new enumerator for the sets contents</returns>
|
||||
public IEnumerator<TItem> GetEnumerator() {
|
||||
return this.set.GetEnumerator();
|
||||
}
|
||||
|
||||
/// <summary>Fires the 'ItemAdded' event</summary>
|
||||
/// <param name="item">Item that has been added to the collection</param>
|
||||
protected virtual void OnAdded(TItem item) {
|
||||
if(ItemAdded != null) {
|
||||
ItemAdded(this, new ItemEventArgs<TItem>(item));
|
||||
}
|
||||
#if !NO_SPECIALIZED_COLLECTIONS
|
||||
if(CollectionChanged != null) {
|
||||
CollectionChanged(
|
||||
this,
|
||||
new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item)
|
||||
);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>Fires the 'ItemRemoved' event</summary>
|
||||
/// <param name="item">Item that has been removed from the collection</param>
|
||||
protected virtual void OnRemoved(TItem item) {
|
||||
if(ItemRemoved != null) {
|
||||
ItemRemoved(this, new ItemEventArgs<TItem>(item));
|
||||
}
|
||||
#if !NO_SPECIALIZED_COLLECTIONS
|
||||
if(CollectionChanged != null) {
|
||||
CollectionChanged(
|
||||
this,
|
||||
new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, item)
|
||||
);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>Fires the 'Clearing' event</summary>
|
||||
protected virtual void OnClearing() {
|
||||
if(Clearing != null) {
|
||||
Clearing(this, EventArgs.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Fires the 'Cleared' event</summary>
|
||||
protected virtual void OnCleared() {
|
||||
if(Cleared != null) {
|
||||
Cleared(this, EventArgs.Empty);
|
||||
}
|
||||
#if !NO_SPECIALIZED_COLLECTIONS
|
||||
if(CollectionChanged != null) {
|
||||
CollectionChanged(this, Constants.NotifyCollectionResetEventArgs);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#region ICollection<T> implementation
|
||||
|
||||
/// <summary>Adds an item to the set</summary>
|
||||
/// <param name="item">Item that will be added to the set</param>
|
||||
void ICollection<TItem>.Add(TItem item) {
|
||||
this.set.Add(item);
|
||||
}
|
||||
|
||||
#endregion // ICollection<T> implementation
|
||||
|
||||
#region IEnumerable implementation
|
||||
|
||||
/// <summary>Creates an enumerator for the set's contents</summary>
|
||||
/// <returns>A new enumerator for the sets contents</returns>
|
||||
IEnumerator IEnumerable.GetEnumerator() {
|
||||
return this.set.GetEnumerator();
|
||||
}
|
||||
|
||||
#endregion // IEnumerable implementation
|
||||
|
||||
/// <summary>The set being wrapped</summary>
|
||||
private ISet<TItem> set;
|
||||
|
||||
}
|
||||
|
||||
} // namespace Nuclex.Support.Collections
|
||||
|
||||
#endif // !NO_SETS
|
||||
|
|
|
|||
|
|
@ -1,151 +1,150 @@
|
|||
#region CPL License
|
||||
/*
|
||||
Nuclex Framework
|
||||
Copyright (C) 2002-2017 Nuclex Development Labs
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the IBM Common Public License as
|
||||
published by the IBM Corporation; either version 1.0 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
IBM Common Public License for more details.
|
||||
|
||||
You should have received a copy of the IBM Common Public
|
||||
License along with this library
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
#if UNITTEST
|
||||
|
||||
using NUnit.Framework;
|
||||
|
||||
namespace Nuclex.Support.Collections {
|
||||
|
||||
/// <summary>Unit Test for the priority queue class</summary>
|
||||
[TestFixture]
|
||||
internal class PairPriorityQueueTest {
|
||||
|
||||
/// <summary>Tests to ensure the count property is properly updated</summary>
|
||||
[Test]
|
||||
public void TestCount() {
|
||||
PairPriorityQueue<float, string> testQueue = new PairPriorityQueue<float, string>();
|
||||
|
||||
Assert.AreEqual(0, testQueue.Count);
|
||||
testQueue.Enqueue(12.34f, "a");
|
||||
Assert.AreEqual(1, testQueue.Count);
|
||||
testQueue.Enqueue(56.78f, "b");
|
||||
Assert.AreEqual(2, testQueue.Count);
|
||||
testQueue.Dequeue();
|
||||
Assert.AreEqual(1, testQueue.Count);
|
||||
testQueue.Enqueue(9.0f, "c");
|
||||
Assert.AreEqual(2, testQueue.Count);
|
||||
testQueue.Clear();
|
||||
Assert.AreEqual(0, testQueue.Count);
|
||||
}
|
||||
|
||||
/// <summary>Tests to ensure that the priority collection actually sorts items</summary>
|
||||
[Test]
|
||||
public void TestOrdering() {
|
||||
PairPriorityQueue<float, string> testQueue = new PairPriorityQueue<float, string>();
|
||||
|
||||
testQueue.Enqueue(1.0f, "a");
|
||||
testQueue.Enqueue(9.0f, "i");
|
||||
testQueue.Enqueue(2.0f, "b");
|
||||
testQueue.Enqueue(8.0f, "h");
|
||||
testQueue.Enqueue(3.0f, "c");
|
||||
testQueue.Enqueue(7.0f, "g");
|
||||
testQueue.Enqueue(4.0f, "d");
|
||||
testQueue.Enqueue(6.0f, "f");
|
||||
testQueue.Enqueue(5.0f, "e");
|
||||
|
||||
Assert.AreEqual("i", testQueue.Dequeue().Item);
|
||||
Assert.AreEqual("h", testQueue.Dequeue().Item);
|
||||
Assert.AreEqual("g", testQueue.Dequeue().Item);
|
||||
Assert.AreEqual("f", testQueue.Dequeue().Item);
|
||||
Assert.AreEqual("e", testQueue.Dequeue().Item);
|
||||
Assert.AreEqual("d", testQueue.Dequeue().Item);
|
||||
Assert.AreEqual("c", testQueue.Dequeue().Item);
|
||||
Assert.AreEqual("b", testQueue.Dequeue().Item);
|
||||
Assert.AreEqual("a", testQueue.Dequeue().Item);
|
||||
}
|
||||
|
||||
/// <summary>Tests to ensure that the priority collection's Peek() method works</summary>
|
||||
[Test]
|
||||
public void TestPeek() {
|
||||
PairPriorityQueue<float, string> testQueue = new PairPriorityQueue<float, string>();
|
||||
|
||||
testQueue.Enqueue(1.0f, "a");
|
||||
testQueue.Enqueue(2.0f, "b");
|
||||
testQueue.Enqueue(0.0f, "c");
|
||||
|
||||
Assert.AreEqual("b", testQueue.Peek().Item);
|
||||
}
|
||||
|
||||
/// <summary>Tests whether the priority collection can copy itself into an array</summary>
|
||||
[Test]
|
||||
public void TestCopyTo() {
|
||||
PairPriorityQueue<float, string> testQueue = new PairPriorityQueue<float, string>();
|
||||
|
||||
testQueue.Enqueue(1.0f, "a");
|
||||
testQueue.Enqueue(9.0f, "i");
|
||||
testQueue.Enqueue(2.0f, "b");
|
||||
testQueue.Enqueue(8.0f, "h");
|
||||
testQueue.Enqueue(3.0f, "c");
|
||||
testQueue.Enqueue(7.0f, "g");
|
||||
testQueue.Enqueue(4.0f, "d");
|
||||
testQueue.Enqueue(6.0f, "f");
|
||||
testQueue.Enqueue(5.0f, "e");
|
||||
|
||||
PriorityItemPair<float, string>[] itemArray = new PriorityItemPair<float, string>[9];
|
||||
testQueue.CopyTo(itemArray, 0);
|
||||
|
||||
CollectionAssert.AreEquivalent(testQueue, itemArray);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests whether the priority collection provides a synchronization root
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestSyncRoot() {
|
||||
PairPriorityQueue<int, int> testQueue = new PairPriorityQueue<int, int>();
|
||||
|
||||
// If IsSynchronized returns true, SyncRoot is allowed to be null
|
||||
if(!testQueue.IsSynchronized) {
|
||||
lock(testQueue.SyncRoot) {
|
||||
testQueue.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests whether the priority collection provides a working type-safe enumerator
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestEnumerator() {
|
||||
PairPriorityQueue<float, string> testQueue = new PairPriorityQueue<float, string>();
|
||||
|
||||
testQueue.Enqueue(1.0f, "a");
|
||||
testQueue.Enqueue(2.0f, "b");
|
||||
testQueue.Enqueue(0.0f, "c");
|
||||
|
||||
List<PriorityItemPair<float, string>> testList =
|
||||
new List<PriorityItemPair<float,string>>();
|
||||
|
||||
foreach(PriorityItemPair<float, string> entry in testQueue) {
|
||||
testList.Add(entry);
|
||||
}
|
||||
|
||||
CollectionAssert.AreEquivalent(testQueue, testList);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} // namespace Nuclex.Support.Collections
|
||||
|
||||
#endif // UNITTEST
|
||||
#region Apache License 2.0
|
||||
/*
|
||||
Nuclex .NET Framework
|
||||
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
#endregion // Apache License 2.0
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
#if UNITTEST
|
||||
|
||||
using NUnit.Framework;
|
||||
|
||||
namespace Nuclex.Support.Collections {
|
||||
|
||||
/// <summary>Unit Test for the priority queue class</summary>
|
||||
[TestFixture]
|
||||
internal class PairPriorityQueueTest {
|
||||
|
||||
/// <summary>Tests to ensure the count property is properly updated</summary>
|
||||
[Test]
|
||||
public void TestCount() {
|
||||
PairPriorityQueue<float, string> testQueue = new PairPriorityQueue<float, string>();
|
||||
|
||||
Assert.AreEqual(0, testQueue.Count);
|
||||
testQueue.Enqueue(12.34f, "a");
|
||||
Assert.AreEqual(1, testQueue.Count);
|
||||
testQueue.Enqueue(56.78f, "b");
|
||||
Assert.AreEqual(2, testQueue.Count);
|
||||
testQueue.Dequeue();
|
||||
Assert.AreEqual(1, testQueue.Count);
|
||||
testQueue.Enqueue(9.0f, "c");
|
||||
Assert.AreEqual(2, testQueue.Count);
|
||||
testQueue.Clear();
|
||||
Assert.AreEqual(0, testQueue.Count);
|
||||
}
|
||||
|
||||
/// <summary>Tests to ensure that the priority collection actually sorts items</summary>
|
||||
[Test]
|
||||
public void TestOrdering() {
|
||||
PairPriorityQueue<float, string> testQueue = new PairPriorityQueue<float, string>();
|
||||
|
||||
testQueue.Enqueue(1.0f, "a");
|
||||
testQueue.Enqueue(9.0f, "i");
|
||||
testQueue.Enqueue(2.0f, "b");
|
||||
testQueue.Enqueue(8.0f, "h");
|
||||
testQueue.Enqueue(3.0f, "c");
|
||||
testQueue.Enqueue(7.0f, "g");
|
||||
testQueue.Enqueue(4.0f, "d");
|
||||
testQueue.Enqueue(6.0f, "f");
|
||||
testQueue.Enqueue(5.0f, "e");
|
||||
|
||||
Assert.AreEqual("i", testQueue.Dequeue().Item);
|
||||
Assert.AreEqual("h", testQueue.Dequeue().Item);
|
||||
Assert.AreEqual("g", testQueue.Dequeue().Item);
|
||||
Assert.AreEqual("f", testQueue.Dequeue().Item);
|
||||
Assert.AreEqual("e", testQueue.Dequeue().Item);
|
||||
Assert.AreEqual("d", testQueue.Dequeue().Item);
|
||||
Assert.AreEqual("c", testQueue.Dequeue().Item);
|
||||
Assert.AreEqual("b", testQueue.Dequeue().Item);
|
||||
Assert.AreEqual("a", testQueue.Dequeue().Item);
|
||||
}
|
||||
|
||||
/// <summary>Tests to ensure that the priority collection's Peek() method works</summary>
|
||||
[Test]
|
||||
public void TestPeek() {
|
||||
PairPriorityQueue<float, string> testQueue = new PairPriorityQueue<float, string>();
|
||||
|
||||
testQueue.Enqueue(1.0f, "a");
|
||||
testQueue.Enqueue(2.0f, "b");
|
||||
testQueue.Enqueue(0.0f, "c");
|
||||
|
||||
Assert.AreEqual("b", testQueue.Peek().Item);
|
||||
}
|
||||
|
||||
/// <summary>Tests whether the priority collection can copy itself into an array</summary>
|
||||
[Test]
|
||||
public void TestCopyTo() {
|
||||
PairPriorityQueue<float, string> testQueue = new PairPriorityQueue<float, string>();
|
||||
|
||||
testQueue.Enqueue(1.0f, "a");
|
||||
testQueue.Enqueue(9.0f, "i");
|
||||
testQueue.Enqueue(2.0f, "b");
|
||||
testQueue.Enqueue(8.0f, "h");
|
||||
testQueue.Enqueue(3.0f, "c");
|
||||
testQueue.Enqueue(7.0f, "g");
|
||||
testQueue.Enqueue(4.0f, "d");
|
||||
testQueue.Enqueue(6.0f, "f");
|
||||
testQueue.Enqueue(5.0f, "e");
|
||||
|
||||
PriorityItemPair<float, string>[] itemArray = new PriorityItemPair<float, string>[9];
|
||||
testQueue.CopyTo(itemArray, 0);
|
||||
|
||||
CollectionAssert.AreEquivalent(testQueue, itemArray);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests whether the priority collection provides a synchronization root
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestSyncRoot() {
|
||||
PairPriorityQueue<int, int> testQueue = new PairPriorityQueue<int, int>();
|
||||
|
||||
// If IsSynchronized returns true, SyncRoot is allowed to be null
|
||||
if(!testQueue.IsSynchronized) {
|
||||
lock(testQueue.SyncRoot) {
|
||||
testQueue.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests whether the priority collection provides a working type-safe enumerator
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestEnumerator() {
|
||||
PairPriorityQueue<float, string> testQueue = new PairPriorityQueue<float, string>();
|
||||
|
||||
testQueue.Enqueue(1.0f, "a");
|
||||
testQueue.Enqueue(2.0f, "b");
|
||||
testQueue.Enqueue(0.0f, "c");
|
||||
|
||||
List<PriorityItemPair<float, string>> testList =
|
||||
new List<PriorityItemPair<float,string>>();
|
||||
|
||||
foreach(PriorityItemPair<float, string> entry in testQueue) {
|
||||
testList.Add(entry);
|
||||
}
|
||||
|
||||
CollectionAssert.AreEquivalent(testQueue, testList);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} // namespace Nuclex.Support.Collections
|
||||
|
||||
#endif // UNITTEST
|
||||
|
|
|
|||
|
|
@ -1,144 +1,143 @@
|
|||
#region CPL License
|
||||
/*
|
||||
Nuclex Framework
|
||||
Copyright (C) 2002-2017 Nuclex Development Labs
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the IBM Common Public License as
|
||||
published by the IBM Corporation; either version 1.0 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
IBM Common Public License for more details.
|
||||
|
||||
You should have received a copy of the IBM Common Public
|
||||
License along with this library
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections;
|
||||
|
||||
namespace Nuclex.Support.Collections {
|
||||
|
||||
/// <summary>Queue that dequeues items in order of their priority</summary>
|
||||
/// <remarks>
|
||||
/// This variant of the priority queue uses an external priority value. If the
|
||||
/// priority data type implements the IComparable interface, the user does not
|
||||
/// even
|
||||
/// </remarks>
|
||||
public class PairPriorityQueue<TPriority, TItem> :
|
||||
ICollection, IEnumerable<PriorityItemPair<TPriority, TItem>> {
|
||||
|
||||
#region class PairComparer
|
||||
|
||||
/// <summary>Compares two priority queue entries based on their priority</summary>
|
||||
private class PairComparer : IComparer<PriorityItemPair<TPriority, TItem>> {
|
||||
|
||||
/// <summary>Initializes a new entry comparer</summary>
|
||||
/// <param name="priorityComparer">Comparer used to compare entry priorities</param>
|
||||
public PairComparer(IComparer<TPriority> priorityComparer) {
|
||||
this.priorityComparer = priorityComparer;
|
||||
}
|
||||
|
||||
/// <summary>Compares the left entry to the right entry</summary>
|
||||
/// <param name="left">Entry on the left side</param>
|
||||
/// <param name="right">Entry on the right side</param>
|
||||
/// <returns>The relationship of the two entries</returns>
|
||||
public int Compare(
|
||||
PriorityItemPair<TPriority, TItem> left,
|
||||
PriorityItemPair<TPriority, TItem> right
|
||||
) {
|
||||
return this.priorityComparer.Compare(left.Priority, right.Priority);
|
||||
}
|
||||
|
||||
/// <summary>Comparer used to compare the priorities of the entries</summary>
|
||||
private IComparer<TPriority> priorityComparer;
|
||||
|
||||
}
|
||||
|
||||
#endregion // class EntryComparer
|
||||
|
||||
/// <summary>Initializes a new non-intrusive priority queue</summary>
|
||||
public PairPriorityQueue() : this(Comparer<TPriority>.Default) { }
|
||||
|
||||
/// <summary>Initializes a new non-intrusive priority queue</summary>
|
||||
/// <param name="priorityComparer">Comparer used to compare the item priorities</param>
|
||||
public PairPriorityQueue(IComparer<TPriority> priorityComparer) {
|
||||
this.internalQueue = new PriorityQueue<PriorityItemPair<TPriority, TItem>>(
|
||||
new PairComparer(priorityComparer)
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>Returns the topmost item in the queue without dequeueing it</summary>
|
||||
/// <returns>The topmost item in the queue</returns>
|
||||
public PriorityItemPair<TPriority, TItem> Peek() {
|
||||
return this.internalQueue.Peek();
|
||||
}
|
||||
|
||||
/// <summary>Takes the item with the highest priority off from the queue</summary>
|
||||
/// <returns>The item with the highest priority in the list</returns>
|
||||
public PriorityItemPair<TPriority, TItem> Dequeue() {
|
||||
return this.internalQueue.Dequeue();
|
||||
}
|
||||
|
||||
/// <summary>Puts an item into the priority queue</summary>
|
||||
/// <param name="priority">Priority of the item to be queued</param>
|
||||
/// <param name="item">Item to be queued</param>
|
||||
public void Enqueue(TPriority priority, TItem item) {
|
||||
this.internalQueue.Enqueue(
|
||||
new PriorityItemPair<TPriority, TItem>(priority, item)
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>Removes all items from the priority queue</summary>
|
||||
public void Clear() {
|
||||
this.internalQueue.Clear();
|
||||
}
|
||||
|
||||
/// <summary>Total number of items in the priority queue</summary>
|
||||
public int Count {
|
||||
get { return this.internalQueue.Count; }
|
||||
}
|
||||
|
||||
/// <summary>Copies the contents of the priority queue into an array</summary>
|
||||
/// <param name="array">Array to copy the priority queue into</param>
|
||||
/// <param name="index">Starting index for the destination array</param>
|
||||
public void CopyTo(Array array, int index) {
|
||||
this.internalQueue.CopyTo(array, index);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Obtains an object that can be used to synchronize accesses to the priority queue
|
||||
/// from different threads
|
||||
/// </summary>
|
||||
public object SyncRoot {
|
||||
get { return this.internalQueue.SyncRoot; }
|
||||
}
|
||||
|
||||
/// <summary>Whether operations performed on this priority queue are thread safe</summary>
|
||||
public bool IsSynchronized {
|
||||
get { return this.internalQueue.IsSynchronized; }
|
||||
}
|
||||
|
||||
/// <summary>Returns a typesafe enumerator for the priority queue</summary>
|
||||
/// <returns>A new enumerator for the priority queue</returns>
|
||||
public IEnumerator<PriorityItemPair<TPriority, TItem>> GetEnumerator() {
|
||||
return this.internalQueue.GetEnumerator();
|
||||
}
|
||||
|
||||
/// <summary>Returns an enumerator for the priority queue</summary>
|
||||
/// <returns>A new enumerator for the priority queue</returns>
|
||||
IEnumerator IEnumerable.GetEnumerator() {
|
||||
return this.internalQueue.GetEnumerator();
|
||||
}
|
||||
|
||||
/// <summary>Intrusive priority queue being wrapped by this class</summary>
|
||||
private PriorityQueue<PriorityItemPair<TPriority, TItem>> internalQueue;
|
||||
|
||||
}
|
||||
|
||||
} // namespace Nuclex.Support.Collections
|
||||
#region Apache License 2.0
|
||||
/*
|
||||
Nuclex .NET Framework
|
||||
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
#endregion // Apache License 2.0
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections;
|
||||
|
||||
namespace Nuclex.Support.Collections {
|
||||
|
||||
/// <summary>Queue that dequeues items in order of their priority</summary>
|
||||
/// <remarks>
|
||||
/// This variant of the priority queue uses an external priority value. If the
|
||||
/// priority data type implements the IComparable interface, the user does not
|
||||
/// even
|
||||
/// </remarks>
|
||||
public class PairPriorityQueue<TPriority, TItem> :
|
||||
ICollection, IEnumerable<PriorityItemPair<TPriority, TItem>> {
|
||||
|
||||
#region class PairComparer
|
||||
|
||||
/// <summary>Compares two priority queue entries based on their priority</summary>
|
||||
private class PairComparer : IComparer<PriorityItemPair<TPriority, TItem>> {
|
||||
|
||||
/// <summary>Initializes a new entry comparer</summary>
|
||||
/// <param name="priorityComparer">Comparer used to compare entry priorities</param>
|
||||
public PairComparer(IComparer<TPriority> priorityComparer) {
|
||||
this.priorityComparer = priorityComparer;
|
||||
}
|
||||
|
||||
/// <summary>Compares the left entry to the right entry</summary>
|
||||
/// <param name="left">Entry on the left side</param>
|
||||
/// <param name="right">Entry on the right side</param>
|
||||
/// <returns>The relationship of the two entries</returns>
|
||||
public int Compare(
|
||||
PriorityItemPair<TPriority, TItem> left,
|
||||
PriorityItemPair<TPriority, TItem> right
|
||||
) {
|
||||
return this.priorityComparer.Compare(left.Priority, right.Priority);
|
||||
}
|
||||
|
||||
/// <summary>Comparer used to compare the priorities of the entries</summary>
|
||||
private IComparer<TPriority> priorityComparer;
|
||||
|
||||
}
|
||||
|
||||
#endregion // class EntryComparer
|
||||
|
||||
/// <summary>Initializes a new non-intrusive priority queue</summary>
|
||||
public PairPriorityQueue() : this(Comparer<TPriority>.Default) { }
|
||||
|
||||
/// <summary>Initializes a new non-intrusive priority queue</summary>
|
||||
/// <param name="priorityComparer">Comparer used to compare the item priorities</param>
|
||||
public PairPriorityQueue(IComparer<TPriority> priorityComparer) {
|
||||
this.internalQueue = new PriorityQueue<PriorityItemPair<TPriority, TItem>>(
|
||||
new PairComparer(priorityComparer)
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>Returns the topmost item in the queue without dequeueing it</summary>
|
||||
/// <returns>The topmost item in the queue</returns>
|
||||
public PriorityItemPair<TPriority, TItem> Peek() {
|
||||
return this.internalQueue.Peek();
|
||||
}
|
||||
|
||||
/// <summary>Takes the item with the highest priority off from the queue</summary>
|
||||
/// <returns>The item with the highest priority in the list</returns>
|
||||
public PriorityItemPair<TPriority, TItem> Dequeue() {
|
||||
return this.internalQueue.Dequeue();
|
||||
}
|
||||
|
||||
/// <summary>Puts an item into the priority queue</summary>
|
||||
/// <param name="priority">Priority of the item to be queued</param>
|
||||
/// <param name="item">Item to be queued</param>
|
||||
public void Enqueue(TPriority priority, TItem item) {
|
||||
this.internalQueue.Enqueue(
|
||||
new PriorityItemPair<TPriority, TItem>(priority, item)
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>Removes all items from the priority queue</summary>
|
||||
public void Clear() {
|
||||
this.internalQueue.Clear();
|
||||
}
|
||||
|
||||
/// <summary>Total number of items in the priority queue</summary>
|
||||
public int Count {
|
||||
get { return this.internalQueue.Count; }
|
||||
}
|
||||
|
||||
/// <summary>Copies the contents of the priority queue into an array</summary>
|
||||
/// <param name="array">Array to copy the priority queue into</param>
|
||||
/// <param name="index">Starting index for the destination array</param>
|
||||
public void CopyTo(Array array, int index) {
|
||||
this.internalQueue.CopyTo(array, index);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Obtains an object that can be used to synchronize accesses to the priority queue
|
||||
/// from different threads
|
||||
/// </summary>
|
||||
public object SyncRoot {
|
||||
get { return this.internalQueue.SyncRoot; }
|
||||
}
|
||||
|
||||
/// <summary>Whether operations performed on this priority queue are thread safe</summary>
|
||||
public bool IsSynchronized {
|
||||
get { return this.internalQueue.IsSynchronized; }
|
||||
}
|
||||
|
||||
/// <summary>Returns a typesafe enumerator for the priority queue</summary>
|
||||
/// <returns>A new enumerator for the priority queue</returns>
|
||||
public IEnumerator<PriorityItemPair<TPriority, TItem>> GetEnumerator() {
|
||||
return this.internalQueue.GetEnumerator();
|
||||
}
|
||||
|
||||
/// <summary>Returns an enumerator for the priority queue</summary>
|
||||
/// <returns>A new enumerator for the priority queue</returns>
|
||||
IEnumerator IEnumerable.GetEnumerator() {
|
||||
return this.internalQueue.GetEnumerator();
|
||||
}
|
||||
|
||||
/// <summary>Intrusive priority queue being wrapped by this class</summary>
|
||||
private PriorityQueue<PriorityItemPair<TPriority, TItem>> internalQueue;
|
||||
|
||||
}
|
||||
|
||||
} // namespace Nuclex.Support.Collections
|
||||
|
|
|
|||
|
|
@ -1,101 +1,100 @@
|
|||
#region CPL License
|
||||
/*
|
||||
Nuclex Framework
|
||||
Copyright (C) 2002-2017 Nuclex Development Labs
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the IBM Common Public License as
|
||||
published by the IBM Corporation; either version 1.0 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
IBM Common Public License for more details.
|
||||
|
||||
You should have received a copy of the IBM Common Public
|
||||
License along with this library
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
#if UNITTEST
|
||||
|
||||
using NUnit.Framework;
|
||||
|
||||
namespace Nuclex.Support.Collections {
|
||||
|
||||
/// <summary>Unit Test for the Parentable class</summary>
|
||||
[TestFixture]
|
||||
internal class ParentableTest {
|
||||
|
||||
#region class TestParentable
|
||||
|
||||
/// <summary>Parentable object that can be the child of an int</summary>
|
||||
private class TestParentable : Parentable<int> {
|
||||
|
||||
/// <summary>Initializes a new instance of the parentable test class</summary>
|
||||
public TestParentable() { }
|
||||
|
||||
/// <summary>The parent object that owns this instance</summary>
|
||||
public int GetParent() {
|
||||
return base.Parent;
|
||||
}
|
||||
|
||||
/// <summary>Invoked whenever the instance's owner changes</summary>
|
||||
/// <remarks>
|
||||
/// When items are parented for the first time, the oldParent argument will
|
||||
/// be null. Also, if the element is removed from the collection, the
|
||||
/// current parent will be null.
|
||||
/// </remarks>
|
||||
/// <param name="oldParent">Previous owner of the instance</param>
|
||||
protected override void OnParentChanged(int oldParent) {
|
||||
this.parentChangedCalled = true;
|
||||
|
||||
base.OnParentChanged(oldParent); // to satisfy NCover :-/
|
||||
}
|
||||
|
||||
/// <summary>Whether the OnParentChanged method has been called</summary>
|
||||
public bool ParentChangedCalled {
|
||||
get { return this.parentChangedCalled; }
|
||||
}
|
||||
|
||||
/// <summary>Whether the OnParentChanged method has been called</summary>
|
||||
private bool parentChangedCalled;
|
||||
|
||||
}
|
||||
|
||||
#endregion // class TestParentable
|
||||
|
||||
/// <summary>
|
||||
/// Tests whether a parent can be assigned and then retrieved from
|
||||
/// the parentable object
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestParentAssignment() {
|
||||
TestParentable testParentable = new TestParentable();
|
||||
|
||||
testParentable.SetParent(12345);
|
||||
Assert.AreEqual(12345, testParentable.GetParent());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests whether a parent can be assigned and then retrieved from
|
||||
/// the parentable object
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestParentChangedNotification() {
|
||||
TestParentable testParentable = new TestParentable();
|
||||
|
||||
testParentable.SetParent(12345);
|
||||
|
||||
Assert.IsTrue(testParentable.ParentChangedCalled);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} // namespace Nuclex.Support.Collections
|
||||
|
||||
#endif // UNITTEST
|
||||
#region Apache License 2.0
|
||||
/*
|
||||
Nuclex .NET Framework
|
||||
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
#endregion // Apache License 2.0
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
#if UNITTEST
|
||||
|
||||
using NUnit.Framework;
|
||||
|
||||
namespace Nuclex.Support.Collections {
|
||||
|
||||
/// <summary>Unit Test for the Parentable class</summary>
|
||||
[TestFixture]
|
||||
internal class ParentableTest {
|
||||
|
||||
#region class TestParentable
|
||||
|
||||
/// <summary>Parentable object that can be the child of an int</summary>
|
||||
private class TestParentable : Parentable<int> {
|
||||
|
||||
/// <summary>Initializes a new instance of the parentable test class</summary>
|
||||
public TestParentable() { }
|
||||
|
||||
/// <summary>The parent object that owns this instance</summary>
|
||||
public int GetParent() {
|
||||
return base.Parent;
|
||||
}
|
||||
|
||||
/// <summary>Invoked whenever the instance's owner changes</summary>
|
||||
/// <remarks>
|
||||
/// When items are parented for the first time, the oldParent argument will
|
||||
/// be null. Also, if the element is removed from the collection, the
|
||||
/// current parent will be null.
|
||||
/// </remarks>
|
||||
/// <param name="oldParent">Previous owner of the instance</param>
|
||||
protected override void OnParentChanged(int oldParent) {
|
||||
this.parentChangedCalled = true;
|
||||
|
||||
base.OnParentChanged(oldParent); // to satisfy NCover :-/
|
||||
}
|
||||
|
||||
/// <summary>Whether the OnParentChanged method has been called</summary>
|
||||
public bool ParentChangedCalled {
|
||||
get { return this.parentChangedCalled; }
|
||||
}
|
||||
|
||||
/// <summary>Whether the OnParentChanged method has been called</summary>
|
||||
private bool parentChangedCalled;
|
||||
|
||||
}
|
||||
|
||||
#endregion // class TestParentable
|
||||
|
||||
/// <summary>
|
||||
/// Tests whether a parent can be assigned and then retrieved from
|
||||
/// the parentable object
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestParentAssignment() {
|
||||
TestParentable testParentable = new TestParentable();
|
||||
|
||||
testParentable.SetParent(12345);
|
||||
Assert.AreEqual(12345, testParentable.GetParent());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests whether a parent can be assigned and then retrieved from
|
||||
/// the parentable object
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestParentChangedNotification() {
|
||||
TestParentable testParentable = new TestParentable();
|
||||
|
||||
testParentable.SetParent(12345);
|
||||
|
||||
Assert.IsTrue(testParentable.ParentChangedCalled);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} // namespace Nuclex.Support.Collections
|
||||
|
||||
#endif // UNITTEST
|
||||
|
|
|
|||
|
|
@ -1,56 +1,55 @@
|
|||
#region CPL License
|
||||
/*
|
||||
Nuclex Framework
|
||||
Copyright (C) 2002-2017 Nuclex Development Labs
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the IBM Common Public License as
|
||||
published by the IBM Corporation; either version 1.0 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
IBM Common Public License for more details.
|
||||
|
||||
You should have received a copy of the IBM Common Public
|
||||
License along with this library
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
|
||||
namespace Nuclex.Support.Collections {
|
||||
|
||||
/// <summary>Base class for objects that can be parented to an owner</summary>
|
||||
/// <typeparam name="TParent">Type of the parent object</typeparam>
|
||||
public class Parentable<TParent> {
|
||||
|
||||
/// <summary>The parent object that owns this instance</summary>
|
||||
protected TParent Parent {
|
||||
get { return this.parent; }
|
||||
}
|
||||
|
||||
/// <summary>Invoked whenever the instance's owner changes</summary>
|
||||
/// <remarks>
|
||||
/// When items are parented for the first time, the oldParent argument will
|
||||
/// be null. Also, if the element is removed from the collection, the
|
||||
/// current parent will be null.
|
||||
/// </remarks>
|
||||
/// <param name="oldParent">Previous owner of the instance</param>
|
||||
protected virtual void OnParentChanged(TParent oldParent) { }
|
||||
|
||||
/// <summary>Assigns a new parent to this instance</summary>
|
||||
internal void SetParent(TParent parent) {
|
||||
TParent oldParent = this.parent;
|
||||
this.parent = parent;
|
||||
|
||||
OnParentChanged(oldParent);
|
||||
}
|
||||
|
||||
/// <summary>Current parent of this object</summary>
|
||||
private TParent parent;
|
||||
|
||||
}
|
||||
|
||||
} // namespace Nuclex.Support.Collections
|
||||
#region Apache License 2.0
|
||||
/*
|
||||
Nuclex .NET Framework
|
||||
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
#endregion // Apache License 2.0
|
||||
|
||||
using System;
|
||||
|
||||
namespace Nuclex.Support.Collections {
|
||||
|
||||
/// <summary>Base class for objects that can be parented to an owner</summary>
|
||||
/// <typeparam name="TParent">Type of the parent object</typeparam>
|
||||
public class Parentable<TParent> {
|
||||
|
||||
/// <summary>The parent object that owns this instance</summary>
|
||||
protected TParent Parent {
|
||||
get { return this.parent; }
|
||||
}
|
||||
|
||||
/// <summary>Invoked whenever the instance's owner changes</summary>
|
||||
/// <remarks>
|
||||
/// When items are parented for the first time, the oldParent argument will
|
||||
/// be null. Also, if the element is removed from the collection, the
|
||||
/// current parent will be null.
|
||||
/// </remarks>
|
||||
/// <param name="oldParent">Previous owner of the instance</param>
|
||||
protected virtual void OnParentChanged(TParent oldParent) { }
|
||||
|
||||
/// <summary>Assigns a new parent to this instance</summary>
|
||||
internal void SetParent(TParent parent) {
|
||||
TParent oldParent = this.parent;
|
||||
this.parent = parent;
|
||||
|
||||
OnParentChanged(oldParent);
|
||||
}
|
||||
|
||||
/// <summary>Current parent of this object</summary>
|
||||
private TParent parent;
|
||||
|
||||
}
|
||||
|
||||
} // namespace Nuclex.Support.Collections
|
||||
|
|
|
|||
|
|
@ -1,190 +1,189 @@
|
|||
#region CPL License
|
||||
/*
|
||||
Nuclex Framework
|
||||
Copyright (C) 2002-2017 Nuclex Development Labs
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the IBM Common Public License as
|
||||
published by the IBM Corporation; either version 1.0 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
IBM Common Public License for more details.
|
||||
|
||||
You should have received a copy of the IBM Common Public
|
||||
License along with this library
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
#if UNITTEST
|
||||
|
||||
using NUnit.Framework;
|
||||
|
||||
namespace Nuclex.Support.Collections {
|
||||
|
||||
/// <summary>Unit Test for the Parenting Collection class</summary>
|
||||
[TestFixture]
|
||||
internal class ParentingCollectionTest {
|
||||
|
||||
#region class TestParentable
|
||||
|
||||
/// <summary>Parentable object that can be the child of an int</summary>
|
||||
private class TestParentable : Parentable<int>, IDisposable {
|
||||
|
||||
/// <summary>Initializes a new instance of the parentable test class</summary>
|
||||
public TestParentable() { }
|
||||
|
||||
/// <summary>The parent object that owns this instance</summary>
|
||||
public int GetParent() {
|
||||
return base.Parent;
|
||||
}
|
||||
|
||||
/// <summary>Immediately releases all resources owned by the item</summary>
|
||||
public void Dispose() {
|
||||
this.disposeCalled = true;
|
||||
}
|
||||
|
||||
/// <summary>Whether Dispose() has been called on this item</summary>
|
||||
public bool DisposeCalled {
|
||||
get { return this.disposeCalled; }
|
||||
}
|
||||
|
||||
/// <summary>Whether Dispose() has been called on this item</summary>
|
||||
private bool disposeCalled;
|
||||
|
||||
}
|
||||
|
||||
#endregion // class TestParentable
|
||||
|
||||
#region class TestParentingCollection
|
||||
|
||||
/// <summary>Parentable object that can be the child of an int</summary>
|
||||
private class TestParentingCollection : ParentingCollection<int, TestParentable> {
|
||||
|
||||
/// <summary>Changes the parent of the collection</summary>
|
||||
/// <param name="parent">New parent to assign to the collection</param>
|
||||
public void SetParent(int parent) {
|
||||
base.Reparent(parent);
|
||||
}
|
||||
|
||||
/// <summary>Disposes all items contained in the collection</summary>
|
||||
public new void DisposeItems() {
|
||||
base.DisposeItems();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endregion // class TestParentingCollection
|
||||
|
||||
/// <summary>
|
||||
/// Tests whether the parenting collection propagates its parent to an item that
|
||||
/// is added to the collection after the collection's aprent is already assigned
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestPropagatePreassignedParent() {
|
||||
TestParentingCollection testCollection = new TestParentingCollection();
|
||||
TestParentable testParentable = new TestParentable();
|
||||
|
||||
testCollection.SetParent(54321);
|
||||
testCollection.Add(testParentable);
|
||||
|
||||
Assert.AreEqual(54321, testParentable.GetParent());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests whether the parenting collection propagates a new parent to all items
|
||||
/// contained in it when its parent is changed
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestPropagateParentChange() {
|
||||
TestParentingCollection testCollection = new TestParentingCollection();
|
||||
TestParentable testParentable = new TestParentable();
|
||||
|
||||
testCollection.Add(testParentable);
|
||||
testCollection.SetParent(54321);
|
||||
|
||||
Assert.AreEqual(54321, testParentable.GetParent());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests whether the parenting collection propagates its parent to an item that
|
||||
/// is added to the collection after the collection's aprent is already assigned
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestPropagateParentOnReplace() {
|
||||
TestParentingCollection testCollection = new TestParentingCollection();
|
||||
TestParentable testParentable1 = new TestParentable();
|
||||
TestParentable testParentable2 = new TestParentable();
|
||||
|
||||
testCollection.SetParent(54321);
|
||||
testCollection.Add(testParentable1);
|
||||
testCollection[0] = testParentable2;
|
||||
|
||||
Assert.AreEqual(0, testParentable1.GetParent());
|
||||
Assert.AreEqual(54321, testParentable2.GetParent());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests whether the parenting collection unsets the parent when an item is removed
|
||||
/// from the collection
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestUnsetParentOnRemoveItem() {
|
||||
TestParentingCollection testCollection = new TestParentingCollection();
|
||||
TestParentable testParentable = new TestParentable();
|
||||
|
||||
testCollection.Add(testParentable);
|
||||
testCollection.SetParent(54321);
|
||||
|
||||
Assert.AreEqual(54321, testParentable.GetParent());
|
||||
|
||||
testCollection.RemoveAt(0);
|
||||
|
||||
Assert.AreEqual(0, testParentable.GetParent());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests whether the parenting collection unsets the parent when all item are
|
||||
/// removed from the collection by clearing it
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestUnsetParentOnClear() {
|
||||
TestParentingCollection testCollection = new TestParentingCollection();
|
||||
TestParentable testParentable = new TestParentable();
|
||||
|
||||
testCollection.Add(testParentable);
|
||||
testCollection.SetParent(54321);
|
||||
|
||||
Assert.AreEqual(54321, testParentable.GetParent());
|
||||
|
||||
testCollection.Clear();
|
||||
|
||||
Assert.AreEqual(0, testParentable.GetParent());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests whether the parenting collection calls Dispose() on all contained items
|
||||
/// that implement IDisposable when its DisposeItems() method is called
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestDisposeItems() {
|
||||
TestParentingCollection testCollection = new TestParentingCollection();
|
||||
TestParentable testParentable = new TestParentable();
|
||||
|
||||
testCollection.Add(testParentable);
|
||||
|
||||
testCollection.DisposeItems();
|
||||
|
||||
Assert.IsTrue(testParentable.DisposeCalled);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} // namespace Nuclex.Support.Collections
|
||||
|
||||
#endif // UNITTEST
|
||||
#region Apache License 2.0
|
||||
/*
|
||||
Nuclex .NET Framework
|
||||
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
#endregion // Apache License 2.0
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
#if UNITTEST
|
||||
|
||||
using NUnit.Framework;
|
||||
|
||||
namespace Nuclex.Support.Collections {
|
||||
|
||||
/// <summary>Unit Test for the Parenting Collection class</summary>
|
||||
[TestFixture]
|
||||
internal class ParentingCollectionTest {
|
||||
|
||||
#region class TestParentable
|
||||
|
||||
/// <summary>Parentable object that can be the child of an int</summary>
|
||||
private class TestParentable : Parentable<int>, IDisposable {
|
||||
|
||||
/// <summary>Initializes a new instance of the parentable test class</summary>
|
||||
public TestParentable() { }
|
||||
|
||||
/// <summary>The parent object that owns this instance</summary>
|
||||
public int GetParent() {
|
||||
return base.Parent;
|
||||
}
|
||||
|
||||
/// <summary>Immediately releases all resources owned by the item</summary>
|
||||
public void Dispose() {
|
||||
this.disposeCalled = true;
|
||||
}
|
||||
|
||||
/// <summary>Whether Dispose() has been called on this item</summary>
|
||||
public bool DisposeCalled {
|
||||
get { return this.disposeCalled; }
|
||||
}
|
||||
|
||||
/// <summary>Whether Dispose() has been called on this item</summary>
|
||||
private bool disposeCalled;
|
||||
|
||||
}
|
||||
|
||||
#endregion // class TestParentable
|
||||
|
||||
#region class TestParentingCollection
|
||||
|
||||
/// <summary>Parentable object that can be the child of an int</summary>
|
||||
private class TestParentingCollection : ParentingCollection<int, TestParentable> {
|
||||
|
||||
/// <summary>Changes the parent of the collection</summary>
|
||||
/// <param name="parent">New parent to assign to the collection</param>
|
||||
public void SetParent(int parent) {
|
||||
base.Reparent(parent);
|
||||
}
|
||||
|
||||
/// <summary>Disposes all items contained in the collection</summary>
|
||||
public new void DisposeItems() {
|
||||
base.DisposeItems();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endregion // class TestParentingCollection
|
||||
|
||||
/// <summary>
|
||||
/// Tests whether the parenting collection propagates its parent to an item that
|
||||
/// is added to the collection after the collection's aprent is already assigned
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestPropagatePreassignedParent() {
|
||||
TestParentingCollection testCollection = new TestParentingCollection();
|
||||
TestParentable testParentable = new TestParentable();
|
||||
|
||||
testCollection.SetParent(54321);
|
||||
testCollection.Add(testParentable);
|
||||
|
||||
Assert.AreEqual(54321, testParentable.GetParent());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests whether the parenting collection propagates a new parent to all items
|
||||
/// contained in it when its parent is changed
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestPropagateParentChange() {
|
||||
TestParentingCollection testCollection = new TestParentingCollection();
|
||||
TestParentable testParentable = new TestParentable();
|
||||
|
||||
testCollection.Add(testParentable);
|
||||
testCollection.SetParent(54321);
|
||||
|
||||
Assert.AreEqual(54321, testParentable.GetParent());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests whether the parenting collection propagates its parent to an item that
|
||||
/// is added to the collection after the collection's aprent is already assigned
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestPropagateParentOnReplace() {
|
||||
TestParentingCollection testCollection = new TestParentingCollection();
|
||||
TestParentable testParentable1 = new TestParentable();
|
||||
TestParentable testParentable2 = new TestParentable();
|
||||
|
||||
testCollection.SetParent(54321);
|
||||
testCollection.Add(testParentable1);
|
||||
testCollection[0] = testParentable2;
|
||||
|
||||
Assert.AreEqual(0, testParentable1.GetParent());
|
||||
Assert.AreEqual(54321, testParentable2.GetParent());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests whether the parenting collection unsets the parent when an item is removed
|
||||
/// from the collection
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestUnsetParentOnRemoveItem() {
|
||||
TestParentingCollection testCollection = new TestParentingCollection();
|
||||
TestParentable testParentable = new TestParentable();
|
||||
|
||||
testCollection.Add(testParentable);
|
||||
testCollection.SetParent(54321);
|
||||
|
||||
Assert.AreEqual(54321, testParentable.GetParent());
|
||||
|
||||
testCollection.RemoveAt(0);
|
||||
|
||||
Assert.AreEqual(0, testParentable.GetParent());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests whether the parenting collection unsets the parent when all item are
|
||||
/// removed from the collection by clearing it
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestUnsetParentOnClear() {
|
||||
TestParentingCollection testCollection = new TestParentingCollection();
|
||||
TestParentable testParentable = new TestParentable();
|
||||
|
||||
testCollection.Add(testParentable);
|
||||
testCollection.SetParent(54321);
|
||||
|
||||
Assert.AreEqual(54321, testParentable.GetParent());
|
||||
|
||||
testCollection.Clear();
|
||||
|
||||
Assert.AreEqual(0, testParentable.GetParent());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests whether the parenting collection calls Dispose() on all contained items
|
||||
/// that implement IDisposable when its DisposeItems() method is called
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestDisposeItems() {
|
||||
TestParentingCollection testCollection = new TestParentingCollection();
|
||||
TestParentable testParentable = new TestParentable();
|
||||
|
||||
testCollection.Add(testParentable);
|
||||
|
||||
testCollection.DisposeItems();
|
||||
|
||||
Assert.IsTrue(testParentable.DisposeCalled);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} // namespace Nuclex.Support.Collections
|
||||
|
||||
#endif // UNITTEST
|
||||
|
|
|
|||
|
|
@ -1,115 +1,114 @@
|
|||
#region CPL License
|
||||
/*
|
||||
Nuclex Framework
|
||||
Copyright (C) 2002-2017 Nuclex Development Labs
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the IBM Common Public License as
|
||||
published by the IBM Corporation; either version 1.0 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
IBM Common Public License for more details.
|
||||
|
||||
You should have received a copy of the IBM Common Public
|
||||
License along with this library
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
|
||||
namespace Nuclex.Support.Collections {
|
||||
|
||||
/// <summary>Collection that automatically assigns an owner to all its elements</summary>
|
||||
/// <remarks>
|
||||
/// This collection automatically assigns a parent object to elements that
|
||||
/// are managed in it. The elements have to derive from the Parentable<>
|
||||
/// base class.
|
||||
/// </remarks>
|
||||
/// <typeparam name="TParent">Type of the parent object to assign to items</typeparam>
|
||||
/// <typeparam name="TItem">Type of the items being managed in the collection</typeparam>
|
||||
public class ParentingCollection<TParent, TItem> : Collection<TItem>
|
||||
where TItem : Parentable<TParent> {
|
||||
|
||||
/// <summary>Reparents all elements in the collection</summary>
|
||||
/// <param name="parent">New parent to take ownership of the items</param>
|
||||
protected void Reparent(TParent parent) {
|
||||
this.parent = parent;
|
||||
|
||||
for(int index = 0; index < Count; ++index)
|
||||
base[index].SetParent(parent);
|
||||
}
|
||||
|
||||
/// <summary>Clears all elements from the collection</summary>
|
||||
protected override void ClearItems() {
|
||||
for(int index = 0; index < Count; ++index)
|
||||
base[index].SetParent(default(TParent));
|
||||
|
||||
base.ClearItems();
|
||||
}
|
||||
|
||||
/// <summary>Inserts a new element into the collection</summary>
|
||||
/// <param name="index">Index at which to insert the element</param>
|
||||
/// <param name="item">Item to be inserted</param>
|
||||
protected override void InsertItem(int index, TItem item) {
|
||||
base.InsertItem(index, item);
|
||||
item.SetParent(this.parent);
|
||||
}
|
||||
|
||||
/// <summary>Removes an element from the collection</summary>
|
||||
/// <param name="index">Index of the element to remove</param>
|
||||
protected override void RemoveItem(int index) {
|
||||
base[index].SetParent(default(TParent));
|
||||
base.RemoveItem(index);
|
||||
}
|
||||
|
||||
/// <summary>Takes over a new element that is directly assigned</summary>
|
||||
/// <param name="index">Index of the element that was assigned</param>
|
||||
/// <param name="item">New item</param>
|
||||
protected override void SetItem(int index, TItem item) {
|
||||
base[index].SetParent(default(TParent));
|
||||
base.SetItem(index, item);
|
||||
item.SetParent(this.parent);
|
||||
}
|
||||
|
||||
/// <summary>Disposes all items contained in the collection</summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// This method is intended to support collections that need to dispose their
|
||||
/// items. It will unparent all of the collection's items and call Dispose()
|
||||
/// on any item that implements IDisposable.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Do not call this method from your destructor as it will access the
|
||||
/// contained items in order to unparent and to Dispose() them, which leads
|
||||
/// to undefined behavior since the object might have already been collected
|
||||
/// by the GC. Call it only if your object is being manually disposed.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
protected void DisposeItems() {
|
||||
|
||||
// Dispose all the items in the collection that implement IDisposable,
|
||||
// starting from the last item in the assumption that this is the fastest
|
||||
// way to empty a list without causing excessive shiftings in the array.
|
||||
for(int index = base.Count - 1; index >= 0; --index) {
|
||||
IDisposable disposable = base[index] as IDisposable;
|
||||
|
||||
// If the item is disposable, destroy it now
|
||||
if(disposable != null) {
|
||||
disposable.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
base.ClearItems();
|
||||
|
||||
}
|
||||
|
||||
/// <summary>Parent this collection currently belongs to</summary>
|
||||
private TParent parent;
|
||||
|
||||
}
|
||||
|
||||
} // namespace Nuclex.Support.Collections
|
||||
#region Apache License 2.0
|
||||
/*
|
||||
Nuclex .NET Framework
|
||||
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
#endregion // Apache License 2.0
|
||||
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
|
||||
namespace Nuclex.Support.Collections {
|
||||
|
||||
/// <summary>Collection that automatically assigns an owner to all its elements</summary>
|
||||
/// <remarks>
|
||||
/// This collection automatically assigns a parent object to elements that
|
||||
/// are managed in it. The elements have to derive from the Parentable<>
|
||||
/// base class.
|
||||
/// </remarks>
|
||||
/// <typeparam name="TParent">Type of the parent object to assign to items</typeparam>
|
||||
/// <typeparam name="TItem">Type of the items being managed in the collection</typeparam>
|
||||
public class ParentingCollection<TParent, TItem> : Collection<TItem>
|
||||
where TItem : Parentable<TParent> {
|
||||
|
||||
/// <summary>Reparents all elements in the collection</summary>
|
||||
/// <param name="parent">New parent to take ownership of the items</param>
|
||||
protected void Reparent(TParent parent) {
|
||||
this.parent = parent;
|
||||
|
||||
for(int index = 0; index < Count; ++index)
|
||||
base[index].SetParent(parent);
|
||||
}
|
||||
|
||||
/// <summary>Clears all elements from the collection</summary>
|
||||
protected override void ClearItems() {
|
||||
for(int index = 0; index < Count; ++index)
|
||||
base[index].SetParent(default(TParent));
|
||||
|
||||
base.ClearItems();
|
||||
}
|
||||
|
||||
/// <summary>Inserts a new element into the collection</summary>
|
||||
/// <param name="index">Index at which to insert the element</param>
|
||||
/// <param name="item">Item to be inserted</param>
|
||||
protected override void InsertItem(int index, TItem item) {
|
||||
base.InsertItem(index, item);
|
||||
item.SetParent(this.parent);
|
||||
}
|
||||
|
||||
/// <summary>Removes an element from the collection</summary>
|
||||
/// <param name="index">Index of the element to remove</param>
|
||||
protected override void RemoveItem(int index) {
|
||||
base[index].SetParent(default(TParent));
|
||||
base.RemoveItem(index);
|
||||
}
|
||||
|
||||
/// <summary>Takes over a new element that is directly assigned</summary>
|
||||
/// <param name="index">Index of the element that was assigned</param>
|
||||
/// <param name="item">New item</param>
|
||||
protected override void SetItem(int index, TItem item) {
|
||||
base[index].SetParent(default(TParent));
|
||||
base.SetItem(index, item);
|
||||
item.SetParent(this.parent);
|
||||
}
|
||||
|
||||
/// <summary>Disposes all items contained in the collection</summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// This method is intended to support collections that need to dispose their
|
||||
/// items. It will unparent all of the collection's items and call Dispose()
|
||||
/// on any item that implements IDisposable.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Do not call this method from your destructor as it will access the
|
||||
/// contained items in order to unparent and to Dispose() them, which leads
|
||||
/// to undefined behavior since the object might have already been collected
|
||||
/// by the GC. Call it only if your object is being manually disposed.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
protected void DisposeItems() {
|
||||
|
||||
// Dispose all the items in the collection that implement IDisposable,
|
||||
// starting from the last item in the assumption that this is the fastest
|
||||
// way to empty a list without causing excessive shiftings in the array.
|
||||
for(int index = base.Count - 1; index >= 0; --index) {
|
||||
IDisposable disposable = base[index] as IDisposable;
|
||||
|
||||
// If the item is disposable, destroy it now
|
||||
if(disposable != null) {
|
||||
disposable.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
base.ClearItems();
|
||||
|
||||
}
|
||||
|
||||
/// <summary>Parent this collection currently belongs to</summary>
|
||||
private TParent parent;
|
||||
|
||||
}
|
||||
|
||||
} // namespace Nuclex.Support.Collections
|
||||
|
|
|
|||
|
|
@ -1,118 +1,117 @@
|
|||
#region CPL License
|
||||
/*
|
||||
Nuclex Framework
|
||||
Copyright (C) 2002-2017 Nuclex Development Labs
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the IBM Common Public License as
|
||||
published by the IBM Corporation; either version 1.0 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
IBM Common Public License for more details.
|
||||
|
||||
You should have received a copy of the IBM Common Public
|
||||
License along with this library
|
||||
*/
|
||||
#endregion
|
||||
|
||||
#if UNITTEST
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
using NUnit.Framework;
|
||||
|
||||
namespace Nuclex.Support.Collections {
|
||||
|
||||
/// <summary>Unit tests for the Pool class</summary>
|
||||
[TestFixture]
|
||||
internal class PoolTest {
|
||||
|
||||
#region class TestClass
|
||||
|
||||
/// <summary>Used to test the pool</summary>
|
||||
private class TestClass : IRecyclable {
|
||||
|
||||
/// <summary>Returns the object to its initial state</summary>
|
||||
public void Recycle() {
|
||||
this.Recycled = true;
|
||||
}
|
||||
|
||||
/// <summary>Whether the instance has been recycled</summary>
|
||||
public bool Recycled;
|
||||
|
||||
}
|
||||
|
||||
#endregion // class TestClass
|
||||
|
||||
#region class NoDefaultConstructor
|
||||
|
||||
/// <summary>Used to test the pool</summary>
|
||||
private class NoDefaultConstructor {
|
||||
|
||||
/// <summary>Private constructor so no instances can be created</summary>
|
||||
private NoDefaultConstructor() { }
|
||||
|
||||
}
|
||||
|
||||
#endregion // class NoDefaultConstructor
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that the pool can return newly constructed objects
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void NewInstancesCanBeObtained() {
|
||||
Pool<TestClass> pool = new Pool<TestClass>();
|
||||
Assert.IsNotNull(pool.Get());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that an exception is thrown if the pool's default instance creator is used
|
||||
/// on a type that doesn't have a default constructor
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void UsingDefaultInstanceCreatorRequiresDefaultConstructor() {
|
||||
Assert.Throws<ArgumentException>(
|
||||
delegate() { new Pool<NoDefaultConstructor>(); }
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests whether the pool can redeem objects that are no longer used
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void InstancesCanBeRedeemed() {
|
||||
Pool<TestClass> pool = new Pool<TestClass>();
|
||||
pool.Redeem(new TestClass());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests whether the Recycle() method is called at the appropriate time
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void RedeemedItemsWillBeRecycled() {
|
||||
Pool<TestClass> pool = new Pool<TestClass>();
|
||||
TestClass x = new TestClass();
|
||||
|
||||
Assert.IsFalse(x.Recycled);
|
||||
pool.Redeem(x);
|
||||
Assert.IsTrue(x.Recycled);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that the pool's Capacity is applied correctly</summary>
|
||||
[Test]
|
||||
public void PoolCapacityCanBeAdjusted() {
|
||||
Pool<TestClass> pool = new Pool<TestClass>(123);
|
||||
Assert.AreEqual(123, pool.Capacity);
|
||||
pool.Capacity = 321;
|
||||
Assert.AreEqual(321, pool.Capacity);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} // namespace Nuclex.Support.Collections
|
||||
|
||||
#endif // UNITTEST
|
||||
#region Apache License 2.0
|
||||
/*
|
||||
Nuclex .NET Framework
|
||||
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
#endregion // Apache License 2.0
|
||||
|
||||
#if UNITTEST
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
using NUnit.Framework;
|
||||
|
||||
namespace Nuclex.Support.Collections {
|
||||
|
||||
/// <summary>Unit tests for the Pool class</summary>
|
||||
[TestFixture]
|
||||
internal class PoolTest {
|
||||
|
||||
#region class TestClass
|
||||
|
||||
/// <summary>Used to test the pool</summary>
|
||||
private class TestClass : IRecyclable {
|
||||
|
||||
/// <summary>Returns the object to its initial state</summary>
|
||||
public void Recycle() {
|
||||
this.Recycled = true;
|
||||
}
|
||||
|
||||
/// <summary>Whether the instance has been recycled</summary>
|
||||
public bool Recycled;
|
||||
|
||||
}
|
||||
|
||||
#endregion // class TestClass
|
||||
|
||||
#region class NoDefaultConstructor
|
||||
|
||||
/// <summary>Used to test the pool</summary>
|
||||
private class NoDefaultConstructor {
|
||||
|
||||
/// <summary>Private constructor so no instances can be created</summary>
|
||||
private NoDefaultConstructor() { }
|
||||
|
||||
}
|
||||
|
||||
#endregion // class NoDefaultConstructor
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that the pool can return newly constructed objects
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void NewInstancesCanBeObtained() {
|
||||
Pool<TestClass> pool = new Pool<TestClass>();
|
||||
Assert.IsNotNull(pool.Get());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that an exception is thrown if the pool's default instance creator is used
|
||||
/// on a type that doesn't have a default constructor
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void UsingDefaultInstanceCreatorRequiresDefaultConstructor() {
|
||||
Assert.Throws<ArgumentException>(
|
||||
delegate() { new Pool<NoDefaultConstructor>(); }
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests whether the pool can redeem objects that are no longer used
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void InstancesCanBeRedeemed() {
|
||||
Pool<TestClass> pool = new Pool<TestClass>();
|
||||
pool.Redeem(new TestClass());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests whether the Recycle() method is called at the appropriate time
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void RedeemedItemsWillBeRecycled() {
|
||||
Pool<TestClass> pool = new Pool<TestClass>();
|
||||
TestClass x = new TestClass();
|
||||
|
||||
Assert.IsFalse(x.Recycled);
|
||||
pool.Redeem(x);
|
||||
Assert.IsTrue(x.Recycled);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that the pool's Capacity is applied correctly</summary>
|
||||
[Test]
|
||||
public void PoolCapacityCanBeAdjusted() {
|
||||
Pool<TestClass> pool = new Pool<TestClass>(123);
|
||||
Assert.AreEqual(123, pool.Capacity);
|
||||
pool.Capacity = 321;
|
||||
Assert.AreEqual(321, pool.Capacity);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} // namespace Nuclex.Support.Collections
|
||||
|
||||
#endif // UNITTEST
|
||||
|
|
|
|||
|
|
@ -1,175 +1,174 @@
|
|||
#region CPL License
|
||||
/*
|
||||
Nuclex Framework
|
||||
Copyright (C) 2002-2017 Nuclex Development Labs
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the IBM Common Public License as
|
||||
published by the IBM Corporation; either version 1.0 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
IBM Common Public License for more details.
|
||||
|
||||
You should have received a copy of the IBM Common Public
|
||||
License along with this library
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Nuclex.Support.Collections {
|
||||
|
||||
/// <summary>Pool that recycles objects in order to avoid garbage build-up</summary>
|
||||
/// <typeparam name="TItem">Type of objects being pooled</typeparam>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// Use this class to recycle objects instead of letting them become garbage,
|
||||
/// creating new instances each time. The Pool class is designed to either be
|
||||
/// used on its own or as a building block for a static class that wraps it.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Special care has to be taken to revert the entire state of a recycled
|
||||
/// object when it is returned to the pool. For example, events will need to
|
||||
/// have their subscriber lists emptied to avoid sending out events to the
|
||||
/// wrong subscribers and accumulating more and more subscribers each time
|
||||
/// they are reused.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// To simplify such cleanup, pooled objects can implement the IRecyclable
|
||||
/// interface. When an object is returned to the pool, the pool will
|
||||
/// automatically call its IRecyclable.Recycle() method.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public class Pool<TItem> {
|
||||
|
||||
/// <summary>Default number of recyclable objects the pool will store</summary>
|
||||
public const int DefaultPoolSize = 64;
|
||||
|
||||
/// <summary>Initializes a new pool using the default capacity</summary>
|
||||
public Pool() : this(DefaultPoolSize, null, null) { }
|
||||
|
||||
/// <summary>Initializes a new pool using the default capacity</summary>
|
||||
/// <param name="createNewDelegate">Delegate that will be used to create new items</param>
|
||||
public Pool(Func<TItem> createNewDelegate) :
|
||||
this(DefaultPoolSize, createNewDelegate, null) { }
|
||||
|
||||
/// <summary>Initializes a new pool using the default capacity</summary>
|
||||
/// <param name="createNewDelegate">Delegate that will be used to create new items</param>
|
||||
/// <param name="recycleDelegate">Delegate that will be used to recycle items</param>
|
||||
public Pool(Func<TItem> createNewDelegate, Action<TItem> recycleDelegate) :
|
||||
this(DefaultPoolSize, createNewDelegate, recycleDelegate) { }
|
||||
|
||||
/// <summary>Initializes a new pool using a user-specified capacity</summary>
|
||||
/// <param name="capacity">Capacity of the pool</param>
|
||||
public Pool(int capacity) :
|
||||
this(capacity, null, null) { }
|
||||
|
||||
/// <summary>Initializes a new pool using a user-specified capacity</summary>
|
||||
/// <param name="capacity">Capacity of the pool</param>
|
||||
/// <param name="createNewDelegate">Delegate that will be used to create new items</param>
|
||||
public Pool(int capacity, Func<TItem> createNewDelegate) :
|
||||
this(capacity, createNewDelegate, null) { }
|
||||
|
||||
/// <summary>Initializes a new pool using a user-specified capacity</summary>
|
||||
/// <param name="capacity">Capacity of the pool</param>
|
||||
/// <param name="createNewDelegate">Delegate that will be used to create new items</param>
|
||||
/// <param name="recycleDelegate">Delegate that will be used to recycle items</param>
|
||||
public Pool(int capacity, Func<TItem> createNewDelegate, Action<TItem> recycleDelegate) {
|
||||
Capacity = capacity;
|
||||
|
||||
if(createNewDelegate == null) {
|
||||
if(!typeof(TItem).HasDefaultConstructor()) {
|
||||
throw new ArgumentException(
|
||||
"Type " + typeof(TItem).Name + " has no default constructor and " +
|
||||
"requires a custom 'create instance' delegate"
|
||||
);
|
||||
}
|
||||
createNewDelegate = new Func<TItem>(Activator.CreateInstance<TItem>);
|
||||
}
|
||||
if(recycleDelegate == null) {
|
||||
recycleDelegate = new Action<TItem>(callRecycleIfSupported);
|
||||
}
|
||||
|
||||
this.createNewDelegate = createNewDelegate;
|
||||
this.recycleDelegate = recycleDelegate;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a new or recycled instance of the types managed by the pool
|
||||
/// </summary>
|
||||
/// <returns>A new or recycled instance</returns>
|
||||
public TItem Get() {
|
||||
lock(this) {
|
||||
if(this.items.Count > 0) {
|
||||
return this.items.Dequeue();
|
||||
} else {
|
||||
return this.createNewDelegate();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Redeems an instance that is no longer used to be recycled by the pool
|
||||
/// </summary>
|
||||
/// <param name="item">The instance that will be redeemed</param>
|
||||
public void Redeem(TItem item) {
|
||||
|
||||
// Call Recycle() when the object is redeemed (instead of when it leaves
|
||||
// the pool again) in order to eliminate any references the object may hold
|
||||
// to other objects.
|
||||
this.recycleDelegate(item);
|
||||
|
||||
lock(this) {
|
||||
if(this.items.Count < this.capacity) {
|
||||
this.items.Enqueue(item);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>Number of objects the pool can retain</summary>
|
||||
/// <remarks>
|
||||
/// Changing this value causes the pool to be emtpied. It is recommended that
|
||||
/// you only read the pool's capacity, never change it.
|
||||
/// </remarks>
|
||||
public int Capacity {
|
||||
get { return this.capacity; }
|
||||
set {
|
||||
this.capacity = value;
|
||||
this.items = new Queue<TItem>(value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calls the Recycle() method on an objects if it implements
|
||||
/// the IRecyclable interface
|
||||
/// </summary>
|
||||
/// <param name="item">
|
||||
/// Object whose Recycle() method will be called if supported by the object
|
||||
/// </param>
|
||||
private static void callRecycleIfSupported(TItem item) {
|
||||
IRecyclable recycleable = item as IRecyclable;
|
||||
if(recycleable != null) {
|
||||
recycleable.Recycle();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Objects being retained for recycling</summary>
|
||||
private Queue<TItem> items;
|
||||
/// <summary>Capacity of the pool</summary>
|
||||
/// <remarks>
|
||||
/// Required because the Queue class doesn't allow this value to be retrieved
|
||||
/// </remarks>
|
||||
private int capacity;
|
||||
/// <summary>Delegate used to create new instances of the pool's type</summary>
|
||||
private Func<TItem> createNewDelegate;
|
||||
/// <summary>Delegate used to recycle instances</summary>
|
||||
private Action<TItem> recycleDelegate;
|
||||
|
||||
}
|
||||
|
||||
} // namespace Nuclex.Support.Collections
|
||||
#region Apache License 2.0
|
||||
/*
|
||||
Nuclex .NET Framework
|
||||
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
#endregion // Apache License 2.0
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Nuclex.Support.Collections {
|
||||
|
||||
/// <summary>Pool that recycles objects in order to avoid garbage build-up</summary>
|
||||
/// <typeparam name="TItem">Type of objects being pooled</typeparam>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// Use this class to recycle objects instead of letting them become garbage,
|
||||
/// creating new instances each time. The Pool class is designed to either be
|
||||
/// used on its own or as a building block for a static class that wraps it.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Special care has to be taken to revert the entire state of a recycled
|
||||
/// object when it is returned to the pool. For example, events will need to
|
||||
/// have their subscriber lists emptied to avoid sending out events to the
|
||||
/// wrong subscribers and accumulating more and more subscribers each time
|
||||
/// they are reused.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// To simplify such cleanup, pooled objects can implement the IRecyclable
|
||||
/// interface. When an object is returned to the pool, the pool will
|
||||
/// automatically call its IRecyclable.Recycle() method.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public class Pool<TItem> {
|
||||
|
||||
/// <summary>Default number of recyclable objects the pool will store</summary>
|
||||
public const int DefaultPoolSize = 64;
|
||||
|
||||
/// <summary>Initializes a new pool using the default capacity</summary>
|
||||
public Pool() : this(DefaultPoolSize, null, null) { }
|
||||
|
||||
/// <summary>Initializes a new pool using the default capacity</summary>
|
||||
/// <param name="createNewDelegate">Delegate that will be used to create new items</param>
|
||||
public Pool(Func<TItem> createNewDelegate) :
|
||||
this(DefaultPoolSize, createNewDelegate, null) { }
|
||||
|
||||
/// <summary>Initializes a new pool using the default capacity</summary>
|
||||
/// <param name="createNewDelegate">Delegate that will be used to create new items</param>
|
||||
/// <param name="recycleDelegate">Delegate that will be used to recycle items</param>
|
||||
public Pool(Func<TItem> createNewDelegate, Action<TItem> recycleDelegate) :
|
||||
this(DefaultPoolSize, createNewDelegate, recycleDelegate) { }
|
||||
|
||||
/// <summary>Initializes a new pool using a user-specified capacity</summary>
|
||||
/// <param name="capacity">Capacity of the pool</param>
|
||||
public Pool(int capacity) :
|
||||
this(capacity, null, null) { }
|
||||
|
||||
/// <summary>Initializes a new pool using a user-specified capacity</summary>
|
||||
/// <param name="capacity">Capacity of the pool</param>
|
||||
/// <param name="createNewDelegate">Delegate that will be used to create new items</param>
|
||||
public Pool(int capacity, Func<TItem> createNewDelegate) :
|
||||
this(capacity, createNewDelegate, null) { }
|
||||
|
||||
/// <summary>Initializes a new pool using a user-specified capacity</summary>
|
||||
/// <param name="capacity">Capacity of the pool</param>
|
||||
/// <param name="createNewDelegate">Delegate that will be used to create new items</param>
|
||||
/// <param name="recycleDelegate">Delegate that will be used to recycle items</param>
|
||||
public Pool(int capacity, Func<TItem> createNewDelegate, Action<TItem> recycleDelegate) {
|
||||
Capacity = capacity;
|
||||
|
||||
if(createNewDelegate == null) {
|
||||
if(!typeof(TItem).HasDefaultConstructor()) {
|
||||
throw new ArgumentException(
|
||||
"Type " + typeof(TItem).Name + " has no default constructor and " +
|
||||
"requires a custom 'create instance' delegate"
|
||||
);
|
||||
}
|
||||
createNewDelegate = new Func<TItem>(Activator.CreateInstance<TItem>);
|
||||
}
|
||||
if(recycleDelegate == null) {
|
||||
recycleDelegate = new Action<TItem>(callRecycleIfSupported);
|
||||
}
|
||||
|
||||
this.createNewDelegate = createNewDelegate;
|
||||
this.recycleDelegate = recycleDelegate;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a new or recycled instance of the types managed by the pool
|
||||
/// </summary>
|
||||
/// <returns>A new or recycled instance</returns>
|
||||
public TItem Get() {
|
||||
lock(this) {
|
||||
if(this.items.Count > 0) {
|
||||
return this.items.Dequeue();
|
||||
} else {
|
||||
return this.createNewDelegate();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Redeems an instance that is no longer used to be recycled by the pool
|
||||
/// </summary>
|
||||
/// <param name="item">The instance that will be redeemed</param>
|
||||
public void Redeem(TItem item) {
|
||||
|
||||
// Call Recycle() when the object is redeemed (instead of when it leaves
|
||||
// the pool again) in order to eliminate any references the object may hold
|
||||
// to other objects.
|
||||
this.recycleDelegate(item);
|
||||
|
||||
lock(this) {
|
||||
if(this.items.Count < this.capacity) {
|
||||
this.items.Enqueue(item);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>Number of objects the pool can retain</summary>
|
||||
/// <remarks>
|
||||
/// Changing this value causes the pool to be emtpied. It is recommended that
|
||||
/// you only read the pool's capacity, never change it.
|
||||
/// </remarks>
|
||||
public int Capacity {
|
||||
get { return this.capacity; }
|
||||
set {
|
||||
this.capacity = value;
|
||||
this.items = new Queue<TItem>(value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calls the Recycle() method on an objects if it implements
|
||||
/// the IRecyclable interface
|
||||
/// </summary>
|
||||
/// <param name="item">
|
||||
/// Object whose Recycle() method will be called if supported by the object
|
||||
/// </param>
|
||||
private static void callRecycleIfSupported(TItem item) {
|
||||
IRecyclable recycleable = item as IRecyclable;
|
||||
if(recycleable != null) {
|
||||
recycleable.Recycle();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Objects being retained for recycling</summary>
|
||||
private Queue<TItem> items;
|
||||
/// <summary>Capacity of the pool</summary>
|
||||
/// <remarks>
|
||||
/// Required because the Queue class doesn't allow this value to be retrieved
|
||||
/// </remarks>
|
||||
private int capacity;
|
||||
/// <summary>Delegate used to create new instances of the pool's type</summary>
|
||||
private Func<TItem> createNewDelegate;
|
||||
/// <summary>Delegate used to recycle instances</summary>
|
||||
private Action<TItem> recycleDelegate;
|
||||
|
||||
}
|
||||
|
||||
} // namespace Nuclex.Support.Collections
|
||||
|
|
|
|||
|
|
@ -1,100 +1,99 @@
|
|||
#region CPL License
|
||||
/*
|
||||
Nuclex Framework
|
||||
Copyright (C) 2002-2017 Nuclex Development Labs
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the IBM Common Public License as
|
||||
published by the IBM Corporation; either version 1.0 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
IBM Common Public License for more details.
|
||||
|
||||
You should have received a copy of the IBM Common Public
|
||||
License along with this library
|
||||
*/
|
||||
#endregion
|
||||
|
||||
#if UNITTEST
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
using NUnit.Framework;
|
||||
|
||||
namespace Nuclex.Support.Collections {
|
||||
|
||||
/// <summary>Unit Test for the Priority/Item pair class</summary>
|
||||
[TestFixture]
|
||||
internal class PriorityItemPairTest {
|
||||
|
||||
#region class ToStringNullReturner
|
||||
|
||||
/// <summary>Test class in which ToString() can return null</summary>
|
||||
private class ToStringNullReturner {
|
||||
|
||||
/// <summary>
|
||||
/// Returns a System.String that represents the current System.Object
|
||||
/// </summary>
|
||||
/// <returns>A System.String that represents the current System.Object</returns>
|
||||
public override string ToString() { return null; }
|
||||
|
||||
}
|
||||
|
||||
#endregion // class ToStringNullReturner
|
||||
|
||||
/// <summary>Tests whether the pair's default constructor works</summary>
|
||||
[Test]
|
||||
public void TestDefaultConstructor() {
|
||||
new PriorityItemPair<int, string>();
|
||||
}
|
||||
|
||||
/// <summary>Tests whether the priority can be retrieved from the pair</summary>
|
||||
[Test]
|
||||
public void TestPriorityRetrieval() {
|
||||
PriorityItemPair<int, string> testPair = new PriorityItemPair<int, string>(
|
||||
12345, "hello world"
|
||||
);
|
||||
|
||||
Assert.AreEqual(12345, testPair.Priority);
|
||||
}
|
||||
|
||||
/// <summary>Tests whether the item can be retrieved from the pair</summary>
|
||||
[Test]
|
||||
public void TestItemRetrieval() {
|
||||
PriorityItemPair<int, string> testPair = new PriorityItemPair<int, string>(
|
||||
12345, "hello world"
|
||||
);
|
||||
|
||||
Assert.AreEqual("hello world", testPair.Item);
|
||||
}
|
||||
|
||||
/// <summary>Tests whether the ToString() methods works with valid strings</summary>
|
||||
[Test]
|
||||
public void TestToStringWithValidStrings() {
|
||||
PriorityItemPair<string, string> testPair = new PriorityItemPair<string, string>(
|
||||
"hello", "world"
|
||||
);
|
||||
|
||||
Assert.AreEqual("[hello, world]", testPair.ToString());
|
||||
}
|
||||
|
||||
/// <summary>Tests whether the ToString() methods works with null strings</summary>
|
||||
[Test]
|
||||
public void TestToStringWithNullStrings() {
|
||||
PriorityItemPair<ToStringNullReturner, ToStringNullReturner> testPair =
|
||||
new PriorityItemPair<ToStringNullReturner, ToStringNullReturner>(
|
||||
new ToStringNullReturner(), new ToStringNullReturner()
|
||||
);
|
||||
|
||||
Assert.AreEqual("[, ]", testPair.ToString());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} // namespace Nuclex.Support.Collections
|
||||
|
||||
#endif // UNITTEST
|
||||
#region Apache License 2.0
|
||||
/*
|
||||
Nuclex .NET Framework
|
||||
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
#endregion // Apache License 2.0
|
||||
|
||||
#if UNITTEST
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
using NUnit.Framework;
|
||||
|
||||
namespace Nuclex.Support.Collections {
|
||||
|
||||
/// <summary>Unit Test for the Priority/Item pair class</summary>
|
||||
[TestFixture]
|
||||
internal class PriorityItemPairTest {
|
||||
|
||||
#region class ToStringNullReturner
|
||||
|
||||
/// <summary>Test class in which ToString() can return null</summary>
|
||||
private class ToStringNullReturner {
|
||||
|
||||
/// <summary>
|
||||
/// Returns a System.String that represents the current System.Object
|
||||
/// </summary>
|
||||
/// <returns>A System.String that represents the current System.Object</returns>
|
||||
public override string ToString() { return null; }
|
||||
|
||||
}
|
||||
|
||||
#endregion // class ToStringNullReturner
|
||||
|
||||
/// <summary>Tests whether the pair's default constructor works</summary>
|
||||
[Test]
|
||||
public void TestDefaultConstructor() {
|
||||
new PriorityItemPair<int, string>();
|
||||
}
|
||||
|
||||
/// <summary>Tests whether the priority can be retrieved from the pair</summary>
|
||||
[Test]
|
||||
public void TestPriorityRetrieval() {
|
||||
PriorityItemPair<int, string> testPair = new PriorityItemPair<int, string>(
|
||||
12345, "hello world"
|
||||
);
|
||||
|
||||
Assert.AreEqual(12345, testPair.Priority);
|
||||
}
|
||||
|
||||
/// <summary>Tests whether the item can be retrieved from the pair</summary>
|
||||
[Test]
|
||||
public void TestItemRetrieval() {
|
||||
PriorityItemPair<int, string> testPair = new PriorityItemPair<int, string>(
|
||||
12345, "hello world"
|
||||
);
|
||||
|
||||
Assert.AreEqual("hello world", testPair.Item);
|
||||
}
|
||||
|
||||
/// <summary>Tests whether the ToString() methods works with valid strings</summary>
|
||||
[Test]
|
||||
public void TestToStringWithValidStrings() {
|
||||
PriorityItemPair<string, string> testPair = new PriorityItemPair<string, string>(
|
||||
"hello", "world"
|
||||
);
|
||||
|
||||
Assert.AreEqual("[hello, world]", testPair.ToString());
|
||||
}
|
||||
|
||||
/// <summary>Tests whether the ToString() methods works with null strings</summary>
|
||||
[Test]
|
||||
public void TestToStringWithNullStrings() {
|
||||
PriorityItemPair<ToStringNullReturner, ToStringNullReturner> testPair =
|
||||
new PriorityItemPair<ToStringNullReturner, ToStringNullReturner>(
|
||||
new ToStringNullReturner(), new ToStringNullReturner()
|
||||
);
|
||||
|
||||
Assert.AreEqual("[, ]", testPair.ToString());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} // namespace Nuclex.Support.Collections
|
||||
|
||||
#endif // UNITTEST
|
||||
|
|
|
|||
|
|
@ -1,75 +1,74 @@
|
|||
#region CPL License
|
||||
/*
|
||||
Nuclex Framework
|
||||
Copyright (C) 2002-2017 Nuclex Development Labs
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the IBM Common Public License as
|
||||
published by the IBM Corporation; either version 1.0 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
IBM Common Public License for more details.
|
||||
|
||||
You should have received a copy of the IBM Common Public
|
||||
License along with this library
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Text;
|
||||
|
||||
namespace Nuclex.Support.Collections {
|
||||
|
||||
/// <summary>An pair of a priority and an item</summary>
|
||||
public struct PriorityItemPair<TPriority, TItem> {
|
||||
|
||||
/// <summary>Initializes a new priority / item pair</summary>
|
||||
/// <param name="priority">Priority of the item in the pair</param>
|
||||
/// <param name="item">Item to be stored in the pair</param>
|
||||
public PriorityItemPair(TPriority priority, TItem item) {
|
||||
this.Priority = priority;
|
||||
this.Item = item;
|
||||
}
|
||||
|
||||
/// <summary>Priority assigned to this priority / item pair</summary>
|
||||
public TPriority Priority;
|
||||
/// <summary>Item contained in this priority / item pair</summary>
|
||||
public TItem Item;
|
||||
|
||||
/// <summary>Converts the priority / item pair into a string</summary>
|
||||
/// <returns>A string describing the priority / item pair</returns>
|
||||
public override string ToString() {
|
||||
int length = 4;
|
||||
|
||||
// Convert the priority value into a string or use the empty string
|
||||
// constant if the ToString() overload returns null
|
||||
string priorityString = this.Priority.ToString();
|
||||
if(priorityString != null)
|
||||
length += priorityString.Length;
|
||||
else
|
||||
priorityString = string.Empty;
|
||||
|
||||
// Convert the item value into a string or use the empty string
|
||||
// constant if the ToString() overload returns null
|
||||
string itemString = this.Item.ToString();
|
||||
if(itemString != null)
|
||||
length += itemString.Length;
|
||||
else
|
||||
itemString = string.Empty;
|
||||
|
||||
// Concatenate priority and item into a single string
|
||||
StringBuilder builder = new StringBuilder(length);
|
||||
builder.Append('[');
|
||||
builder.Append(priorityString);
|
||||
builder.Append(", ");
|
||||
builder.Append(itemString);
|
||||
builder.Append(']');
|
||||
return builder.ToString();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} // namespace Nuclex.Support.Collections
|
||||
#region Apache License 2.0
|
||||
/*
|
||||
Nuclex .NET Framework
|
||||
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
#endregion // Apache License 2.0
|
||||
|
||||
using System;
|
||||
using System.Text;
|
||||
|
||||
namespace Nuclex.Support.Collections {
|
||||
|
||||
/// <summary>An pair of a priority and an item</summary>
|
||||
public struct PriorityItemPair<TPriority, TItem> {
|
||||
|
||||
/// <summary>Initializes a new priority / item pair</summary>
|
||||
/// <param name="priority">Priority of the item in the pair</param>
|
||||
/// <param name="item">Item to be stored in the pair</param>
|
||||
public PriorityItemPair(TPriority priority, TItem item) {
|
||||
this.Priority = priority;
|
||||
this.Item = item;
|
||||
}
|
||||
|
||||
/// <summary>Priority assigned to this priority / item pair</summary>
|
||||
public TPriority Priority;
|
||||
/// <summary>Item contained in this priority / item pair</summary>
|
||||
public TItem Item;
|
||||
|
||||
/// <summary>Converts the priority / item pair into a string</summary>
|
||||
/// <returns>A string describing the priority / item pair</returns>
|
||||
public override string ToString() {
|
||||
int length = 4;
|
||||
|
||||
// Convert the priority value into a string or use the empty string
|
||||
// constant if the ToString() overload returns null
|
||||
string priorityString = this.Priority.ToString();
|
||||
if(priorityString != null)
|
||||
length += priorityString.Length;
|
||||
else
|
||||
priorityString = string.Empty;
|
||||
|
||||
// Convert the item value into a string or use the empty string
|
||||
// constant if the ToString() overload returns null
|
||||
string itemString = this.Item.ToString();
|
||||
if(itemString != null)
|
||||
length += itemString.Length;
|
||||
else
|
||||
itemString = string.Empty;
|
||||
|
||||
// Concatenate priority and item into a single string
|
||||
StringBuilder builder = new StringBuilder(length);
|
||||
builder.Append('[');
|
||||
builder.Append(priorityString);
|
||||
builder.Append(", ");
|
||||
builder.Append(itemString);
|
||||
builder.Append(']');
|
||||
return builder.ToString();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} // namespace Nuclex.Support.Collections
|
||||
|
|
|
|||
|
|
@ -1,158 +1,157 @@
|
|||
#region CPL License
|
||||
/*
|
||||
Nuclex Framework
|
||||
Copyright (C) 2002-2017 Nuclex Development Labs
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the IBM Common Public License as
|
||||
published by the IBM Corporation; either version 1.0 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
IBM Common Public License for more details.
|
||||
|
||||
You should have received a copy of the IBM Common Public
|
||||
License along with this library
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
#if UNITTEST
|
||||
|
||||
using NUnit.Framework;
|
||||
|
||||
namespace Nuclex.Support.Collections {
|
||||
|
||||
/// <summary>Unit Test for the priority queue class</summary>
|
||||
[TestFixture]
|
||||
internal class PriorityQueueTest {
|
||||
|
||||
#region class FloatComparer
|
||||
|
||||
/// <summary>Comparer for two floating point values</summary>
|
||||
private class FloatComparer : IComparer<float> {
|
||||
|
||||
/// <summary>The default instance of this comparer</summary>
|
||||
public static readonly FloatComparer Default = new FloatComparer();
|
||||
|
||||
/// <summary>Compares two floating points against each other</summary>
|
||||
/// <param name="left">First float to compare</param>
|
||||
/// <param name="right">Second float to compare</param>
|
||||
/// <returns>The relationship of the two floats to each other</returns>
|
||||
public int Compare(float left, float right) {
|
||||
return Math.Sign(left - right);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endregion // class FloatComparer
|
||||
|
||||
/// <summary>Tests to ensure the count property is properly updated</summary>
|
||||
[Test]
|
||||
public void TestCount() {
|
||||
PriorityQueue<float> testQueue = new PriorityQueue<float>(FloatComparer.Default);
|
||||
|
||||
Assert.AreEqual(0, testQueue.Count);
|
||||
testQueue.Enqueue(12.34f);
|
||||
Assert.AreEqual(1, testQueue.Count);
|
||||
testQueue.Enqueue(56.78f);
|
||||
Assert.AreEqual(2, testQueue.Count);
|
||||
testQueue.Dequeue();
|
||||
Assert.AreEqual(1, testQueue.Count);
|
||||
testQueue.Enqueue(9.0f);
|
||||
Assert.AreEqual(2, testQueue.Count);
|
||||
testQueue.Clear();
|
||||
Assert.AreEqual(0, testQueue.Count);
|
||||
}
|
||||
|
||||
/// <summary>Tests to ensure that the priority collection actually sorts items</summary>
|
||||
[Test]
|
||||
public void TestOrdering() {
|
||||
PriorityQueue<float> testQueue = new PriorityQueue<float>(FloatComparer.Default);
|
||||
|
||||
testQueue.Enqueue(1.0f);
|
||||
testQueue.Enqueue(9.0f);
|
||||
testQueue.Enqueue(2.0f);
|
||||
testQueue.Enqueue(8.0f);
|
||||
testQueue.Enqueue(3.0f);
|
||||
testQueue.Enqueue(7.0f);
|
||||
testQueue.Enqueue(4.0f);
|
||||
testQueue.Enqueue(6.0f);
|
||||
testQueue.Enqueue(5.0f);
|
||||
|
||||
Assert.AreEqual(9.0f, testQueue.Dequeue());
|
||||
Assert.AreEqual(8.0f, testQueue.Dequeue());
|
||||
Assert.AreEqual(7.0f, testQueue.Dequeue());
|
||||
Assert.AreEqual(6.0f, testQueue.Dequeue());
|
||||
Assert.AreEqual(5.0f, testQueue.Dequeue());
|
||||
Assert.AreEqual(4.0f, testQueue.Dequeue());
|
||||
Assert.AreEqual(3.0f, testQueue.Dequeue());
|
||||
Assert.AreEqual(2.0f, testQueue.Dequeue());
|
||||
Assert.AreEqual(1.0f, testQueue.Dequeue());
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
/// <summary>
|
||||
/// Tests whether the priority queue's enumerators are invalidated when the queue's
|
||||
/// contents are modified
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestEnumeratorInvalidationOnModify() {
|
||||
PriorityQueue<int> testQueue = new PriorityQueue<int>();
|
||||
IEnumerator<int> testQueueEnumerator = testQueue.GetEnumerator();
|
||||
|
||||
testQueue.Enqueue(123);
|
||||
|
||||
Assert.Throws<InvalidOperationException>(
|
||||
delegate() { testQueueEnumerator.MoveNext(); }
|
||||
);
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that an exception is thrown when Peek() is called on an empty queue
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestPeekEmptyQueue() {
|
||||
PriorityQueue<int> testQueue = new PriorityQueue<int>();
|
||||
Assert.Throws<InvalidOperationException>(
|
||||
delegate() { testQueue.Peek(); }
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that an exception is thrown when Dequeue() is called on an empty queue
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestDequeueEmptyQueue() {
|
||||
PriorityQueue<int> testQueue = new PriorityQueue<int>();
|
||||
Assert.Throws<InvalidOperationException>(
|
||||
delegate() { testQueue.Dequeue(); }
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that the priority queue can handle large amounts of data
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestLargeQueue() {
|
||||
PriorityQueue<int> testQueue = new PriorityQueue<int>();
|
||||
List<int> testList = new List<int>();
|
||||
|
||||
for(int index = 0; index < 1000; ++index) {
|
||||
testQueue.Enqueue(index * 2);
|
||||
testList.Add(index * 2);
|
||||
}
|
||||
|
||||
CollectionAssert.AreEquivalent(testList, testQueue);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} // namespace Nuclex.Support.Collections
|
||||
|
||||
#endif // UNITTEST
|
||||
#region Apache License 2.0
|
||||
/*
|
||||
Nuclex .NET Framework
|
||||
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
#endregion // Apache License 2.0
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
#if UNITTEST
|
||||
|
||||
using NUnit.Framework;
|
||||
|
||||
namespace Nuclex.Support.Collections {
|
||||
|
||||
/// <summary>Unit Test for the priority queue class</summary>
|
||||
[TestFixture]
|
||||
internal class PriorityQueueTest {
|
||||
|
||||
#region class FloatComparer
|
||||
|
||||
/// <summary>Comparer for two floating point values</summary>
|
||||
private class FloatComparer : IComparer<float> {
|
||||
|
||||
/// <summary>The default instance of this comparer</summary>
|
||||
public static readonly FloatComparer Default = new FloatComparer();
|
||||
|
||||
/// <summary>Compares two floating points against each other</summary>
|
||||
/// <param name="left">First float to compare</param>
|
||||
/// <param name="right">Second float to compare</param>
|
||||
/// <returns>The relationship of the two floats to each other</returns>
|
||||
public int Compare(float left, float right) {
|
||||
return Math.Sign(left - right);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endregion // class FloatComparer
|
||||
|
||||
/// <summary>Tests to ensure the count property is properly updated</summary>
|
||||
[Test]
|
||||
public void TestCount() {
|
||||
PriorityQueue<float> testQueue = new PriorityQueue<float>(FloatComparer.Default);
|
||||
|
||||
Assert.AreEqual(0, testQueue.Count);
|
||||
testQueue.Enqueue(12.34f);
|
||||
Assert.AreEqual(1, testQueue.Count);
|
||||
testQueue.Enqueue(56.78f);
|
||||
Assert.AreEqual(2, testQueue.Count);
|
||||
testQueue.Dequeue();
|
||||
Assert.AreEqual(1, testQueue.Count);
|
||||
testQueue.Enqueue(9.0f);
|
||||
Assert.AreEqual(2, testQueue.Count);
|
||||
testQueue.Clear();
|
||||
Assert.AreEqual(0, testQueue.Count);
|
||||
}
|
||||
|
||||
/// <summary>Tests to ensure that the priority collection actually sorts items</summary>
|
||||
[Test]
|
||||
public void TestOrdering() {
|
||||
PriorityQueue<float> testQueue = new PriorityQueue<float>(FloatComparer.Default);
|
||||
|
||||
testQueue.Enqueue(1.0f);
|
||||
testQueue.Enqueue(9.0f);
|
||||
testQueue.Enqueue(2.0f);
|
||||
testQueue.Enqueue(8.0f);
|
||||
testQueue.Enqueue(3.0f);
|
||||
testQueue.Enqueue(7.0f);
|
||||
testQueue.Enqueue(4.0f);
|
||||
testQueue.Enqueue(6.0f);
|
||||
testQueue.Enqueue(5.0f);
|
||||
|
||||
Assert.AreEqual(9.0f, testQueue.Dequeue());
|
||||
Assert.AreEqual(8.0f, testQueue.Dequeue());
|
||||
Assert.AreEqual(7.0f, testQueue.Dequeue());
|
||||
Assert.AreEqual(6.0f, testQueue.Dequeue());
|
||||
Assert.AreEqual(5.0f, testQueue.Dequeue());
|
||||
Assert.AreEqual(4.0f, testQueue.Dequeue());
|
||||
Assert.AreEqual(3.0f, testQueue.Dequeue());
|
||||
Assert.AreEqual(2.0f, testQueue.Dequeue());
|
||||
Assert.AreEqual(1.0f, testQueue.Dequeue());
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
/// <summary>
|
||||
/// Tests whether the priority queue's enumerators are invalidated when the queue's
|
||||
/// contents are modified
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestEnumeratorInvalidationOnModify() {
|
||||
PriorityQueue<int> testQueue = new PriorityQueue<int>();
|
||||
IEnumerator<int> testQueueEnumerator = testQueue.GetEnumerator();
|
||||
|
||||
testQueue.Enqueue(123);
|
||||
|
||||
Assert.Throws<InvalidOperationException>(
|
||||
delegate() { testQueueEnumerator.MoveNext(); }
|
||||
);
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that an exception is thrown when Peek() is called on an empty queue
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestPeekEmptyQueue() {
|
||||
PriorityQueue<int> testQueue = new PriorityQueue<int>();
|
||||
Assert.Throws<InvalidOperationException>(
|
||||
delegate() { testQueue.Peek(); }
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that an exception is thrown when Dequeue() is called on an empty queue
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestDequeueEmptyQueue() {
|
||||
PriorityQueue<int> testQueue = new PriorityQueue<int>();
|
||||
Assert.Throws<InvalidOperationException>(
|
||||
delegate() { testQueue.Dequeue(); }
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that the priority queue can handle large amounts of data
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestLargeQueue() {
|
||||
PriorityQueue<int> testQueue = new PriorityQueue<int>();
|
||||
List<int> testList = new List<int>();
|
||||
|
||||
for(int index = 0; index < 1000; ++index) {
|
||||
testQueue.Enqueue(index * 2);
|
||||
testList.Add(index * 2);
|
||||
}
|
||||
|
||||
CollectionAssert.AreEquivalent(testList, testQueue);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} // namespace Nuclex.Support.Collections
|
||||
|
||||
#endif // UNITTEST
|
||||
|
|
|
|||
|
|
@ -1,285 +1,284 @@
|
|||
#region CPL License
|
||||
/*
|
||||
Nuclex Framework
|
||||
Copyright (C) 2002-2017 Nuclex Development Labs
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the IBM Common Public License as
|
||||
published by the IBM Corporation; either version 1.0 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
IBM Common Public License for more details.
|
||||
|
||||
You should have received a copy of the IBM Common Public
|
||||
License along with this library
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections;
|
||||
|
||||
namespace Nuclex.Support.Collections {
|
||||
|
||||
/// <summary>Queue that dequeues items in order of their priority</summary>
|
||||
public class PriorityQueue<TItem> : ICollection, IEnumerable<TItem> {
|
||||
|
||||
#region class Enumerator
|
||||
|
||||
/// <summary>Enumerates all items contained in a priority queue</summary>
|
||||
private class Enumerator : IEnumerator<TItem> {
|
||||
|
||||
/// <summary>Initializes a new priority queue enumerator</summary>
|
||||
/// <param name="priorityQueue">Priority queue to be enumerated</param>
|
||||
public Enumerator(PriorityQueue<TItem> priorityQueue) {
|
||||
this.priorityQueue = priorityQueue;
|
||||
Reset();
|
||||
}
|
||||
|
||||
/// <summary>Resets the enumerator to its initial state</summary>
|
||||
public void Reset() {
|
||||
this.index = -1;
|
||||
#if DEBUG
|
||||
this.expectedVersion = this.priorityQueue.version;
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>The current item being enumerated</summary>
|
||||
TItem IEnumerator<TItem>.Current {
|
||||
get {
|
||||
#if DEBUG
|
||||
checkVersion();
|
||||
#endif
|
||||
return this.priorityQueue.heap[index];
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Moves to the next item in the priority queue</summary>
|
||||
/// <returns>True if a next item was found, false if the end has been reached</returns>
|
||||
public bool MoveNext() {
|
||||
#if DEBUG
|
||||
checkVersion();
|
||||
#endif
|
||||
if(this.index + 1 == this.priorityQueue.count)
|
||||
return false;
|
||||
|
||||
++this.index;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>Releases all resources used by the enumerator</summary>
|
||||
public void Dispose() { }
|
||||
|
||||
#if DEBUG
|
||||
/// <summary>Ensures that the priority queue has not changed</summary>
|
||||
private void checkVersion() {
|
||||
if(this.expectedVersion != this.priorityQueue.version)
|
||||
throw new InvalidOperationException("Priority queue has been modified");
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>The current item being enumerated</summary>
|
||||
object IEnumerator.Current {
|
||||
get {
|
||||
#if DEBUG
|
||||
checkVersion();
|
||||
#endif
|
||||
return this.priorityQueue.heap[index];
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Index of the current item in the priority queue</summary>
|
||||
private int index;
|
||||
/// <summary>The priority queue whose items this instance enumerates</summary>
|
||||
private PriorityQueue<TItem> priorityQueue;
|
||||
#if DEBUG
|
||||
/// <summary>Expected version of the priority queue</summary>
|
||||
private int expectedVersion;
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
#endregion // class Enumerator
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new priority queue using IComparable for comparing items
|
||||
/// </summary>
|
||||
public PriorityQueue() : this(Comparer<TItem>.Default) { }
|
||||
|
||||
/// <summary>Initializes a new priority queue</summary>
|
||||
/// <param name="comparer">Comparer to use for ordering the items</param>
|
||||
public PriorityQueue(IComparer<TItem> comparer) {
|
||||
this.comparer = comparer;
|
||||
this.capacity = 15; // 15 is equal to 4 complete levels
|
||||
this.heap = new TItem[this.capacity];
|
||||
}
|
||||
|
||||
/// <summary>Returns the topmost item in the queue without dequeueing it</summary>
|
||||
/// <returns>The topmost item in the queue</returns>
|
||||
public TItem Peek() {
|
||||
if(this.count == 0) {
|
||||
throw new InvalidOperationException("No items queued");
|
||||
}
|
||||
|
||||
return this.heap[0];
|
||||
}
|
||||
|
||||
/// <summary>Takes the item with the highest priority off from the queue</summary>
|
||||
/// <returns>The item with the highest priority in the list</returns>
|
||||
/// <exception cref="InvalidOperationException">When the queue is empty</exception>
|
||||
public TItem Dequeue() {
|
||||
if(this.count == 0) {
|
||||
throw new InvalidOperationException("No items available to dequeue");
|
||||
}
|
||||
|
||||
TItem result = this.heap[0];
|
||||
--this.count;
|
||||
trickleDown(0, this.heap[this.count]);
|
||||
#if DEBUG
|
||||
++this.version;
|
||||
#endif
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>Puts an item into the priority queue</summary>
|
||||
/// <param name="item">Item to be queued</param>
|
||||
public void Enqueue(TItem item) {
|
||||
if(this.count == capacity)
|
||||
growHeap();
|
||||
|
||||
++this.count;
|
||||
bubbleUp(this.count - 1, item);
|
||||
#if DEBUG
|
||||
++this.version;
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>Removes all items from the priority queue</summary>
|
||||
public void Clear() {
|
||||
this.count = 0;
|
||||
#if DEBUG
|
||||
++this.version;
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
/// <summary>Total number of items in the priority queue</summary>
|
||||
public int Count {
|
||||
get { return this.count; }
|
||||
}
|
||||
|
||||
/// <summary>Copies the contents of the priority queue into an array</summary>
|
||||
/// <param name="array">Array to copy the priority queue into</param>
|
||||
/// <param name="index">Starting index for the destination array</param>
|
||||
public void CopyTo(Array array, int index) {
|
||||
Array.Copy(this.heap, 0, array, index, this.count);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Obtains an object that can be used to synchronize accesses to the priority queue
|
||||
/// from different threads
|
||||
/// </summary>
|
||||
public object SyncRoot {
|
||||
get { return this; }
|
||||
}
|
||||
|
||||
/// <summary>Whether operations performed on this priority queue are thread safe</summary>
|
||||
public bool IsSynchronized {
|
||||
get { return false; }
|
||||
}
|
||||
|
||||
/// <summary>Returns a typesafe enumerator for the priority queue</summary>
|
||||
/// <returns>A new enumerator for the priority queue</returns>
|
||||
public IEnumerator<TItem> GetEnumerator() {
|
||||
return new Enumerator(this);
|
||||
}
|
||||
|
||||
/// <summary>Moves an item upwards in the heap tree</summary>
|
||||
/// <param name="index">Index of the item to be moved</param>
|
||||
/// <param name="item">Item to be moved</param>
|
||||
private void bubbleUp(int index, TItem item) {
|
||||
int parent = getParent(index);
|
||||
|
||||
// Note: (index > 0) means there is a parent
|
||||
while((index > 0) && (this.comparer.Compare(this.heap[parent], item) < 0)) {
|
||||
this.heap[index] = this.heap[parent];
|
||||
index = parent;
|
||||
parent = getParent(index);
|
||||
}
|
||||
|
||||
this.heap[index] = item;
|
||||
}
|
||||
|
||||
/// <summary>Move the item downwards in the heap tree</summary>
|
||||
/// <param name="index">Index of the item to be moved</param>
|
||||
/// <param name="item">Item to be moved</param>
|
||||
private void trickleDown(int index, TItem item) {
|
||||
int child = getLeftChild(index);
|
||||
|
||||
while(child < this.count) {
|
||||
|
||||
bool needsToBeMoved =
|
||||
((child + 1) < this.count) &&
|
||||
(this.comparer.Compare(heap[child], this.heap[child + 1]) < 0);
|
||||
|
||||
if(needsToBeMoved)
|
||||
++child;
|
||||
|
||||
this.heap[index] = this.heap[child];
|
||||
index = child;
|
||||
child = getLeftChild(index);
|
||||
|
||||
}
|
||||
|
||||
bubbleUp(index, item);
|
||||
}
|
||||
|
||||
/// <summary>Obtains the left child item in the heap tree</summary>
|
||||
/// <param name="index">Index of the item whose left child to return</param>
|
||||
/// <returns>The left child item of the provided parent item</returns>
|
||||
private int getLeftChild(int index) {
|
||||
return (index * 2) + 1;
|
||||
}
|
||||
|
||||
/// <summary>Calculates the parent entry of the item on the heap</summary>
|
||||
/// <param name="index">Index of the item whose parent to calculate</param>
|
||||
/// <returns>The index of the parent to the specified item</returns>
|
||||
private int getParent(int index) {
|
||||
return (index - 1) / 2;
|
||||
}
|
||||
|
||||
/// <summary>Increases the size of the priority collection's heap</summary>
|
||||
private void growHeap() {
|
||||
this.capacity = (capacity * 2) + 1;
|
||||
|
||||
TItem[] newHeap = new TItem[this.capacity];
|
||||
Array.Copy(this.heap, 0, newHeap, 0, this.count);
|
||||
this.heap = newHeap;
|
||||
}
|
||||
|
||||
/// <summary>Returns an enumerator for the priority queue</summary>
|
||||
/// <returns>A new enumerator for the priority queue</returns>
|
||||
IEnumerator IEnumerable.GetEnumerator() {
|
||||
return new Enumerator(this);
|
||||
}
|
||||
|
||||
/// <summary>Comparer used to order the items in the priority queue</summary>
|
||||
private IComparer<TItem> comparer;
|
||||
/// <summary>Total number of items in the priority queue</summary>
|
||||
private int count;
|
||||
/// <summary>Available space in the priority queue</summary>
|
||||
private int capacity;
|
||||
/// <summary>Tree containing the items in the priority queue</summary>
|
||||
private TItem[] heap;
|
||||
#if DEBUG
|
||||
/// <summary>Incremented whenever the priority queue is modified</summary>
|
||||
private int version;
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
} // namespace Nuclex.Support.Collections
|
||||
#region Apache License 2.0
|
||||
/*
|
||||
Nuclex .NET Framework
|
||||
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
#endregion // Apache License 2.0
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections;
|
||||
|
||||
namespace Nuclex.Support.Collections {
|
||||
|
||||
/// <summary>Queue that dequeues items in order of their priority</summary>
|
||||
public class PriorityQueue<TItem> : ICollection, IEnumerable<TItem> {
|
||||
|
||||
#region class Enumerator
|
||||
|
||||
/// <summary>Enumerates all items contained in a priority queue</summary>
|
||||
private class Enumerator : IEnumerator<TItem> {
|
||||
|
||||
/// <summary>Initializes a new priority queue enumerator</summary>
|
||||
/// <param name="priorityQueue">Priority queue to be enumerated</param>
|
||||
public Enumerator(PriorityQueue<TItem> priorityQueue) {
|
||||
this.priorityQueue = priorityQueue;
|
||||
Reset();
|
||||
}
|
||||
|
||||
/// <summary>Resets the enumerator to its initial state</summary>
|
||||
public void Reset() {
|
||||
this.index = -1;
|
||||
#if DEBUG
|
||||
this.expectedVersion = this.priorityQueue.version;
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>The current item being enumerated</summary>
|
||||
TItem IEnumerator<TItem>.Current {
|
||||
get {
|
||||
#if DEBUG
|
||||
checkVersion();
|
||||
#endif
|
||||
return this.priorityQueue.heap[index];
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Moves to the next item in the priority queue</summary>
|
||||
/// <returns>True if a next item was found, false if the end has been reached</returns>
|
||||
public bool MoveNext() {
|
||||
#if DEBUG
|
||||
checkVersion();
|
||||
#endif
|
||||
if(this.index + 1 == this.priorityQueue.count)
|
||||
return false;
|
||||
|
||||
++this.index;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>Releases all resources used by the enumerator</summary>
|
||||
public void Dispose() { }
|
||||
|
||||
#if DEBUG
|
||||
/// <summary>Ensures that the priority queue has not changed</summary>
|
||||
private void checkVersion() {
|
||||
if(this.expectedVersion != this.priorityQueue.version)
|
||||
throw new InvalidOperationException("Priority queue has been modified");
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>The current item being enumerated</summary>
|
||||
object IEnumerator.Current {
|
||||
get {
|
||||
#if DEBUG
|
||||
checkVersion();
|
||||
#endif
|
||||
return this.priorityQueue.heap[index];
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Index of the current item in the priority queue</summary>
|
||||
private int index;
|
||||
/// <summary>The priority queue whose items this instance enumerates</summary>
|
||||
private PriorityQueue<TItem> priorityQueue;
|
||||
#if DEBUG
|
||||
/// <summary>Expected version of the priority queue</summary>
|
||||
private int expectedVersion;
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
#endregion // class Enumerator
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new priority queue using IComparable for comparing items
|
||||
/// </summary>
|
||||
public PriorityQueue() : this(Comparer<TItem>.Default) { }
|
||||
|
||||
/// <summary>Initializes a new priority queue</summary>
|
||||
/// <param name="comparer">Comparer to use for ordering the items</param>
|
||||
public PriorityQueue(IComparer<TItem> comparer) {
|
||||
this.comparer = comparer;
|
||||
this.capacity = 15; // 15 is equal to 4 complete levels
|
||||
this.heap = new TItem[this.capacity];
|
||||
}
|
||||
|
||||
/// <summary>Returns the topmost item in the queue without dequeueing it</summary>
|
||||
/// <returns>The topmost item in the queue</returns>
|
||||
public TItem Peek() {
|
||||
if(this.count == 0) {
|
||||
throw new InvalidOperationException("No items queued");
|
||||
}
|
||||
|
||||
return this.heap[0];
|
||||
}
|
||||
|
||||
/// <summary>Takes the item with the highest priority off from the queue</summary>
|
||||
/// <returns>The item with the highest priority in the list</returns>
|
||||
/// <exception cref="InvalidOperationException">When the queue is empty</exception>
|
||||
public TItem Dequeue() {
|
||||
if(this.count == 0) {
|
||||
throw new InvalidOperationException("No items available to dequeue");
|
||||
}
|
||||
|
||||
TItem result = this.heap[0];
|
||||
--this.count;
|
||||
trickleDown(0, this.heap[this.count]);
|
||||
#if DEBUG
|
||||
++this.version;
|
||||
#endif
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>Puts an item into the priority queue</summary>
|
||||
/// <param name="item">Item to be queued</param>
|
||||
public void Enqueue(TItem item) {
|
||||
if(this.count == capacity)
|
||||
growHeap();
|
||||
|
||||
++this.count;
|
||||
bubbleUp(this.count - 1, item);
|
||||
#if DEBUG
|
||||
++this.version;
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>Removes all items from the priority queue</summary>
|
||||
public void Clear() {
|
||||
this.count = 0;
|
||||
#if DEBUG
|
||||
++this.version;
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
/// <summary>Total number of items in the priority queue</summary>
|
||||
public int Count {
|
||||
get { return this.count; }
|
||||
}
|
||||
|
||||
/// <summary>Copies the contents of the priority queue into an array</summary>
|
||||
/// <param name="array">Array to copy the priority queue into</param>
|
||||
/// <param name="index">Starting index for the destination array</param>
|
||||
public void CopyTo(Array array, int index) {
|
||||
Array.Copy(this.heap, 0, array, index, this.count);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Obtains an object that can be used to synchronize accesses to the priority queue
|
||||
/// from different threads
|
||||
/// </summary>
|
||||
public object SyncRoot {
|
||||
get { return this; }
|
||||
}
|
||||
|
||||
/// <summary>Whether operations performed on this priority queue are thread safe</summary>
|
||||
public bool IsSynchronized {
|
||||
get { return false; }
|
||||
}
|
||||
|
||||
/// <summary>Returns a typesafe enumerator for the priority queue</summary>
|
||||
/// <returns>A new enumerator for the priority queue</returns>
|
||||
public IEnumerator<TItem> GetEnumerator() {
|
||||
return new Enumerator(this);
|
||||
}
|
||||
|
||||
/// <summary>Moves an item upwards in the heap tree</summary>
|
||||
/// <param name="index">Index of the item to be moved</param>
|
||||
/// <param name="item">Item to be moved</param>
|
||||
private void bubbleUp(int index, TItem item) {
|
||||
int parent = getParent(index);
|
||||
|
||||
// Note: (index > 0) means there is a parent
|
||||
while((index > 0) && (this.comparer.Compare(this.heap[parent], item) < 0)) {
|
||||
this.heap[index] = this.heap[parent];
|
||||
index = parent;
|
||||
parent = getParent(index);
|
||||
}
|
||||
|
||||
this.heap[index] = item;
|
||||
}
|
||||
|
||||
/// <summary>Move the item downwards in the heap tree</summary>
|
||||
/// <param name="index">Index of the item to be moved</param>
|
||||
/// <param name="item">Item to be moved</param>
|
||||
private void trickleDown(int index, TItem item) {
|
||||
int child = getLeftChild(index);
|
||||
|
||||
while(child < this.count) {
|
||||
|
||||
bool needsToBeMoved =
|
||||
((child + 1) < this.count) &&
|
||||
(this.comparer.Compare(heap[child], this.heap[child + 1]) < 0);
|
||||
|
||||
if(needsToBeMoved)
|
||||
++child;
|
||||
|
||||
this.heap[index] = this.heap[child];
|
||||
index = child;
|
||||
child = getLeftChild(index);
|
||||
|
||||
}
|
||||
|
||||
bubbleUp(index, item);
|
||||
}
|
||||
|
||||
/// <summary>Obtains the left child item in the heap tree</summary>
|
||||
/// <param name="index">Index of the item whose left child to return</param>
|
||||
/// <returns>The left child item of the provided parent item</returns>
|
||||
private int getLeftChild(int index) {
|
||||
return (index * 2) + 1;
|
||||
}
|
||||
|
||||
/// <summary>Calculates the parent entry of the item on the heap</summary>
|
||||
/// <param name="index">Index of the item whose parent to calculate</param>
|
||||
/// <returns>The index of the parent to the specified item</returns>
|
||||
private int getParent(int index) {
|
||||
return (index - 1) / 2;
|
||||
}
|
||||
|
||||
/// <summary>Increases the size of the priority collection's heap</summary>
|
||||
private void growHeap() {
|
||||
this.capacity = (capacity * 2) + 1;
|
||||
|
||||
TItem[] newHeap = new TItem[this.capacity];
|
||||
Array.Copy(this.heap, 0, newHeap, 0, this.count);
|
||||
this.heap = newHeap;
|
||||
}
|
||||
|
||||
/// <summary>Returns an enumerator for the priority queue</summary>
|
||||
/// <returns>A new enumerator for the priority queue</returns>
|
||||
IEnumerator IEnumerable.GetEnumerator() {
|
||||
return new Enumerator(this);
|
||||
}
|
||||
|
||||
/// <summary>Comparer used to order the items in the priority queue</summary>
|
||||
private IComparer<TItem> comparer;
|
||||
/// <summary>Total number of items in the priority queue</summary>
|
||||
private int count;
|
||||
/// <summary>Available space in the priority queue</summary>
|
||||
private int capacity;
|
||||
/// <summary>Tree containing the items in the priority queue</summary>
|
||||
private TItem[] heap;
|
||||
#if DEBUG
|
||||
/// <summary>Incremented whenever the priority queue is modified</summary>
|
||||
private int version;
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
} // namespace Nuclex.Support.Collections
|
||||
|
|
|
|||
|
|
@ -1,163 +1,162 @@
|
|||
#region CPL License
|
||||
/*
|
||||
Nuclex Framework
|
||||
Copyright (C) 2002-2017 Nuclex Development Labs
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the IBM Common Public License as
|
||||
published by the IBM Corporation; either version 1.0 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
IBM Common Public License for more details.
|
||||
|
||||
You should have received a copy of the IBM Common Public
|
||||
License along with this library
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
#if UNITTEST
|
||||
|
||||
using NUnit.Framework;
|
||||
|
||||
namespace Nuclex.Support.Collections {
|
||||
|
||||
/// <summary>Unit Test for the read only collection wrapper</summary>
|
||||
[TestFixture]
|
||||
internal class ReadOnlyCollectionTest {
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that the copy constructor of the read only collection works
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestCopyConstructor() {
|
||||
int[] integers = new int[] { 12, 34, 56, 78 };
|
||||
ReadOnlyCollection<int> testCollection = new ReadOnlyCollection<int>(integers);
|
||||
|
||||
CollectionAssert.AreEqual(integers, testCollection);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that the IsReadOnly property returns true</summary>
|
||||
[Test]
|
||||
public void TestIsReadOnly() {
|
||||
ReadOnlyCollection<int> testCollection = new ReadOnlyCollection<int>(new int[0]);
|
||||
|
||||
Assert.IsTrue(testCollection.IsReadOnly);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that the CopyTo() of the read only collection works
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestCopyToArray() {
|
||||
int[] inputIntegers = new int[] { 12, 34, 56, 78 };
|
||||
ReadOnlyCollection<int> testCollection = new ReadOnlyCollection<int>(inputIntegers);
|
||||
|
||||
int[] outputIntegers = new int[testCollection.Count];
|
||||
testCollection.CopyTo(outputIntegers, 0);
|
||||
|
||||
CollectionAssert.AreEqual(inputIntegers, outputIntegers);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether the Contains() method of the read only collection is able to
|
||||
/// determine if the collection contains an item
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestContains() {
|
||||
int[] integers = new int[] { 1234, 6789 };
|
||||
ReadOnlyCollection<int> testCollection = new ReadOnlyCollection<int>(integers);
|
||||
|
||||
Assert.IsTrue(testCollection.Contains(1234));
|
||||
Assert.IsFalse(testCollection.Contains(4321));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ensures that the Add() method of the read only collection throws an exception
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestThrowOnAdd() {
|
||||
ReadOnlyCollection<int> testCollection = new ReadOnlyCollection<int>(new int[0]);
|
||||
Assert.Throws<NotSupportedException>(
|
||||
delegate() { (testCollection as ICollection<int>).Add(123); }
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ensures that the Remove() method of the read only collection throws an exception
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestThrowOnRemove() {
|
||||
ReadOnlyCollection<int> testCollection = new ReadOnlyCollection<int>(new int[0]);
|
||||
Assert.Throws<NotSupportedException>(
|
||||
delegate() { (testCollection as ICollection<int>).Remove(123); }
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ensures that the Clear() method of the read only collection throws an exception
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestThrowOnClear() {
|
||||
ReadOnlyCollection<int> testCollection = new ReadOnlyCollection<int>(new int[0]);
|
||||
Assert.Throws<NotSupportedException>(
|
||||
delegate() { (testCollection as ICollection<int>).Clear(); }
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests whether the typesafe enumerator of the read only collection is working
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestTypesafeEnumerator() {
|
||||
int[] inputIntegers = new int[] { 12, 34, 56, 78 };
|
||||
ReadOnlyCollection<int> testCollection = new ReadOnlyCollection<int>(inputIntegers);
|
||||
|
||||
List<int> outputIntegers = new List<int>();
|
||||
foreach(int value in testCollection) {
|
||||
outputIntegers.Add(value);
|
||||
}
|
||||
|
||||
CollectionAssert.AreEqual(inputIntegers, outputIntegers);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that the CopyTo() of the read only collection works if invoked via
|
||||
/// the ICollection interface
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestCopyToArrayViaICollection() {
|
||||
int[] inputIntegers = new int[] { 12, 34, 56, 78 };
|
||||
ReadOnlyCollection<int> testCollection = new ReadOnlyCollection<int>(inputIntegers);
|
||||
|
||||
int[] outputIntegers = new int[testCollection.Count];
|
||||
(testCollection as ICollection).CopyTo(outputIntegers, 0);
|
||||
|
||||
CollectionAssert.AreEqual(inputIntegers, outputIntegers);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that the IsSynchronized property and the SyncRoot property are working
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestSynchronization() {
|
||||
ReadOnlyCollection<int> testCollection = new ReadOnlyCollection<int>(new int[0]);
|
||||
|
||||
if(!(testCollection as ICollection).IsSynchronized) {
|
||||
lock((testCollection as ICollection).SyncRoot) {
|
||||
Assert.AreEqual(0, testCollection.Count);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} // namespace Nuclex.Support.Collections
|
||||
|
||||
#endif // UNITTEST
|
||||
#region Apache License 2.0
|
||||
/*
|
||||
Nuclex .NET Framework
|
||||
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
#endregion // Apache License 2.0
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
#if UNITTEST
|
||||
|
||||
using NUnit.Framework;
|
||||
|
||||
namespace Nuclex.Support.Collections {
|
||||
|
||||
/// <summary>Unit Test for the read only collection wrapper</summary>
|
||||
[TestFixture]
|
||||
internal class ReadOnlyCollectionTest {
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that the copy constructor of the read only collection works
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestCopyConstructor() {
|
||||
int[] integers = new int[] { 12, 34, 56, 78 };
|
||||
ReadOnlyCollection<int> testCollection = new ReadOnlyCollection<int>(integers);
|
||||
|
||||
CollectionAssert.AreEqual(integers, testCollection);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that the IsReadOnly property returns true</summary>
|
||||
[Test]
|
||||
public void TestIsReadOnly() {
|
||||
ReadOnlyCollection<int> testCollection = new ReadOnlyCollection<int>(new int[0]);
|
||||
|
||||
Assert.IsTrue(testCollection.IsReadOnly);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that the CopyTo() of the read only collection works
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestCopyToArray() {
|
||||
int[] inputIntegers = new int[] { 12, 34, 56, 78 };
|
||||
ReadOnlyCollection<int> testCollection = new ReadOnlyCollection<int>(inputIntegers);
|
||||
|
||||
int[] outputIntegers = new int[testCollection.Count];
|
||||
testCollection.CopyTo(outputIntegers, 0);
|
||||
|
||||
CollectionAssert.AreEqual(inputIntegers, outputIntegers);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether the Contains() method of the read only collection is able to
|
||||
/// determine if the collection contains an item
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestContains() {
|
||||
int[] integers = new int[] { 1234, 6789 };
|
||||
ReadOnlyCollection<int> testCollection = new ReadOnlyCollection<int>(integers);
|
||||
|
||||
Assert.IsTrue(testCollection.Contains(1234));
|
||||
Assert.IsFalse(testCollection.Contains(4321));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ensures that the Add() method of the read only collection throws an exception
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestThrowOnAdd() {
|
||||
ReadOnlyCollection<int> testCollection = new ReadOnlyCollection<int>(new int[0]);
|
||||
Assert.Throws<NotSupportedException>(
|
||||
delegate() { (testCollection as ICollection<int>).Add(123); }
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ensures that the Remove() method of the read only collection throws an exception
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestThrowOnRemove() {
|
||||
ReadOnlyCollection<int> testCollection = new ReadOnlyCollection<int>(new int[0]);
|
||||
Assert.Throws<NotSupportedException>(
|
||||
delegate() { (testCollection as ICollection<int>).Remove(123); }
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ensures that the Clear() method of the read only collection throws an exception
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestThrowOnClear() {
|
||||
ReadOnlyCollection<int> testCollection = new ReadOnlyCollection<int>(new int[0]);
|
||||
Assert.Throws<NotSupportedException>(
|
||||
delegate() { (testCollection as ICollection<int>).Clear(); }
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests whether the typesafe enumerator of the read only collection is working
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestTypesafeEnumerator() {
|
||||
int[] inputIntegers = new int[] { 12, 34, 56, 78 };
|
||||
ReadOnlyCollection<int> testCollection = new ReadOnlyCollection<int>(inputIntegers);
|
||||
|
||||
List<int> outputIntegers = new List<int>();
|
||||
foreach(int value in testCollection) {
|
||||
outputIntegers.Add(value);
|
||||
}
|
||||
|
||||
CollectionAssert.AreEqual(inputIntegers, outputIntegers);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that the CopyTo() of the read only collection works if invoked via
|
||||
/// the ICollection interface
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestCopyToArrayViaICollection() {
|
||||
int[] inputIntegers = new int[] { 12, 34, 56, 78 };
|
||||
ReadOnlyCollection<int> testCollection = new ReadOnlyCollection<int>(inputIntegers);
|
||||
|
||||
int[] outputIntegers = new int[testCollection.Count];
|
||||
(testCollection as ICollection).CopyTo(outputIntegers, 0);
|
||||
|
||||
CollectionAssert.AreEqual(inputIntegers, outputIntegers);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that the IsSynchronized property and the SyncRoot property are working
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestSynchronization() {
|
||||
ReadOnlyCollection<int> testCollection = new ReadOnlyCollection<int>(new int[0]);
|
||||
|
||||
if(!(testCollection as ICollection).IsSynchronized) {
|
||||
lock((testCollection as ICollection).SyncRoot) {
|
||||
Assert.AreEqual(0, testCollection.Count);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} // namespace Nuclex.Support.Collections
|
||||
|
||||
#endif // UNITTEST
|
||||
|
|
|
|||
|
|
@ -1,140 +1,139 @@
|
|||
#region CPL License
|
||||
/*
|
||||
Nuclex Framework
|
||||
Copyright (C) 2002-2017 Nuclex Development Labs
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the IBM Common Public License as
|
||||
published by the IBM Corporation; either version 1.0 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
IBM Common Public License for more details.
|
||||
|
||||
You should have received a copy of the IBM Common Public
|
||||
License along with this library
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Nuclex.Support.Collections {
|
||||
|
||||
/// <summary>Wraps a Collection and prevents users from modifying it</summary>
|
||||
/// <typeparam name="TItem">Type of items to manage in the Collection</typeparam>
|
||||
public class ReadOnlyCollection<TItem> :
|
||||
ICollection<TItem>,
|
||||
ICollection {
|
||||
|
||||
/// <summary>Initializes a new read-only Collection wrapper</summary>
|
||||
/// <param name="collection">Collection that will be wrapped</param>
|
||||
public ReadOnlyCollection(ICollection<TItem> collection) {
|
||||
this.typedCollection = collection;
|
||||
this.objectCollection = (collection as ICollection);
|
||||
}
|
||||
|
||||
/// <summary>Determines whether the List contains the specified item</summary>
|
||||
/// <param name="item">Item that will be checked for</param>
|
||||
/// <returns>True if the specified item is contained in the List</returns>
|
||||
public bool Contains(TItem item) {
|
||||
return this.typedCollection.Contains(item);
|
||||
}
|
||||
|
||||
/// <summary>Copies the contents of the List into an array</summary>
|
||||
/// <param name="array">Array the List will be copied into</param>
|
||||
/// <param name="arrayIndex">
|
||||
/// Starting index at which to begin filling the destination array
|
||||
/// </param>
|
||||
public void CopyTo(TItem[] array, int arrayIndex) {
|
||||
this.typedCollection.CopyTo(array, arrayIndex);
|
||||
}
|
||||
|
||||
/// <summary>The number of items current contained in the List</summary>
|
||||
public int Count {
|
||||
get { return this.typedCollection.Count; }
|
||||
}
|
||||
|
||||
/// <summary>Whether the List is write-protected</summary>
|
||||
public bool IsReadOnly {
|
||||
get { return true; }
|
||||
}
|
||||
|
||||
/// <summary>Returns a new enumerator over the contents of the List</summary>
|
||||
/// <returns>The new List contents enumerator</returns>
|
||||
public IEnumerator<TItem> GetEnumerator() {
|
||||
return this.typedCollection.GetEnumerator();
|
||||
}
|
||||
|
||||
#region ICollection<> implementation
|
||||
|
||||
/// <summary>Adds an item to the end of the List</summary>
|
||||
/// <param name="item">Item that will be added to the List</param>
|
||||
void ICollection<TItem>.Add(TItem item) {
|
||||
throw new NotSupportedException(
|
||||
"Adding items is not supported by the read-only List"
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>Removes all items from the List</summary>
|
||||
void ICollection<TItem>.Clear() {
|
||||
throw new NotSupportedException(
|
||||
"Clearing is not supported by the read-only List"
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>Removes the specified item from the List</summary>
|
||||
/// <param name="item">Item that will be removed from the List</param>
|
||||
/// <returns>True of the specified item was found in the List and removed</returns>
|
||||
bool ICollection<TItem>.Remove(TItem item) {
|
||||
throw new NotSupportedException(
|
||||
"Removing items is not supported by the read-only List"
|
||||
);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IEnumerable implementation
|
||||
|
||||
/// <summary>Returns a new enumerator over the contents of the List</summary>
|
||||
/// <returns>The new List contents enumerator</returns>
|
||||
IEnumerator IEnumerable.GetEnumerator() {
|
||||
return this.objectCollection.GetEnumerator();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region ICollection implementation
|
||||
|
||||
/// <summary>Copies the contents of the List into an array</summary>
|
||||
/// <param name="array">Array the List will be copied into</param>
|
||||
/// <param name="index">
|
||||
/// Starting index at which to begin filling the destination array
|
||||
/// </param>
|
||||
void ICollection.CopyTo(Array array, int index) {
|
||||
this.objectCollection.CopyTo(array, index);
|
||||
}
|
||||
|
||||
/// <summary>Whether the List is synchronized for multi-threaded usage</summary>
|
||||
bool ICollection.IsSynchronized {
|
||||
get { return this.objectCollection.IsSynchronized; }
|
||||
}
|
||||
|
||||
/// <summary>Synchronization root on which the List locks</summary>
|
||||
object ICollection.SyncRoot {
|
||||
get { return this.objectCollection.SyncRoot; }
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>The wrapped Collection under its type-safe interface</summary>
|
||||
private ICollection<TItem> typedCollection;
|
||||
/// <summary>The wrapped Collection under its object interface</summary>
|
||||
private ICollection objectCollection;
|
||||
|
||||
}
|
||||
|
||||
} // namespace Nuclex.Support.Collections
|
||||
#region Apache License 2.0
|
||||
/*
|
||||
Nuclex .NET Framework
|
||||
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
#endregion // Apache License 2.0
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Nuclex.Support.Collections {
|
||||
|
||||
/// <summary>Wraps a Collection and prevents users from modifying it</summary>
|
||||
/// <typeparam name="TItem">Type of items to manage in the Collection</typeparam>
|
||||
public class ReadOnlyCollection<TItem> :
|
||||
ICollection<TItem>,
|
||||
ICollection {
|
||||
|
||||
/// <summary>Initializes a new read-only Collection wrapper</summary>
|
||||
/// <param name="collection">Collection that will be wrapped</param>
|
||||
public ReadOnlyCollection(ICollection<TItem> collection) {
|
||||
this.typedCollection = collection;
|
||||
this.objectCollection = (collection as ICollection);
|
||||
}
|
||||
|
||||
/// <summary>Determines whether the List contains the specified item</summary>
|
||||
/// <param name="item">Item that will be checked for</param>
|
||||
/// <returns>True if the specified item is contained in the List</returns>
|
||||
public bool Contains(TItem item) {
|
||||
return this.typedCollection.Contains(item);
|
||||
}
|
||||
|
||||
/// <summary>Copies the contents of the List into an array</summary>
|
||||
/// <param name="array">Array the List will be copied into</param>
|
||||
/// <param name="arrayIndex">
|
||||
/// Starting index at which to begin filling the destination array
|
||||
/// </param>
|
||||
public void CopyTo(TItem[] array, int arrayIndex) {
|
||||
this.typedCollection.CopyTo(array, arrayIndex);
|
||||
}
|
||||
|
||||
/// <summary>The number of items current contained in the List</summary>
|
||||
public int Count {
|
||||
get { return this.typedCollection.Count; }
|
||||
}
|
||||
|
||||
/// <summary>Whether the List is write-protected</summary>
|
||||
public bool IsReadOnly {
|
||||
get { return true; }
|
||||
}
|
||||
|
||||
/// <summary>Returns a new enumerator over the contents of the List</summary>
|
||||
/// <returns>The new List contents enumerator</returns>
|
||||
public IEnumerator<TItem> GetEnumerator() {
|
||||
return this.typedCollection.GetEnumerator();
|
||||
}
|
||||
|
||||
#region ICollection<> implementation
|
||||
|
||||
/// <summary>Adds an item to the end of the List</summary>
|
||||
/// <param name="item">Item that will be added to the List</param>
|
||||
void ICollection<TItem>.Add(TItem item) {
|
||||
throw new NotSupportedException(
|
||||
"Adding items is not supported by the read-only List"
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>Removes all items from the List</summary>
|
||||
void ICollection<TItem>.Clear() {
|
||||
throw new NotSupportedException(
|
||||
"Clearing is not supported by the read-only List"
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>Removes the specified item from the List</summary>
|
||||
/// <param name="item">Item that will be removed from the List</param>
|
||||
/// <returns>True of the specified item was found in the List and removed</returns>
|
||||
bool ICollection<TItem>.Remove(TItem item) {
|
||||
throw new NotSupportedException(
|
||||
"Removing items is not supported by the read-only List"
|
||||
);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IEnumerable implementation
|
||||
|
||||
/// <summary>Returns a new enumerator over the contents of the List</summary>
|
||||
/// <returns>The new List contents enumerator</returns>
|
||||
IEnumerator IEnumerable.GetEnumerator() {
|
||||
return this.objectCollection.GetEnumerator();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region ICollection implementation
|
||||
|
||||
/// <summary>Copies the contents of the List into an array</summary>
|
||||
/// <param name="array">Array the List will be copied into</param>
|
||||
/// <param name="index">
|
||||
/// Starting index at which to begin filling the destination array
|
||||
/// </param>
|
||||
void ICollection.CopyTo(Array array, int index) {
|
||||
this.objectCollection.CopyTo(array, index);
|
||||
}
|
||||
|
||||
/// <summary>Whether the List is synchronized for multi-threaded usage</summary>
|
||||
bool ICollection.IsSynchronized {
|
||||
get { return this.objectCollection.IsSynchronized; }
|
||||
}
|
||||
|
||||
/// <summary>Synchronization root on which the List locks</summary>
|
||||
object ICollection.SyncRoot {
|
||||
get { return this.objectCollection.SyncRoot; }
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>The wrapped Collection under its type-safe interface</summary>
|
||||
private ICollection<TItem> typedCollection;
|
||||
/// <summary>The wrapped Collection under its object interface</summary>
|
||||
private ICollection objectCollection;
|
||||
|
||||
}
|
||||
|
||||
} // namespace Nuclex.Support.Collections
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -1,407 +1,406 @@
|
|||
#region CPL License
|
||||
/*
|
||||
Nuclex Framework
|
||||
Copyright (C) 2002-2017 Nuclex Development Labs
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the IBM Common Public License as
|
||||
published by the IBM Corporation; either version 1.0 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
IBM Common Public License for more details.
|
||||
|
||||
You should have received a copy of the IBM Common Public
|
||||
License along with this library
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections;
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace Nuclex.Support.Collections {
|
||||
|
||||
/// <summary>Wraps a dictionary and prevents users from modifying it</summary>
|
||||
/// <typeparam name="KeyType">Type of the keys used in the dictionary</typeparam>
|
||||
/// <typeparam name="ValueType">Type of the values used in the dictionary</typeparam>
|
||||
#if !NO_SERIALIZATION
|
||||
[Serializable]
|
||||
#endif
|
||||
public class ReadOnlyDictionary<KeyType, ValueType> :
|
||||
#if !NO_SERIALIZATION
|
||||
ISerializable,
|
||||
IDeserializationCallback,
|
||||
#endif
|
||||
IDictionary,
|
||||
IDictionary<KeyType, ValueType> {
|
||||
|
||||
#if !NO_SERIALIZATION
|
||||
|
||||
#region class SerializedDictionary
|
||||
|
||||
/// <summary>
|
||||
/// Dictionary wrapped used to reconstruct a serialized read only dictionary
|
||||
/// </summary>
|
||||
private class SerializedDictionary : Dictionary<KeyType, ValueType> {
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the System.WeakReference class, using deserialized
|
||||
/// data from the specified serialization and stream objects.
|
||||
/// </summary>
|
||||
/// <param name="info">
|
||||
/// An object that holds all the data needed to serialize or deserialize the
|
||||
/// current System.WeakReference object.
|
||||
/// </param>
|
||||
/// <param name="context">
|
||||
/// (Reserved) Describes the source and destination of the serialized stream
|
||||
/// specified by info.
|
||||
/// </param>
|
||||
/// <exception cref="System.ArgumentNullException">
|
||||
/// The info parameter is null.
|
||||
/// </exception>
|
||||
public SerializedDictionary(SerializationInfo info, StreamingContext context) :
|
||||
base(info, context) { }
|
||||
|
||||
}
|
||||
|
||||
#endregion // class SerializedDictionary
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the System.WeakReference class, using deserialized
|
||||
/// data from the specified serialization and stream objects.
|
||||
/// </summary>
|
||||
/// <param name="info">
|
||||
/// An object that holds all the data needed to serialize or deserialize the
|
||||
/// current System.WeakReference object.
|
||||
/// </param>
|
||||
/// <param name="context">
|
||||
/// (Reserved) Describes the source and destination of the serialized stream
|
||||
/// specified by info.
|
||||
/// </param>
|
||||
/// <exception cref="System.ArgumentNullException">
|
||||
/// The info parameter is null.
|
||||
/// </exception>
|
||||
protected ReadOnlyDictionary(SerializationInfo info, StreamingContext context) :
|
||||
this(new SerializedDictionary(info, context)) { }
|
||||
|
||||
#endif // !NO_SERIALIZATION
|
||||
|
||||
/// <summary>Initializes a new read-only dictionary wrapper</summary>
|
||||
/// <param name="dictionary">Dictionary that will be wrapped</param>
|
||||
public ReadOnlyDictionary(IDictionary<KeyType, ValueType> dictionary) {
|
||||
this.typedDictionary = dictionary;
|
||||
this.objectDictionary = (this.typedDictionary as IDictionary);
|
||||
}
|
||||
|
||||
/// <summary>Whether the directory is write-protected</summary>
|
||||
public bool IsReadOnly {
|
||||
get { return true; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified KeyValuePair is contained in the Dictionary
|
||||
/// </summary>
|
||||
/// <param name="item">KeyValuePair that will be checked for</param>
|
||||
/// <returns>True if the provided KeyValuePair was contained in the Dictionary</returns>
|
||||
public bool Contains(KeyValuePair<KeyType, ValueType> item) {
|
||||
return this.typedDictionary.Contains(item);
|
||||
}
|
||||
|
||||
/// <summary>Determines whether the Dictionary contains the specified key</summary>
|
||||
/// <param name="key">Key that will be checked for</param>
|
||||
/// <returns>
|
||||
/// True if an entry with the specified key was contained in the Dictionary
|
||||
/// </returns>
|
||||
public bool ContainsKey(KeyType key) {
|
||||
return this.typedDictionary.ContainsKey(key);
|
||||
}
|
||||
|
||||
/// <summary>Copies the contents of the Dictionary into an array</summary>
|
||||
/// <param name="array">Array the Dictionary will be copied into</param>
|
||||
/// <param name="arrayIndex">
|
||||
/// Starting index at which to begin filling the destination array
|
||||
/// </param>
|
||||
public void CopyTo(KeyValuePair<KeyType, ValueType>[] array, int arrayIndex) {
|
||||
this.typedDictionary.CopyTo(array, arrayIndex);
|
||||
}
|
||||
|
||||
/// <summary>Number of elements contained in the Dictionary</summary>
|
||||
public int Count {
|
||||
get { return this.typedDictionary.Count; }
|
||||
}
|
||||
|
||||
/// <summary>Creates a new enumerator for the Dictionary</summary>
|
||||
/// <returns>The new Dictionary enumerator</returns>
|
||||
public IEnumerator<KeyValuePair<KeyType, ValueType>> GetEnumerator() {
|
||||
return this.typedDictionary.GetEnumerator();
|
||||
}
|
||||
|
||||
/// <summary>Collection of all keys contained in the Dictionary</summary>
|
||||
public ICollection<KeyType> Keys {
|
||||
get {
|
||||
if(this.readonlyKeyCollection == null) {
|
||||
this.readonlyKeyCollection = new ReadOnlyCollection<KeyType>(
|
||||
this.typedDictionary.Keys
|
||||
);
|
||||
}
|
||||
|
||||
return this.readonlyKeyCollection;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Collection of all values contained in the Dictionary</summary>
|
||||
public ICollection<ValueType> Values {
|
||||
get {
|
||||
if(this.readonlyValueCollection == null) {
|
||||
this.readonlyValueCollection = new ReadOnlyCollection<ValueType>(
|
||||
this.typedDictionary.Values
|
||||
);
|
||||
}
|
||||
|
||||
return this.readonlyValueCollection;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to retrieve the item with the specified key from the Dictionary
|
||||
/// </summary>
|
||||
/// <param name="key">Key of the item to attempt to retrieve</param>
|
||||
/// <param name="value">
|
||||
/// Output parameter that will receive the key upon successful completion
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// True if the item was found and has been placed in the output parameter
|
||||
/// </returns>
|
||||
public bool TryGetValue(KeyType key, out ValueType value) {
|
||||
return this.typedDictionary.TryGetValue(key, out value);
|
||||
}
|
||||
|
||||
/// <summary>Accesses an item in the Dictionary by its key</summary>
|
||||
/// <param name="key">Key of the item that will be accessed</param>
|
||||
public ValueType this[KeyType key] {
|
||||
get { return this.typedDictionary[key]; }
|
||||
}
|
||||
|
||||
#region IDictionary<,> implementation
|
||||
|
||||
/// <summary>Inserts an item into the Dictionary</summary>
|
||||
/// <param name="key">Key under which to add the new item</param>
|
||||
/// <param name="value">Item that will be added to the Dictionary</param>
|
||||
void IDictionary<KeyType, ValueType>.Add(KeyType key, ValueType value) {
|
||||
throw new NotSupportedException(
|
||||
"Adding items is not supported by the read-only Dictionary"
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>Removes the item with the specified key from the Dictionary</summary>
|
||||
/// <param name="key">Key of the elementes that will be removed</param>
|
||||
/// <returns>True if an item with the specified key was found and removed</returns>
|
||||
bool IDictionary<KeyType, ValueType>.Remove(KeyType key) {
|
||||
throw new NotSupportedException(
|
||||
"Removing items is not supported by the read-only Dictionary"
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>Accesses an item in the Dictionary by its key</summary>
|
||||
/// <param name="key">Key of the item that will be accessed</param>
|
||||
ValueType IDictionary<KeyType, ValueType>.this[KeyType key] {
|
||||
get { return this.typedDictionary[key]; }
|
||||
set {
|
||||
throw new NotSupportedException(
|
||||
"Assigning items is not supported in a read-only Dictionary"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IEnumerable implementation
|
||||
|
||||
/// <summary>Returns a new object enumerator for the Dictionary</summary>
|
||||
/// <returns>The new object enumerator</returns>
|
||||
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() {
|
||||
return (this.typedDictionary as IEnumerable).GetEnumerator();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IDictionary implementation
|
||||
|
||||
/// <summary>Removes all items from the Dictionary</summary>
|
||||
void IDictionary.Clear() {
|
||||
throw new NotSupportedException(
|
||||
"Clearing is not supported in a read-only Dictionary"
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>Adds an item into the Dictionary</summary>
|
||||
/// <param name="key">Key under which the item will be added</param>
|
||||
/// <param name="value">Item that will be added</param>
|
||||
void IDictionary.Add(object key, object value) {
|
||||
throw new NotSupportedException(
|
||||
"Adding items is not supported in a read-only Dictionary"
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>Determines whether the specified key exists in the Dictionary</summary>
|
||||
/// <param name="key">Key that will be checked for</param>
|
||||
/// <returns>True if an item with the specified key exists in the Dictionary</returns>
|
||||
bool IDictionary.Contains(object key) {
|
||||
return this.objectDictionary.Contains(key);
|
||||
}
|
||||
|
||||
/// <summary>Whether the size of the Dictionary is fixed</summary>
|
||||
bool IDictionary.IsFixedSize {
|
||||
get { return this.objectDictionary.IsFixedSize; }
|
||||
}
|
||||
|
||||
/// <summary>Returns a collection of all keys in the Dictionary</summary>
|
||||
ICollection IDictionary.Keys {
|
||||
get {
|
||||
if(this.readonlyKeyCollection == null) {
|
||||
this.readonlyKeyCollection = new ReadOnlyCollection<KeyType>(
|
||||
this.typedDictionary.Keys
|
||||
);
|
||||
}
|
||||
|
||||
return this.readonlyKeyCollection;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Returns a collection of all values stored in the Dictionary</summary>
|
||||
ICollection IDictionary.Values {
|
||||
get {
|
||||
if(this.readonlyValueCollection == null) {
|
||||
this.readonlyValueCollection = new ReadOnlyCollection<ValueType>(
|
||||
this.typedDictionary.Values
|
||||
);
|
||||
}
|
||||
|
||||
return this.readonlyValueCollection;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Removes an item from the Dictionary</summary>
|
||||
/// <param name="key">Key of the item that will be removed</param>
|
||||
void IDictionary.Remove(object key) {
|
||||
throw new NotSupportedException(
|
||||
"Removing is not supported by the read-only Dictionary"
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>Accesses an item in the Dictionary by its key</summary>
|
||||
/// <param name="key">Key of the item that will be accessed</param>
|
||||
/// <returns>The item with the specified key</returns>
|
||||
object IDictionary.this[object key] {
|
||||
get { return this.objectDictionary[key]; }
|
||||
set {
|
||||
throw new NotSupportedException(
|
||||
"Assigning items is not supported by the read-only Dictionary"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion // IDictionary implementation
|
||||
|
||||
#region IDictionaryEnumerator implementation
|
||||
|
||||
/// <summary>Returns a new entry enumerator for the dictionary</summary>
|
||||
/// <returns>The new entry enumerator</returns>
|
||||
IDictionaryEnumerator IDictionary.GetEnumerator() {
|
||||
return this.objectDictionary.GetEnumerator();
|
||||
}
|
||||
|
||||
#endregion // IDictionaryEnumerator implementation
|
||||
|
||||
#region ICollection<> implementation
|
||||
|
||||
/// <summary>Inserts an already prepared element into the Dictionary</summary>
|
||||
/// <param name="item">Prepared element that will be added to the Dictionary</param>
|
||||
void ICollection<KeyValuePair<KeyType, ValueType>>.Add(
|
||||
KeyValuePair<KeyType, ValueType> item
|
||||
) {
|
||||
throw new NotSupportedException(
|
||||
"Adding items is not supported by the read-only Dictionary"
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>Removes all items from the Dictionary</summary>
|
||||
void ICollection<KeyValuePair<KeyType, ValueType>>.Clear() {
|
||||
throw new NotSupportedException(
|
||||
"Clearing is not supported in a read-only Dictionary"
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>Removes all items from the Dictionary</summary>
|
||||
/// <param name="itemToRemove">Item that will be removed from the Dictionary</param>
|
||||
bool ICollection<KeyValuePair<KeyType, ValueType>>.Remove(
|
||||
KeyValuePair<KeyType, ValueType> itemToRemove
|
||||
) {
|
||||
throw new NotSupportedException(
|
||||
"Removing items is not supported in a read-only Dictionary"
|
||||
);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region ICollection implementation
|
||||
|
||||
/// <summary>Copies the contents of the Dictionary into an array</summary>
|
||||
/// <param name="array">Array the Dictionary contents will be copied into</param>
|
||||
/// <param name="index">
|
||||
/// Starting index at which to begin filling the destination array
|
||||
/// </param>
|
||||
void ICollection.CopyTo(Array array, int index) {
|
||||
this.objectDictionary.CopyTo(array, index);
|
||||
}
|
||||
|
||||
/// <summary>Whether the Dictionary is synchronized for multi-threaded usage</summary>
|
||||
bool ICollection.IsSynchronized {
|
||||
get { return this.objectDictionary.IsSynchronized; }
|
||||
}
|
||||
|
||||
/// <summary>Synchronization root on which the Dictionary locks</summary>
|
||||
object ICollection.SyncRoot {
|
||||
get { return this.objectDictionary.SyncRoot; }
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#if !NO_SERIALIZATION
|
||||
#region ISerializable implementation
|
||||
|
||||
/// <summary>Serializes the Dictionary</summary>
|
||||
/// <param name="info">
|
||||
/// Provides the container into which the Dictionary will serialize itself
|
||||
/// </param>
|
||||
/// <param name="context">
|
||||
/// Contextual informations about the serialization environment
|
||||
/// </param>
|
||||
void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context) {
|
||||
(this.typedDictionary as ISerializable).GetObjectData(info, context);
|
||||
}
|
||||
|
||||
/// <summary>Called after all objects have been successfully deserialized</summary>
|
||||
/// <param name="sender">Nicht unterstützt</param>
|
||||
void IDeserializationCallback.OnDeserialization(object sender) {
|
||||
(this.typedDictionary as IDeserializationCallback).OnDeserialization(sender);
|
||||
}
|
||||
|
||||
#endregion
|
||||
#endif //!NO_SERIALIZATION
|
||||
|
||||
/// <summary>The wrapped Dictionary under its type-safe interface</summary>
|
||||
private IDictionary<KeyType, ValueType> typedDictionary;
|
||||
/// <summary>The wrapped Dictionary under its object interface</summary>
|
||||
private IDictionary objectDictionary;
|
||||
/// <summary>ReadOnly wrapper for the keys collection of the Dictionary</summary>
|
||||
private ReadOnlyCollection<KeyType> readonlyKeyCollection;
|
||||
/// <summary>ReadOnly wrapper for the values collection of the Dictionary</summary>
|
||||
private ReadOnlyCollection<ValueType> readonlyValueCollection;
|
||||
}
|
||||
|
||||
} // namespace Nuclex.Support.Collections
|
||||
#region Apache License 2.0
|
||||
/*
|
||||
Nuclex .NET Framework
|
||||
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
#endregion // Apache License 2.0
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections;
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace Nuclex.Support.Collections {
|
||||
|
||||
/// <summary>Wraps a dictionary and prevents users from modifying it</summary>
|
||||
/// <typeparam name="KeyType">Type of the keys used in the dictionary</typeparam>
|
||||
/// <typeparam name="ValueType">Type of the values used in the dictionary</typeparam>
|
||||
#if !NO_SERIALIZATION
|
||||
[Serializable]
|
||||
#endif
|
||||
public class ReadOnlyDictionary<KeyType, ValueType> :
|
||||
#if !NO_SERIALIZATION
|
||||
ISerializable,
|
||||
IDeserializationCallback,
|
||||
#endif
|
||||
IDictionary,
|
||||
IDictionary<KeyType, ValueType> {
|
||||
|
||||
#if !NO_SERIALIZATION
|
||||
|
||||
#region class SerializedDictionary
|
||||
|
||||
/// <summary>
|
||||
/// Dictionary wrapped used to reconstruct a serialized read only dictionary
|
||||
/// </summary>
|
||||
private class SerializedDictionary : Dictionary<KeyType, ValueType> {
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the System.WeakReference class, using deserialized
|
||||
/// data from the specified serialization and stream objects.
|
||||
/// </summary>
|
||||
/// <param name="info">
|
||||
/// An object that holds all the data needed to serialize or deserialize the
|
||||
/// current System.WeakReference object.
|
||||
/// </param>
|
||||
/// <param name="context">
|
||||
/// (Reserved) Describes the source and destination of the serialized stream
|
||||
/// specified by info.
|
||||
/// </param>
|
||||
/// <exception cref="System.ArgumentNullException">
|
||||
/// The info parameter is null.
|
||||
/// </exception>
|
||||
public SerializedDictionary(SerializationInfo info, StreamingContext context) :
|
||||
base(info, context) { }
|
||||
|
||||
}
|
||||
|
||||
#endregion // class SerializedDictionary
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the System.WeakReference class, using deserialized
|
||||
/// data from the specified serialization and stream objects.
|
||||
/// </summary>
|
||||
/// <param name="info">
|
||||
/// An object that holds all the data needed to serialize or deserialize the
|
||||
/// current System.WeakReference object.
|
||||
/// </param>
|
||||
/// <param name="context">
|
||||
/// (Reserved) Describes the source and destination of the serialized stream
|
||||
/// specified by info.
|
||||
/// </param>
|
||||
/// <exception cref="System.ArgumentNullException">
|
||||
/// The info parameter is null.
|
||||
/// </exception>
|
||||
protected ReadOnlyDictionary(SerializationInfo info, StreamingContext context) :
|
||||
this(new SerializedDictionary(info, context)) { }
|
||||
|
||||
#endif // !NO_SERIALIZATION
|
||||
|
||||
/// <summary>Initializes a new read-only dictionary wrapper</summary>
|
||||
/// <param name="dictionary">Dictionary that will be wrapped</param>
|
||||
public ReadOnlyDictionary(IDictionary<KeyType, ValueType> dictionary) {
|
||||
this.typedDictionary = dictionary;
|
||||
this.objectDictionary = (this.typedDictionary as IDictionary);
|
||||
}
|
||||
|
||||
/// <summary>Whether the directory is write-protected</summary>
|
||||
public bool IsReadOnly {
|
||||
get { return true; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified KeyValuePair is contained in the Dictionary
|
||||
/// </summary>
|
||||
/// <param name="item">KeyValuePair that will be checked for</param>
|
||||
/// <returns>True if the provided KeyValuePair was contained in the Dictionary</returns>
|
||||
public bool Contains(KeyValuePair<KeyType, ValueType> item) {
|
||||
return this.typedDictionary.Contains(item);
|
||||
}
|
||||
|
||||
/// <summary>Determines whether the Dictionary contains the specified key</summary>
|
||||
/// <param name="key">Key that will be checked for</param>
|
||||
/// <returns>
|
||||
/// True if an entry with the specified key was contained in the Dictionary
|
||||
/// </returns>
|
||||
public bool ContainsKey(KeyType key) {
|
||||
return this.typedDictionary.ContainsKey(key);
|
||||
}
|
||||
|
||||
/// <summary>Copies the contents of the Dictionary into an array</summary>
|
||||
/// <param name="array">Array the Dictionary will be copied into</param>
|
||||
/// <param name="arrayIndex">
|
||||
/// Starting index at which to begin filling the destination array
|
||||
/// </param>
|
||||
public void CopyTo(KeyValuePair<KeyType, ValueType>[] array, int arrayIndex) {
|
||||
this.typedDictionary.CopyTo(array, arrayIndex);
|
||||
}
|
||||
|
||||
/// <summary>Number of elements contained in the Dictionary</summary>
|
||||
public int Count {
|
||||
get { return this.typedDictionary.Count; }
|
||||
}
|
||||
|
||||
/// <summary>Creates a new enumerator for the Dictionary</summary>
|
||||
/// <returns>The new Dictionary enumerator</returns>
|
||||
public IEnumerator<KeyValuePair<KeyType, ValueType>> GetEnumerator() {
|
||||
return this.typedDictionary.GetEnumerator();
|
||||
}
|
||||
|
||||
/// <summary>Collection of all keys contained in the Dictionary</summary>
|
||||
public ICollection<KeyType> Keys {
|
||||
get {
|
||||
if(this.readonlyKeyCollection == null) {
|
||||
this.readonlyKeyCollection = new ReadOnlyCollection<KeyType>(
|
||||
this.typedDictionary.Keys
|
||||
);
|
||||
}
|
||||
|
||||
return this.readonlyKeyCollection;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Collection of all values contained in the Dictionary</summary>
|
||||
public ICollection<ValueType> Values {
|
||||
get {
|
||||
if(this.readonlyValueCollection == null) {
|
||||
this.readonlyValueCollection = new ReadOnlyCollection<ValueType>(
|
||||
this.typedDictionary.Values
|
||||
);
|
||||
}
|
||||
|
||||
return this.readonlyValueCollection;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to retrieve the item with the specified key from the Dictionary
|
||||
/// </summary>
|
||||
/// <param name="key">Key of the item to attempt to retrieve</param>
|
||||
/// <param name="value">
|
||||
/// Output parameter that will receive the key upon successful completion
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// True if the item was found and has been placed in the output parameter
|
||||
/// </returns>
|
||||
public bool TryGetValue(KeyType key, out ValueType value) {
|
||||
return this.typedDictionary.TryGetValue(key, out value);
|
||||
}
|
||||
|
||||
/// <summary>Accesses an item in the Dictionary by its key</summary>
|
||||
/// <param name="key">Key of the item that will be accessed</param>
|
||||
public ValueType this[KeyType key] {
|
||||
get { return this.typedDictionary[key]; }
|
||||
}
|
||||
|
||||
#region IDictionary<,> implementation
|
||||
|
||||
/// <summary>Inserts an item into the Dictionary</summary>
|
||||
/// <param name="key">Key under which to add the new item</param>
|
||||
/// <param name="value">Item that will be added to the Dictionary</param>
|
||||
void IDictionary<KeyType, ValueType>.Add(KeyType key, ValueType value) {
|
||||
throw new NotSupportedException(
|
||||
"Adding items is not supported by the read-only Dictionary"
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>Removes the item with the specified key from the Dictionary</summary>
|
||||
/// <param name="key">Key of the elementes that will be removed</param>
|
||||
/// <returns>True if an item with the specified key was found and removed</returns>
|
||||
bool IDictionary<KeyType, ValueType>.Remove(KeyType key) {
|
||||
throw new NotSupportedException(
|
||||
"Removing items is not supported by the read-only Dictionary"
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>Accesses an item in the Dictionary by its key</summary>
|
||||
/// <param name="key">Key of the item that will be accessed</param>
|
||||
ValueType IDictionary<KeyType, ValueType>.this[KeyType key] {
|
||||
get { return this.typedDictionary[key]; }
|
||||
set {
|
||||
throw new NotSupportedException(
|
||||
"Assigning items is not supported in a read-only Dictionary"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IEnumerable implementation
|
||||
|
||||
/// <summary>Returns a new object enumerator for the Dictionary</summary>
|
||||
/// <returns>The new object enumerator</returns>
|
||||
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() {
|
||||
return (this.typedDictionary as IEnumerable).GetEnumerator();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IDictionary implementation
|
||||
|
||||
/// <summary>Removes all items from the Dictionary</summary>
|
||||
void IDictionary.Clear() {
|
||||
throw new NotSupportedException(
|
||||
"Clearing is not supported in a read-only Dictionary"
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>Adds an item into the Dictionary</summary>
|
||||
/// <param name="key">Key under which the item will be added</param>
|
||||
/// <param name="value">Item that will be added</param>
|
||||
void IDictionary.Add(object key, object value) {
|
||||
throw new NotSupportedException(
|
||||
"Adding items is not supported in a read-only Dictionary"
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>Determines whether the specified key exists in the Dictionary</summary>
|
||||
/// <param name="key">Key that will be checked for</param>
|
||||
/// <returns>True if an item with the specified key exists in the Dictionary</returns>
|
||||
bool IDictionary.Contains(object key) {
|
||||
return this.objectDictionary.Contains(key);
|
||||
}
|
||||
|
||||
/// <summary>Whether the size of the Dictionary is fixed</summary>
|
||||
bool IDictionary.IsFixedSize {
|
||||
get { return this.objectDictionary.IsFixedSize; }
|
||||
}
|
||||
|
||||
/// <summary>Returns a collection of all keys in the Dictionary</summary>
|
||||
ICollection IDictionary.Keys {
|
||||
get {
|
||||
if(this.readonlyKeyCollection == null) {
|
||||
this.readonlyKeyCollection = new ReadOnlyCollection<KeyType>(
|
||||
this.typedDictionary.Keys
|
||||
);
|
||||
}
|
||||
|
||||
return this.readonlyKeyCollection;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Returns a collection of all values stored in the Dictionary</summary>
|
||||
ICollection IDictionary.Values {
|
||||
get {
|
||||
if(this.readonlyValueCollection == null) {
|
||||
this.readonlyValueCollection = new ReadOnlyCollection<ValueType>(
|
||||
this.typedDictionary.Values
|
||||
);
|
||||
}
|
||||
|
||||
return this.readonlyValueCollection;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Removes an item from the Dictionary</summary>
|
||||
/// <param name="key">Key of the item that will be removed</param>
|
||||
void IDictionary.Remove(object key) {
|
||||
throw new NotSupportedException(
|
||||
"Removing is not supported by the read-only Dictionary"
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>Accesses an item in the Dictionary by its key</summary>
|
||||
/// <param name="key">Key of the item that will be accessed</param>
|
||||
/// <returns>The item with the specified key</returns>
|
||||
object IDictionary.this[object key] {
|
||||
get { return this.objectDictionary[key]; }
|
||||
set {
|
||||
throw new NotSupportedException(
|
||||
"Assigning items is not supported by the read-only Dictionary"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion // IDictionary implementation
|
||||
|
||||
#region IDictionaryEnumerator implementation
|
||||
|
||||
/// <summary>Returns a new entry enumerator for the dictionary</summary>
|
||||
/// <returns>The new entry enumerator</returns>
|
||||
IDictionaryEnumerator IDictionary.GetEnumerator() {
|
||||
return this.objectDictionary.GetEnumerator();
|
||||
}
|
||||
|
||||
#endregion // IDictionaryEnumerator implementation
|
||||
|
||||
#region ICollection<> implementation
|
||||
|
||||
/// <summary>Inserts an already prepared element into the Dictionary</summary>
|
||||
/// <param name="item">Prepared element that will be added to the Dictionary</param>
|
||||
void ICollection<KeyValuePair<KeyType, ValueType>>.Add(
|
||||
KeyValuePair<KeyType, ValueType> item
|
||||
) {
|
||||
throw new NotSupportedException(
|
||||
"Adding items is not supported by the read-only Dictionary"
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>Removes all items from the Dictionary</summary>
|
||||
void ICollection<KeyValuePair<KeyType, ValueType>>.Clear() {
|
||||
throw new NotSupportedException(
|
||||
"Clearing is not supported in a read-only Dictionary"
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>Removes all items from the Dictionary</summary>
|
||||
/// <param name="itemToRemove">Item that will be removed from the Dictionary</param>
|
||||
bool ICollection<KeyValuePair<KeyType, ValueType>>.Remove(
|
||||
KeyValuePair<KeyType, ValueType> itemToRemove
|
||||
) {
|
||||
throw new NotSupportedException(
|
||||
"Removing items is not supported in a read-only Dictionary"
|
||||
);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region ICollection implementation
|
||||
|
||||
/// <summary>Copies the contents of the Dictionary into an array</summary>
|
||||
/// <param name="array">Array the Dictionary contents will be copied into</param>
|
||||
/// <param name="index">
|
||||
/// Starting index at which to begin filling the destination array
|
||||
/// </param>
|
||||
void ICollection.CopyTo(Array array, int index) {
|
||||
this.objectDictionary.CopyTo(array, index);
|
||||
}
|
||||
|
||||
/// <summary>Whether the Dictionary is synchronized for multi-threaded usage</summary>
|
||||
bool ICollection.IsSynchronized {
|
||||
get { return this.objectDictionary.IsSynchronized; }
|
||||
}
|
||||
|
||||
/// <summary>Synchronization root on which the Dictionary locks</summary>
|
||||
object ICollection.SyncRoot {
|
||||
get { return this.objectDictionary.SyncRoot; }
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#if !NO_SERIALIZATION
|
||||
#region ISerializable implementation
|
||||
|
||||
/// <summary>Serializes the Dictionary</summary>
|
||||
/// <param name="info">
|
||||
/// Provides the container into which the Dictionary will serialize itself
|
||||
/// </param>
|
||||
/// <param name="context">
|
||||
/// Contextual informations about the serialization environment
|
||||
/// </param>
|
||||
void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context) {
|
||||
(this.typedDictionary as ISerializable).GetObjectData(info, context);
|
||||
}
|
||||
|
||||
/// <summary>Called after all objects have been successfully deserialized</summary>
|
||||
/// <param name="sender">Nicht unterstützt</param>
|
||||
void IDeserializationCallback.OnDeserialization(object sender) {
|
||||
(this.typedDictionary as IDeserializationCallback).OnDeserialization(sender);
|
||||
}
|
||||
|
||||
#endregion
|
||||
#endif //!NO_SERIALIZATION
|
||||
|
||||
/// <summary>The wrapped Dictionary under its type-safe interface</summary>
|
||||
private IDictionary<KeyType, ValueType> typedDictionary;
|
||||
/// <summary>The wrapped Dictionary under its object interface</summary>
|
||||
private IDictionary objectDictionary;
|
||||
/// <summary>ReadOnly wrapper for the keys collection of the Dictionary</summary>
|
||||
private ReadOnlyCollection<KeyType> readonlyKeyCollection;
|
||||
/// <summary>ReadOnly wrapper for the values collection of the Dictionary</summary>
|
||||
private ReadOnlyCollection<ValueType> readonlyValueCollection;
|
||||
}
|
||||
|
||||
} // namespace Nuclex.Support.Collections
|
||||
|
|
|
|||
|
|
@ -1,381 +1,380 @@
|
|||
#region CPL License
|
||||
/*
|
||||
Nuclex Framework
|
||||
Copyright (C) 2002-2017 Nuclex Development Labs
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the IBM Common Public License as
|
||||
published by the IBM Corporation; either version 1.0 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
IBM Common Public License for more details.
|
||||
|
||||
You should have received a copy of the IBM Common Public
|
||||
License along with this library
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
#if UNITTEST
|
||||
|
||||
using NUnit.Framework;
|
||||
|
||||
namespace Nuclex.Support.Collections {
|
||||
|
||||
/// <summary>Unit Test for the read only list wrapper</summary>
|
||||
[TestFixture]
|
||||
internal class ReadOnlyListTest {
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that the copy constructor of the read only list works
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestCopyConstructor() {
|
||||
int[] integers = new int[] { 12, 34, 56, 78 };
|
||||
ReadOnlyList<int> testList = new ReadOnlyList<int>(integers);
|
||||
|
||||
CollectionAssert.AreEqual(integers, testList);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that the IsReadOnly property returns true</summary>
|
||||
[Test]
|
||||
public void TestIsReadOnly() {
|
||||
ReadOnlyList<int> testList = new ReadOnlyList<int>(new int[0]);
|
||||
|
||||
Assert.IsTrue(testList.IsReadOnly);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that the CopyTo() of the read only list works
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestCopyToArray() {
|
||||
int[] inputIntegers = new int[] { 12, 34, 56, 78 };
|
||||
ReadOnlyList<int> testList = new ReadOnlyList<int>(inputIntegers);
|
||||
|
||||
int[] outputIntegers = new int[testList.Count];
|
||||
testList.CopyTo(outputIntegers, 0);
|
||||
|
||||
CollectionAssert.AreEqual(inputIntegers, outputIntegers);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether the Contains() method of the read only list is able to
|
||||
/// determine if the list contains an item
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestContains() {
|
||||
int[] integers = new int[] { 1234, 6789 };
|
||||
ReadOnlyList<int> testList = new ReadOnlyList<int>(integers);
|
||||
|
||||
Assert.IsTrue(testList.Contains(1234));
|
||||
Assert.IsFalse(testList.Contains(4321));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether the IndexOf() method of the read only list is able to
|
||||
/// determine if the index of an item in the list
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestIndexOf() {
|
||||
int[] integers = new int[] { 12, 34, 67, 89 };
|
||||
ReadOnlyList<int> testList = new ReadOnlyList<int>(integers);
|
||||
|
||||
Assert.AreEqual(0, testList.IndexOf(12));
|
||||
Assert.AreEqual(1, testList.IndexOf(34));
|
||||
Assert.AreEqual(2, testList.IndexOf(67));
|
||||
Assert.AreEqual(3, testList.IndexOf(89));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether the indexer method of the read only list is able to
|
||||
/// retrieve items from the list
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestRetrieveByIndexer() {
|
||||
int[] integers = new int[] { 12, 34, 67, 89 };
|
||||
ReadOnlyList<int> testList = new ReadOnlyList<int>(integers);
|
||||
|
||||
Assert.AreEqual(12, testList[0]);
|
||||
Assert.AreEqual(34, testList[1]);
|
||||
Assert.AreEqual(67, testList[2]);
|
||||
Assert.AreEqual(89, testList[3]);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether the read only list will throw an exception if its Insert() method
|
||||
/// is called via the generic IList<> interface
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestThrowOnInsertViaGenericIList() {
|
||||
ReadOnlyList<int> testList = new ReadOnlyList<int>(new int[0]);
|
||||
Assert.Throws<NotSupportedException>(
|
||||
delegate() { (testList as IList<int>).Insert(0, 12345); }
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether the read only list will throw an exception if its RemoveAt() method
|
||||
/// is called via the generic IList<> interface
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestThrowOnRemoveViaGenericIList() {
|
||||
ReadOnlyList<int> testList = new ReadOnlyList<int>(new int[1]);
|
||||
Assert.Throws<NotSupportedException>(
|
||||
delegate() { (testList as IList<int>).RemoveAt(0); }
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether the indexer method of the read only list will throw an exception
|
||||
/// if it is attempted to be used for replacing an item
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestRetrieveByIndexerViaGenericIList() {
|
||||
int[] integers = new int[] { 12, 34, 67, 89 };
|
||||
ReadOnlyList<int> testList = new ReadOnlyList<int>(integers);
|
||||
|
||||
Assert.AreEqual(12, (testList as IList<int>)[0]);
|
||||
Assert.AreEqual(34, (testList as IList<int>)[1]);
|
||||
Assert.AreEqual(67, (testList as IList<int>)[2]);
|
||||
Assert.AreEqual(89, (testList as IList<int>)[3]);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether the indexer method of the read only list will throw an exception
|
||||
/// if it is attempted to be used for replacing an item
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestThrowOnReplaceByIndexerViaGenericIList() {
|
||||
ReadOnlyList<int> testList = new ReadOnlyList<int>(new int[1]);
|
||||
|
||||
Assert.Throws<NotSupportedException>(
|
||||
delegate() { (testList as IList<int>)[0] = 12345; }
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether the read only list will throw an exception if its Add() method
|
||||
/// is called via the generic ICollection<> interface
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestThrowOnAddViaGenericICollection() {
|
||||
ReadOnlyList<int> testList = new ReadOnlyList<int>(new int[0]);
|
||||
Assert.Throws<NotSupportedException>(
|
||||
delegate() { (testList as ICollection<int>).Add(12345); }
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether the read only list will throw an exception if its Clear() method
|
||||
/// is called via the generic ICollection<> interface
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestThrowOnClearViaGenericICollection() {
|
||||
ReadOnlyList<int> testList = new ReadOnlyList<int>(new int[1]);
|
||||
Assert.Throws<NotSupportedException>(
|
||||
delegate() { (testList as ICollection<int>).Clear(); }
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether the read only list will throw an exception if its Remove() method
|
||||
/// is called via the generic ICollection<> interface
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestThrowOnRemoveViaGenericICollection() {
|
||||
int[] integers = new int[] { 12, 34, 67, 89 };
|
||||
ReadOnlyList<int> testList = new ReadOnlyList<int>(integers);
|
||||
|
||||
Assert.Throws<NotSupportedException>(
|
||||
delegate() { (testList as ICollection<int>).Remove(89); }
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests whether the typesafe enumerator of the read only list is working
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestTypesafeEnumerator() {
|
||||
int[] inputIntegers = new int[] { 12, 34, 56, 78 };
|
||||
ReadOnlyList<int> testList = new ReadOnlyList<int>(inputIntegers);
|
||||
|
||||
List<int> outputIntegers = new List<int>();
|
||||
foreach(int value in testList) {
|
||||
outputIntegers.Add(value);
|
||||
}
|
||||
|
||||
CollectionAssert.AreEqual(inputIntegers, outputIntegers);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether the read only list will throw an exception if its Clear() method
|
||||
/// is called via the IList interface
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestThrowOnClearViaIList() {
|
||||
ReadOnlyList<int> testList = new ReadOnlyList<int>(new int[1]);
|
||||
Assert.Throws<NotSupportedException>(
|
||||
delegate() { (testList as IList).Clear(); }
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether the read only list will throw an exception if its Add() method
|
||||
/// is called via the IList interface
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestThrowOnAddViaIList() {
|
||||
ReadOnlyList<int> testList = new ReadOnlyList<int>(new int[0]);
|
||||
Assert.Throws<NotSupportedException>(
|
||||
delegate() { (testList as IList).Add(12345); }
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether the Contains() method of the read only list is able to
|
||||
/// determine if the list contains an item
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestContainsViaIList() {
|
||||
int[] integers = new int[] { 1234, 6789 };
|
||||
ReadOnlyList<int> testList = new ReadOnlyList<int>(integers);
|
||||
|
||||
Assert.IsTrue((testList as IList).Contains(1234));
|
||||
Assert.IsFalse((testList as IList).Contains(4321));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether the IndexOf() method of the read only list is able to
|
||||
/// determine if the index of an item in the list
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestIndexOfViaIList() {
|
||||
int[] integers = new int[] { 12, 34, 67, 89 };
|
||||
ReadOnlyList<int> testList = new ReadOnlyList<int>(integers);
|
||||
|
||||
Assert.AreEqual(0, (testList as IList).IndexOf(12));
|
||||
Assert.AreEqual(1, (testList as IList).IndexOf(34));
|
||||
Assert.AreEqual(2, (testList as IList).IndexOf(67));
|
||||
Assert.AreEqual(3, (testList as IList).IndexOf(89));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether the read only list will throw an exception if its Insert() method
|
||||
/// is called via the IList interface
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestThrowOnInsertViaIList() {
|
||||
ReadOnlyList<int> testList = new ReadOnlyList<int>(new int[0]);
|
||||
Assert.Throws<NotSupportedException>(
|
||||
delegate() { (testList as IList).Insert(0, 12345); }
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether the IsFixedSize property of the read only list returns the
|
||||
/// expected result for a read only list based on a fixed array
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestIsFixedSizeViaIList() {
|
||||
int[] integers = new int[] { 12, 34, 67, 89 };
|
||||
ReadOnlyList<int> testList = new ReadOnlyList<int>(integers);
|
||||
|
||||
Assert.IsTrue((testList as IList).IsFixedSize);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether the read only list will throw an exception if its Remove() method
|
||||
/// is called via the IList interface
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestThrowOnRemoveViaIList() {
|
||||
int[] integers = new int[] { 1234, 6789 };
|
||||
ReadOnlyList<int> testList = new ReadOnlyList<int>(integers);
|
||||
|
||||
Assert.Throws<NotSupportedException>(
|
||||
delegate() { (testList as IList).Remove(6789); }
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether the read only list will throw an exception if its Remove() method
|
||||
/// is called via the IList interface
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestThrowOnRemoveAtViaIList() {
|
||||
ReadOnlyList<int> testList = new ReadOnlyList<int>(new int[1]);
|
||||
|
||||
Assert.Throws<NotSupportedException>(
|
||||
delegate() { (testList as IList).RemoveAt(0); }
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether the indexer method of the read only list will throw an exception
|
||||
/// if it is attempted to be used for replacing an item
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestRetrieveByIndexerViaIList() {
|
||||
int[] integers = new int[] { 12, 34, 67, 89 };
|
||||
ReadOnlyList<int> testList = new ReadOnlyList<int>(integers);
|
||||
|
||||
Assert.AreEqual(12, (testList as IList)[0]);
|
||||
Assert.AreEqual(34, (testList as IList)[1]);
|
||||
Assert.AreEqual(67, (testList as IList)[2]);
|
||||
Assert.AreEqual(89, (testList as IList)[3]);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether the indexer method of the read only list will throw an exception
|
||||
/// if it is attempted to be used for replacing an item
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestThrowOnReplaceByIndexerViaIList() {
|
||||
ReadOnlyList<int> testList = new ReadOnlyList<int>(new int[1]);
|
||||
|
||||
Assert.Throws<NotSupportedException>(
|
||||
delegate() { (testList as IList)[0] = 12345; }
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that the CopyTo() of the read only list works if invoked via
|
||||
/// the ICollection interface
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestCopyToArrayViaICollection() {
|
||||
int[] inputIntegers = new int[] { 12, 34, 56, 78 };
|
||||
ReadOnlyList<int> testList = new ReadOnlyList<int>(inputIntegers);
|
||||
|
||||
int[] outputIntegers = new int[testList.Count];
|
||||
(testList as ICollection).CopyTo(outputIntegers, 0);
|
||||
|
||||
CollectionAssert.AreEqual(inputIntegers, outputIntegers);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that the IsSynchronized property and the SyncRoot property are working
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestSynchronization() {
|
||||
ReadOnlyList<int> testList = new ReadOnlyList<int>(new int[0]);
|
||||
|
||||
if(!(testList as ICollection).IsSynchronized) {
|
||||
lock((testList as ICollection).SyncRoot) {
|
||||
Assert.AreEqual(0, testList.Count);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} // namespace Nuclex.Support.Collections
|
||||
|
||||
#endif // UNITTEST
|
||||
#region Apache License 2.0
|
||||
/*
|
||||
Nuclex .NET Framework
|
||||
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
#endregion // Apache License 2.0
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
#if UNITTEST
|
||||
|
||||
using NUnit.Framework;
|
||||
|
||||
namespace Nuclex.Support.Collections {
|
||||
|
||||
/// <summary>Unit Test for the read only list wrapper</summary>
|
||||
[TestFixture]
|
||||
internal class ReadOnlyListTest {
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that the copy constructor of the read only list works
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestCopyConstructor() {
|
||||
int[] integers = new int[] { 12, 34, 56, 78 };
|
||||
ReadOnlyList<int> testList = new ReadOnlyList<int>(integers);
|
||||
|
||||
CollectionAssert.AreEqual(integers, testList);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that the IsReadOnly property returns true</summary>
|
||||
[Test]
|
||||
public void TestIsReadOnly() {
|
||||
ReadOnlyList<int> testList = new ReadOnlyList<int>(new int[0]);
|
||||
|
||||
Assert.IsTrue(testList.IsReadOnly);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that the CopyTo() of the read only list works
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestCopyToArray() {
|
||||
int[] inputIntegers = new int[] { 12, 34, 56, 78 };
|
||||
ReadOnlyList<int> testList = new ReadOnlyList<int>(inputIntegers);
|
||||
|
||||
int[] outputIntegers = new int[testList.Count];
|
||||
testList.CopyTo(outputIntegers, 0);
|
||||
|
||||
CollectionAssert.AreEqual(inputIntegers, outputIntegers);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether the Contains() method of the read only list is able to
|
||||
/// determine if the list contains an item
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestContains() {
|
||||
int[] integers = new int[] { 1234, 6789 };
|
||||
ReadOnlyList<int> testList = new ReadOnlyList<int>(integers);
|
||||
|
||||
Assert.IsTrue(testList.Contains(1234));
|
||||
Assert.IsFalse(testList.Contains(4321));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether the IndexOf() method of the read only list is able to
|
||||
/// determine if the index of an item in the list
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestIndexOf() {
|
||||
int[] integers = new int[] { 12, 34, 67, 89 };
|
||||
ReadOnlyList<int> testList = new ReadOnlyList<int>(integers);
|
||||
|
||||
Assert.AreEqual(0, testList.IndexOf(12));
|
||||
Assert.AreEqual(1, testList.IndexOf(34));
|
||||
Assert.AreEqual(2, testList.IndexOf(67));
|
||||
Assert.AreEqual(3, testList.IndexOf(89));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether the indexer method of the read only list is able to
|
||||
/// retrieve items from the list
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestRetrieveByIndexer() {
|
||||
int[] integers = new int[] { 12, 34, 67, 89 };
|
||||
ReadOnlyList<int> testList = new ReadOnlyList<int>(integers);
|
||||
|
||||
Assert.AreEqual(12, testList[0]);
|
||||
Assert.AreEqual(34, testList[1]);
|
||||
Assert.AreEqual(67, testList[2]);
|
||||
Assert.AreEqual(89, testList[3]);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether the read only list will throw an exception if its Insert() method
|
||||
/// is called via the generic IList<> interface
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestThrowOnInsertViaGenericIList() {
|
||||
ReadOnlyList<int> testList = new ReadOnlyList<int>(new int[0]);
|
||||
Assert.Throws<NotSupportedException>(
|
||||
delegate() { (testList as IList<int>).Insert(0, 12345); }
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether the read only list will throw an exception if its RemoveAt() method
|
||||
/// is called via the generic IList<> interface
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestThrowOnRemoveViaGenericIList() {
|
||||
ReadOnlyList<int> testList = new ReadOnlyList<int>(new int[1]);
|
||||
Assert.Throws<NotSupportedException>(
|
||||
delegate() { (testList as IList<int>).RemoveAt(0); }
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether the indexer method of the read only list will throw an exception
|
||||
/// if it is attempted to be used for replacing an item
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestRetrieveByIndexerViaGenericIList() {
|
||||
int[] integers = new int[] { 12, 34, 67, 89 };
|
||||
ReadOnlyList<int> testList = new ReadOnlyList<int>(integers);
|
||||
|
||||
Assert.AreEqual(12, (testList as IList<int>)[0]);
|
||||
Assert.AreEqual(34, (testList as IList<int>)[1]);
|
||||
Assert.AreEqual(67, (testList as IList<int>)[2]);
|
||||
Assert.AreEqual(89, (testList as IList<int>)[3]);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether the indexer method of the read only list will throw an exception
|
||||
/// if it is attempted to be used for replacing an item
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestThrowOnReplaceByIndexerViaGenericIList() {
|
||||
ReadOnlyList<int> testList = new ReadOnlyList<int>(new int[1]);
|
||||
|
||||
Assert.Throws<NotSupportedException>(
|
||||
delegate() { (testList as IList<int>)[0] = 12345; }
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether the read only list will throw an exception if its Add() method
|
||||
/// is called via the generic ICollection<> interface
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestThrowOnAddViaGenericICollection() {
|
||||
ReadOnlyList<int> testList = new ReadOnlyList<int>(new int[0]);
|
||||
Assert.Throws<NotSupportedException>(
|
||||
delegate() { (testList as ICollection<int>).Add(12345); }
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether the read only list will throw an exception if its Clear() method
|
||||
/// is called via the generic ICollection<> interface
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestThrowOnClearViaGenericICollection() {
|
||||
ReadOnlyList<int> testList = new ReadOnlyList<int>(new int[1]);
|
||||
Assert.Throws<NotSupportedException>(
|
||||
delegate() { (testList as ICollection<int>).Clear(); }
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether the read only list will throw an exception if its Remove() method
|
||||
/// is called via the generic ICollection<> interface
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestThrowOnRemoveViaGenericICollection() {
|
||||
int[] integers = new int[] { 12, 34, 67, 89 };
|
||||
ReadOnlyList<int> testList = new ReadOnlyList<int>(integers);
|
||||
|
||||
Assert.Throws<NotSupportedException>(
|
||||
delegate() { (testList as ICollection<int>).Remove(89); }
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests whether the typesafe enumerator of the read only list is working
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestTypesafeEnumerator() {
|
||||
int[] inputIntegers = new int[] { 12, 34, 56, 78 };
|
||||
ReadOnlyList<int> testList = new ReadOnlyList<int>(inputIntegers);
|
||||
|
||||
List<int> outputIntegers = new List<int>();
|
||||
foreach(int value in testList) {
|
||||
outputIntegers.Add(value);
|
||||
}
|
||||
|
||||
CollectionAssert.AreEqual(inputIntegers, outputIntegers);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether the read only list will throw an exception if its Clear() method
|
||||
/// is called via the IList interface
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestThrowOnClearViaIList() {
|
||||
ReadOnlyList<int> testList = new ReadOnlyList<int>(new int[1]);
|
||||
Assert.Throws<NotSupportedException>(
|
||||
delegate() { (testList as IList).Clear(); }
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether the read only list will throw an exception if its Add() method
|
||||
/// is called via the IList interface
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestThrowOnAddViaIList() {
|
||||
ReadOnlyList<int> testList = new ReadOnlyList<int>(new int[0]);
|
||||
Assert.Throws<NotSupportedException>(
|
||||
delegate() { (testList as IList).Add(12345); }
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether the Contains() method of the read only list is able to
|
||||
/// determine if the list contains an item
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestContainsViaIList() {
|
||||
int[] integers = new int[] { 1234, 6789 };
|
||||
ReadOnlyList<int> testList = new ReadOnlyList<int>(integers);
|
||||
|
||||
Assert.IsTrue((testList as IList).Contains(1234));
|
||||
Assert.IsFalse((testList as IList).Contains(4321));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether the IndexOf() method of the read only list is able to
|
||||
/// determine if the index of an item in the list
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestIndexOfViaIList() {
|
||||
int[] integers = new int[] { 12, 34, 67, 89 };
|
||||
ReadOnlyList<int> testList = new ReadOnlyList<int>(integers);
|
||||
|
||||
Assert.AreEqual(0, (testList as IList).IndexOf(12));
|
||||
Assert.AreEqual(1, (testList as IList).IndexOf(34));
|
||||
Assert.AreEqual(2, (testList as IList).IndexOf(67));
|
||||
Assert.AreEqual(3, (testList as IList).IndexOf(89));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether the read only list will throw an exception if its Insert() method
|
||||
/// is called via the IList interface
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestThrowOnInsertViaIList() {
|
||||
ReadOnlyList<int> testList = new ReadOnlyList<int>(new int[0]);
|
||||
Assert.Throws<NotSupportedException>(
|
||||
delegate() { (testList as IList).Insert(0, 12345); }
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether the IsFixedSize property of the read only list returns the
|
||||
/// expected result for a read only list based on a fixed array
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestIsFixedSizeViaIList() {
|
||||
int[] integers = new int[] { 12, 34, 67, 89 };
|
||||
ReadOnlyList<int> testList = new ReadOnlyList<int>(integers);
|
||||
|
||||
Assert.IsTrue((testList as IList).IsFixedSize);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether the read only list will throw an exception if its Remove() method
|
||||
/// is called via the IList interface
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestThrowOnRemoveViaIList() {
|
||||
int[] integers = new int[] { 1234, 6789 };
|
||||
ReadOnlyList<int> testList = new ReadOnlyList<int>(integers);
|
||||
|
||||
Assert.Throws<NotSupportedException>(
|
||||
delegate() { (testList as IList).Remove(6789); }
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether the read only list will throw an exception if its Remove() method
|
||||
/// is called via the IList interface
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestThrowOnRemoveAtViaIList() {
|
||||
ReadOnlyList<int> testList = new ReadOnlyList<int>(new int[1]);
|
||||
|
||||
Assert.Throws<NotSupportedException>(
|
||||
delegate() { (testList as IList).RemoveAt(0); }
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether the indexer method of the read only list will throw an exception
|
||||
/// if it is attempted to be used for replacing an item
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestRetrieveByIndexerViaIList() {
|
||||
int[] integers = new int[] { 12, 34, 67, 89 };
|
||||
ReadOnlyList<int> testList = new ReadOnlyList<int>(integers);
|
||||
|
||||
Assert.AreEqual(12, (testList as IList)[0]);
|
||||
Assert.AreEqual(34, (testList as IList)[1]);
|
||||
Assert.AreEqual(67, (testList as IList)[2]);
|
||||
Assert.AreEqual(89, (testList as IList)[3]);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether the indexer method of the read only list will throw an exception
|
||||
/// if it is attempted to be used for replacing an item
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestThrowOnReplaceByIndexerViaIList() {
|
||||
ReadOnlyList<int> testList = new ReadOnlyList<int>(new int[1]);
|
||||
|
||||
Assert.Throws<NotSupportedException>(
|
||||
delegate() { (testList as IList)[0] = 12345; }
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that the CopyTo() of the read only list works if invoked via
|
||||
/// the ICollection interface
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestCopyToArrayViaICollection() {
|
||||
int[] inputIntegers = new int[] { 12, 34, 56, 78 };
|
||||
ReadOnlyList<int> testList = new ReadOnlyList<int>(inputIntegers);
|
||||
|
||||
int[] outputIntegers = new int[testList.Count];
|
||||
(testList as ICollection).CopyTo(outputIntegers, 0);
|
||||
|
||||
CollectionAssert.AreEqual(inputIntegers, outputIntegers);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that the IsSynchronized property and the SyncRoot property are working
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestSynchronization() {
|
||||
ReadOnlyList<int> testList = new ReadOnlyList<int>(new int[0]);
|
||||
|
||||
if(!(testList as ICollection).IsSynchronized) {
|
||||
lock((testList as ICollection).SyncRoot) {
|
||||
Assert.AreEqual(0, testList.Count);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} // namespace Nuclex.Support.Collections
|
||||
|
||||
#endif // UNITTEST
|
||||
|
|
|
|||
|
|
@ -1,258 +1,257 @@
|
|||
#region CPL License
|
||||
/*
|
||||
Nuclex Framework
|
||||
Copyright (C) 2002-2017 Nuclex Development Labs
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the IBM Common Public License as
|
||||
published by the IBM Corporation; either version 1.0 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
IBM Common Public License for more details.
|
||||
|
||||
You should have received a copy of the IBM Common Public
|
||||
License along with this library
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Nuclex.Support.Collections {
|
||||
|
||||
/// <summary>Wraps a list and prevents users from modifying it</summary>
|
||||
/// <typeparam name="TItem">Type of items to manage in the set</typeparam>
|
||||
public class ReadOnlyList<TItem> : IList<TItem>, IList {
|
||||
|
||||
/// <summary>Initializes a new read-only List wrapper</summary>
|
||||
/// <param name="list">List that will be wrapped</param>
|
||||
public ReadOnlyList(IList<TItem> list) {
|
||||
this.typedList = list;
|
||||
this.objectList = (list as IList);
|
||||
}
|
||||
|
||||
/// <summary>Retrieves the index of an item within the List</summary>
|
||||
/// <param name="item">Item whose index will be returned</param>
|
||||
/// <returns>The zero-based index of the specified item in the List</returns>
|
||||
public int IndexOf(TItem item) {
|
||||
return this.typedList.IndexOf(item);
|
||||
}
|
||||
|
||||
/// <summary>Accesses the List item with the specified index</summary>
|
||||
/// <param name="index">Zero-based index of the List item that will be accessed</param>
|
||||
public TItem this[int index] {
|
||||
get { return this.typedList[index]; }
|
||||
}
|
||||
|
||||
/// <summary>Determines whether the List contains the specified item</summary>
|
||||
/// <param name="item">Item that will be checked for</param>
|
||||
/// <returns>True if the specified item is contained in the List</returns>
|
||||
public bool Contains(TItem item) {
|
||||
return this.typedList.Contains(item);
|
||||
}
|
||||
|
||||
/// <summary>Copies the contents of the List into an array</summary>
|
||||
/// <param name="array">Array the List will be copied into</param>
|
||||
/// <param name="arrayIndex">
|
||||
/// Starting index at which to begin filling the destination array
|
||||
/// </param>
|
||||
public void CopyTo(TItem[] array, int arrayIndex) {
|
||||
this.typedList.CopyTo(array, arrayIndex);
|
||||
}
|
||||
|
||||
/// <summary>The number of items current contained in the list</summary>
|
||||
public int Count {
|
||||
get { return this.typedList.Count; }
|
||||
}
|
||||
|
||||
/// <summary>Whether the list is write-protected</summary>
|
||||
public bool IsReadOnly {
|
||||
get { return true; }
|
||||
}
|
||||
|
||||
/// <summary>Returns a new enumerator over the contents of the list</summary>
|
||||
/// <returns>The new list content enumerator</returns>
|
||||
public IEnumerator<TItem> GetEnumerator() {
|
||||
return this.typedList.GetEnumerator();
|
||||
}
|
||||
|
||||
#region IList<> implementation
|
||||
|
||||
/// <summary>Inserts an item into the list</summary>
|
||||
/// <param name="index">Zero-based index before which the item will be inserted</param>
|
||||
/// <param name="item">Item that will be inserted into the list</param>
|
||||
void IList<TItem>.Insert(int index, TItem item) {
|
||||
throw new NotSupportedException(
|
||||
"Inserting items is not supported by the read-only list"
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>Removes an item from the list</summary>
|
||||
/// <param name="index">Zero-based index of the item that will be removed</param>
|
||||
void IList<TItem>.RemoveAt(int index) {
|
||||
throw new NotSupportedException(
|
||||
"Removing items is not supported by the read-only list"
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>Accesses the list item with the specified index</summary>
|
||||
/// <param name="index">Zero-based index of the list item that will be accessed</param>
|
||||
TItem IList<TItem>.this[int index] {
|
||||
get { return this.typedList[index]; }
|
||||
set {
|
||||
throw new NotSupportedException(
|
||||
"Assigning items is not supported by the read-only list"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region ICollection<> implementation
|
||||
|
||||
/// <summary>Adds an item to the end of the list</summary>
|
||||
/// <param name="item">Item that will be added to the list</param>
|
||||
void ICollection<TItem>.Add(TItem item) {
|
||||
throw new NotSupportedException(
|
||||
"Adding items is not supported by the read-only list"
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>Removes all items from the List</summary>
|
||||
void ICollection<TItem>.Clear() {
|
||||
throw new NotSupportedException(
|
||||
"Clearing is not supported by the read-only list"
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>Removes the specified item from the list</summary>
|
||||
/// <param name="item">Item that will be removed from the list</param>
|
||||
/// <returns>True of the specified item was found in the list and removed</returns>
|
||||
bool ICollection<TItem>.Remove(TItem item) {
|
||||
throw new NotSupportedException(
|
||||
"Removing items is not supported by the read-only list"
|
||||
);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IEnumerable implementation
|
||||
|
||||
/// <summary>Returns a new enumerator over the contents of the list</summary>
|
||||
/// <returns>The new list content enumerator</returns>
|
||||
IEnumerator IEnumerable.GetEnumerator() {
|
||||
return this.objectList.GetEnumerator();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IList implementation
|
||||
|
||||
/// <summary>Removes all items from the list</summary>
|
||||
void IList.Clear() {
|
||||
throw new NotSupportedException(
|
||||
"Clearing is not supported by the read-only list"
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>Adds an item to the end of the list</summary>
|
||||
/// <param name="value">Item that will be added to the list</param>
|
||||
int IList.Add(object value) {
|
||||
throw new NotSupportedException(
|
||||
"Adding items is not supported by the read-only list"
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>Determines whether the List contains the specified item</summary>
|
||||
/// <param name="value">Item that will be checked for</param>
|
||||
/// <returns>True if the specified item is contained in the list</returns>
|
||||
bool IList.Contains(object value) {
|
||||
return this.objectList.Contains(value);
|
||||
}
|
||||
|
||||
/// <summary>Retrieves the index of an item within the list</summary>
|
||||
/// <param name="value">Item whose index will be returned</param>
|
||||
/// <returns>The zero-based index of the specified item in the list</returns>
|
||||
int IList.IndexOf(object value) {
|
||||
return this.objectList.IndexOf(value);
|
||||
}
|
||||
|
||||
/// <summary>Inserts an item into the list</summary>
|
||||
/// <param name="index">Zero-based index before which the item will be inserted</param>
|
||||
/// <param name="value">Item that will be inserted into the list</param>
|
||||
void IList.Insert(int index, object value) {
|
||||
throw new NotSupportedException(
|
||||
"Inserting items is not supported by the read-only list"
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>Whether the size of the list is fixed</summary>
|
||||
bool IList.IsFixedSize {
|
||||
get { return this.objectList.IsFixedSize; }
|
||||
}
|
||||
|
||||
/// <summary>Removes the specified item from the list</summary>
|
||||
/// <param name="value">Item that will be removed from the list</param>
|
||||
/// <returns>True of the specified item was found in the list and removed</returns>
|
||||
void IList.Remove(object value) {
|
||||
throw new NotSupportedException(
|
||||
"Removing items is not supported by the read-only list"
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>Removes an item from the list</summary>
|
||||
/// <param name="index">Zero-based index of the item that will be removed</param>
|
||||
void IList.RemoveAt(int index) {
|
||||
throw new NotSupportedException(
|
||||
"Removing items is not supported by the read-only list"
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>Accesses the list item with the specified index</summary>
|
||||
/// <param name="index">Zero-based index of the list item that will be accessed</param>
|
||||
object IList.this[int index] {
|
||||
get { return this.objectList[index]; }
|
||||
set {
|
||||
throw new NotSupportedException(
|
||||
"Assigning items is not supported by the read-only list"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region ICollection implementation
|
||||
|
||||
/// <summary>Copies the contents of the list into an array</summary>
|
||||
/// <param name="array">Array the list will be copied into</param>
|
||||
/// <param name="index">
|
||||
/// Starting index at which to begin filling the destination array
|
||||
/// </param>
|
||||
void ICollection.CopyTo(Array array, int index) {
|
||||
this.objectList.CopyTo(array, index);
|
||||
}
|
||||
|
||||
/// <summary>Whether the list is synchronized for multi-threaded usage</summary>
|
||||
bool ICollection.IsSynchronized {
|
||||
get { return this.objectList.IsSynchronized; }
|
||||
}
|
||||
|
||||
/// <summary>Synchronization root on which the list locks</summary>
|
||||
object ICollection.SyncRoot {
|
||||
get { return this.objectList.SyncRoot; }
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>The wrapped list under its type-safe interface</summary>
|
||||
private IList<TItem> typedList;
|
||||
/// <summary>The wrapped list under its object interface</summary>
|
||||
private IList objectList;
|
||||
|
||||
}
|
||||
|
||||
} // namespace Nuclex.Support.Collections
|
||||
#region Apache License 2.0
|
||||
/*
|
||||
Nuclex .NET Framework
|
||||
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
#endregion // Apache License 2.0
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Nuclex.Support.Collections {
|
||||
|
||||
/// <summary>Wraps a list and prevents users from modifying it</summary>
|
||||
/// <typeparam name="TItem">Type of items to manage in the set</typeparam>
|
||||
public class ReadOnlyList<TItem> : IList<TItem>, IList {
|
||||
|
||||
/// <summary>Initializes a new read-only List wrapper</summary>
|
||||
/// <param name="list">List that will be wrapped</param>
|
||||
public ReadOnlyList(IList<TItem> list) {
|
||||
this.typedList = list;
|
||||
this.objectList = (list as IList);
|
||||
}
|
||||
|
||||
/// <summary>Retrieves the index of an item within the List</summary>
|
||||
/// <param name="item">Item whose index will be returned</param>
|
||||
/// <returns>The zero-based index of the specified item in the List</returns>
|
||||
public int IndexOf(TItem item) {
|
||||
return this.typedList.IndexOf(item);
|
||||
}
|
||||
|
||||
/// <summary>Accesses the List item with the specified index</summary>
|
||||
/// <param name="index">Zero-based index of the List item that will be accessed</param>
|
||||
public TItem this[int index] {
|
||||
get { return this.typedList[index]; }
|
||||
}
|
||||
|
||||
/// <summary>Determines whether the List contains the specified item</summary>
|
||||
/// <param name="item">Item that will be checked for</param>
|
||||
/// <returns>True if the specified item is contained in the List</returns>
|
||||
public bool Contains(TItem item) {
|
||||
return this.typedList.Contains(item);
|
||||
}
|
||||
|
||||
/// <summary>Copies the contents of the List into an array</summary>
|
||||
/// <param name="array">Array the List will be copied into</param>
|
||||
/// <param name="arrayIndex">
|
||||
/// Starting index at which to begin filling the destination array
|
||||
/// </param>
|
||||
public void CopyTo(TItem[] array, int arrayIndex) {
|
||||
this.typedList.CopyTo(array, arrayIndex);
|
||||
}
|
||||
|
||||
/// <summary>The number of items current contained in the list</summary>
|
||||
public int Count {
|
||||
get { return this.typedList.Count; }
|
||||
}
|
||||
|
||||
/// <summary>Whether the list is write-protected</summary>
|
||||
public bool IsReadOnly {
|
||||
get { return true; }
|
||||
}
|
||||
|
||||
/// <summary>Returns a new enumerator over the contents of the list</summary>
|
||||
/// <returns>The new list content enumerator</returns>
|
||||
public IEnumerator<TItem> GetEnumerator() {
|
||||
return this.typedList.GetEnumerator();
|
||||
}
|
||||
|
||||
#region IList<> implementation
|
||||
|
||||
/// <summary>Inserts an item into the list</summary>
|
||||
/// <param name="index">Zero-based index before which the item will be inserted</param>
|
||||
/// <param name="item">Item that will be inserted into the list</param>
|
||||
void IList<TItem>.Insert(int index, TItem item) {
|
||||
throw new NotSupportedException(
|
||||
"Inserting items is not supported by the read-only list"
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>Removes an item from the list</summary>
|
||||
/// <param name="index">Zero-based index of the item that will be removed</param>
|
||||
void IList<TItem>.RemoveAt(int index) {
|
||||
throw new NotSupportedException(
|
||||
"Removing items is not supported by the read-only list"
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>Accesses the list item with the specified index</summary>
|
||||
/// <param name="index">Zero-based index of the list item that will be accessed</param>
|
||||
TItem IList<TItem>.this[int index] {
|
||||
get { return this.typedList[index]; }
|
||||
set {
|
||||
throw new NotSupportedException(
|
||||
"Assigning items is not supported by the read-only list"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region ICollection<> implementation
|
||||
|
||||
/// <summary>Adds an item to the end of the list</summary>
|
||||
/// <param name="item">Item that will be added to the list</param>
|
||||
void ICollection<TItem>.Add(TItem item) {
|
||||
throw new NotSupportedException(
|
||||
"Adding items is not supported by the read-only list"
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>Removes all items from the List</summary>
|
||||
void ICollection<TItem>.Clear() {
|
||||
throw new NotSupportedException(
|
||||
"Clearing is not supported by the read-only list"
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>Removes the specified item from the list</summary>
|
||||
/// <param name="item">Item that will be removed from the list</param>
|
||||
/// <returns>True of the specified item was found in the list and removed</returns>
|
||||
bool ICollection<TItem>.Remove(TItem item) {
|
||||
throw new NotSupportedException(
|
||||
"Removing items is not supported by the read-only list"
|
||||
);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IEnumerable implementation
|
||||
|
||||
/// <summary>Returns a new enumerator over the contents of the list</summary>
|
||||
/// <returns>The new list content enumerator</returns>
|
||||
IEnumerator IEnumerable.GetEnumerator() {
|
||||
return this.objectList.GetEnumerator();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IList implementation
|
||||
|
||||
/// <summary>Removes all items from the list</summary>
|
||||
void IList.Clear() {
|
||||
throw new NotSupportedException(
|
||||
"Clearing is not supported by the read-only list"
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>Adds an item to the end of the list</summary>
|
||||
/// <param name="value">Item that will be added to the list</param>
|
||||
int IList.Add(object value) {
|
||||
throw new NotSupportedException(
|
||||
"Adding items is not supported by the read-only list"
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>Determines whether the List contains the specified item</summary>
|
||||
/// <param name="value">Item that will be checked for</param>
|
||||
/// <returns>True if the specified item is contained in the list</returns>
|
||||
bool IList.Contains(object value) {
|
||||
return this.objectList.Contains(value);
|
||||
}
|
||||
|
||||
/// <summary>Retrieves the index of an item within the list</summary>
|
||||
/// <param name="value">Item whose index will be returned</param>
|
||||
/// <returns>The zero-based index of the specified item in the list</returns>
|
||||
int IList.IndexOf(object value) {
|
||||
return this.objectList.IndexOf(value);
|
||||
}
|
||||
|
||||
/// <summary>Inserts an item into the list</summary>
|
||||
/// <param name="index">Zero-based index before which the item will be inserted</param>
|
||||
/// <param name="value">Item that will be inserted into the list</param>
|
||||
void IList.Insert(int index, object value) {
|
||||
throw new NotSupportedException(
|
||||
"Inserting items is not supported by the read-only list"
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>Whether the size of the list is fixed</summary>
|
||||
bool IList.IsFixedSize {
|
||||
get { return this.objectList.IsFixedSize; }
|
||||
}
|
||||
|
||||
/// <summary>Removes the specified item from the list</summary>
|
||||
/// <param name="value">Item that will be removed from the list</param>
|
||||
/// <returns>True of the specified item was found in the list and removed</returns>
|
||||
void IList.Remove(object value) {
|
||||
throw new NotSupportedException(
|
||||
"Removing items is not supported by the read-only list"
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>Removes an item from the list</summary>
|
||||
/// <param name="index">Zero-based index of the item that will be removed</param>
|
||||
void IList.RemoveAt(int index) {
|
||||
throw new NotSupportedException(
|
||||
"Removing items is not supported by the read-only list"
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>Accesses the list item with the specified index</summary>
|
||||
/// <param name="index">Zero-based index of the list item that will be accessed</param>
|
||||
object IList.this[int index] {
|
||||
get { return this.objectList[index]; }
|
||||
set {
|
||||
throw new NotSupportedException(
|
||||
"Assigning items is not supported by the read-only list"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region ICollection implementation
|
||||
|
||||
/// <summary>Copies the contents of the list into an array</summary>
|
||||
/// <param name="array">Array the list will be copied into</param>
|
||||
/// <param name="index">
|
||||
/// Starting index at which to begin filling the destination array
|
||||
/// </param>
|
||||
void ICollection.CopyTo(Array array, int index) {
|
||||
this.objectList.CopyTo(array, index);
|
||||
}
|
||||
|
||||
/// <summary>Whether the list is synchronized for multi-threaded usage</summary>
|
||||
bool ICollection.IsSynchronized {
|
||||
get { return this.objectList.IsSynchronized; }
|
||||
}
|
||||
|
||||
/// <summary>Synchronization root on which the list locks</summary>
|
||||
object ICollection.SyncRoot {
|
||||
get { return this.objectList.SyncRoot; }
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>The wrapped list under its type-safe interface</summary>
|
||||
private IList<TItem> typedList;
|
||||
/// <summary>The wrapped list under its object interface</summary>
|
||||
private IList objectList;
|
||||
|
||||
}
|
||||
|
||||
} // namespace Nuclex.Support.Collections
|
||||
|
|
|
|||
|
|
@ -1,208 +1,207 @@
|
|||
#region CPL License
|
||||
/*
|
||||
Nuclex Framework
|
||||
Copyright (C) 2002-2017 Nuclex Development Labs
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the IBM Common Public License as
|
||||
published by the IBM Corporation; either version 1.0 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
IBM Common Public License for more details.
|
||||
|
||||
You should have received a copy of the IBM Common Public
|
||||
License along with this library
|
||||
*/
|
||||
#endregion
|
||||
|
||||
#if !NO_SETS
|
||||
|
||||
#if UNITTEST
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Runtime.Serialization.Formatters.Binary;
|
||||
|
||||
using NUnit.Framework;
|
||||
using NMock;
|
||||
|
||||
namespace Nuclex.Support.Collections {
|
||||
|
||||
/// <summary>Unit Test for the observable set wrapper</summary>
|
||||
[TestFixture]
|
||||
internal class ReadOnlySetTest {
|
||||
|
||||
/// <summary>Called before each test is run</summary>
|
||||
[SetUp]
|
||||
public void Setup() {
|
||||
this.set = new HashSet<int>();
|
||||
this.readOnlySet = new ReadOnlySet<int>(this.set);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that the observable set has a default constructor
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void HasDefaultConstructor() {
|
||||
Assert.IsNotNull(new ReadOnlySet<int>(new HashSet<int>()));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that an exception is thrown upon any attempt to add items
|
||||
/// to a read-only set
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void AddingThrowsException() {
|
||||
Assert.Throws<NotSupportedException>(
|
||||
delegate() { ((ISet<int>)this.readOnlySet).Add(123); }
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that an exception is thrown upon any attempt to remove items
|
||||
/// from a read-only set
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void RemovingThrowsException() {
|
||||
Assert.Throws<NotSupportedException>(
|
||||
delegate() { ((ISet<int>)this.readOnlySet).Remove(123); }
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that an exception is thrown upon any attempt to except
|
||||
/// the set with another set
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void ExceptingThrowsException() {
|
||||
Assert.Throws<NotSupportedException>(
|
||||
delegate() { ((ISet<int>)this.readOnlySet).ExceptWith(null); }
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that an exception is thrown upon any attempt to intersect
|
||||
/// the set with another set
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void InsersectThrowsException() {
|
||||
Assert.Throws<NotSupportedException>(
|
||||
delegate() { ((ISet<int>)this.readOnlySet).IntersectWith(null); }
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that it's possible to determine whether a set is a proper subset
|
||||
/// or superset of another set
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void CanDetermineProperSubsetAndSuperset() {
|
||||
this.set.Add(1);
|
||||
this.set.Add(2);
|
||||
this.set.Add(3);
|
||||
|
||||
var set2 = new HashSet<int>() { 1, 3 };
|
||||
|
||||
Assert.IsTrue(this.readOnlySet.IsProperSupersetOf(set2));
|
||||
Assert.IsTrue(set2.IsProperSubsetOf(this.readOnlySet));
|
||||
|
||||
set2.Add(2);
|
||||
|
||||
Assert.IsFalse(this.readOnlySet.IsProperSupersetOf(set2));
|
||||
Assert.IsFalse(set2.IsProperSubsetOf(this.readOnlySet));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that it's possible to determine whether a set is a subset
|
||||
/// or a superset of another set
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void CanDetermineSubsetAndSuperset() {
|
||||
this.set.Add(1);
|
||||
this.set.Add(2);
|
||||
this.set.Add(3);
|
||||
|
||||
var set2 = new HashSet<int>() { 1, 2, 3 };
|
||||
|
||||
Assert.IsTrue(this.readOnlySet.IsSupersetOf(set2));
|
||||
Assert.IsTrue(set2.IsSubsetOf(this.readOnlySet));
|
||||
|
||||
set2.Add(4);
|
||||
|
||||
Assert.IsFalse(this.readOnlySet.IsSupersetOf(set2));
|
||||
Assert.IsFalse(set2.IsSubsetOf(this.readOnlySet));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that a set can determine if another set overlaps with it
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void CanDetermineOverlap() {
|
||||
this.set.Add(1);
|
||||
this.set.Add(3);
|
||||
this.set.Add(5);
|
||||
|
||||
var set2 = new HashSet<int>() { 3 };
|
||||
|
||||
Assert.IsTrue(this.readOnlySet.Overlaps(set2));
|
||||
Assert.IsTrue(set2.Overlaps(this.readOnlySet));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that a set can determine if another set contains the same elements
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void CanDetermineSetEquality() {
|
||||
this.set.Add(1);
|
||||
this.set.Add(3);
|
||||
this.set.Add(5);
|
||||
|
||||
var set2 = new HashSet<int>() { 3, 1, 5 };
|
||||
|
||||
Assert.IsTrue(this.readOnlySet.SetEquals(set2));
|
||||
Assert.IsTrue(set2.SetEquals(this.readOnlySet));
|
||||
|
||||
this.set.Add(7);
|
||||
|
||||
Assert.IsFalse(this.readOnlySet.SetEquals(set2));
|
||||
Assert.IsFalse(set2.SetEquals(this.readOnlySet));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that any attempt to symmetrically except a read-only set
|
||||
/// causes an exception
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void SymmetricallyExceptingThrowsException() {
|
||||
Assert.Throws<NotSupportedException>(
|
||||
delegate() { ((ISet<int>)this.readOnlySet).SymmetricExceptWith(null); }
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that any attempt to union a read-only set causes an exception
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void UnioningThrowsException() {
|
||||
Assert.Throws<NotSupportedException>(
|
||||
delegate() { ((ISet<int>)this.readOnlySet).UnionWith(null); }
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>Set being wrapped in a read-only set</summary>
|
||||
private ISet<int> set;
|
||||
/// <summary>Read-only wrapper around the set</summary>
|
||||
private ReadOnlySet<int> readOnlySet;
|
||||
|
||||
}
|
||||
|
||||
} // namespace Nuclex.Support.Collections
|
||||
|
||||
#endif // UNITTEST
|
||||
|
||||
#endif // !NO_SETS
|
||||
#region Apache License 2.0
|
||||
/*
|
||||
Nuclex .NET Framework
|
||||
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
#endregion // Apache License 2.0
|
||||
|
||||
#if !NO_SETS
|
||||
|
||||
#if UNITTEST
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Runtime.Serialization.Formatters.Binary;
|
||||
|
||||
using NUnit.Framework;
|
||||
using NMock;
|
||||
|
||||
namespace Nuclex.Support.Collections {
|
||||
|
||||
/// <summary>Unit Test for the observable set wrapper</summary>
|
||||
[TestFixture]
|
||||
internal class ReadOnlySetTest {
|
||||
|
||||
/// <summary>Called before each test is run</summary>
|
||||
[SetUp]
|
||||
public void Setup() {
|
||||
this.set = new HashSet<int>();
|
||||
this.readOnlySet = new ReadOnlySet<int>(this.set);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that the observable set has a default constructor
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void HasDefaultConstructor() {
|
||||
Assert.IsNotNull(new ReadOnlySet<int>(new HashSet<int>()));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that an exception is thrown upon any attempt to add items
|
||||
/// to a read-only set
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void AddingThrowsException() {
|
||||
Assert.Throws<NotSupportedException>(
|
||||
delegate() { ((ISet<int>)this.readOnlySet).Add(123); }
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that an exception is thrown upon any attempt to remove items
|
||||
/// from a read-only set
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void RemovingThrowsException() {
|
||||
Assert.Throws<NotSupportedException>(
|
||||
delegate() { ((ISet<int>)this.readOnlySet).Remove(123); }
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that an exception is thrown upon any attempt to except
|
||||
/// the set with another set
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void ExceptingThrowsException() {
|
||||
Assert.Throws<NotSupportedException>(
|
||||
delegate() { ((ISet<int>)this.readOnlySet).ExceptWith(null); }
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that an exception is thrown upon any attempt to intersect
|
||||
/// the set with another set
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void InsersectThrowsException() {
|
||||
Assert.Throws<NotSupportedException>(
|
||||
delegate() { ((ISet<int>)this.readOnlySet).IntersectWith(null); }
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that it's possible to determine whether a set is a proper subset
|
||||
/// or superset of another set
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void CanDetermineProperSubsetAndSuperset() {
|
||||
this.set.Add(1);
|
||||
this.set.Add(2);
|
||||
this.set.Add(3);
|
||||
|
||||
var set2 = new HashSet<int>() { 1, 3 };
|
||||
|
||||
Assert.IsTrue(this.readOnlySet.IsProperSupersetOf(set2));
|
||||
Assert.IsTrue(set2.IsProperSubsetOf(this.readOnlySet));
|
||||
|
||||
set2.Add(2);
|
||||
|
||||
Assert.IsFalse(this.readOnlySet.IsProperSupersetOf(set2));
|
||||
Assert.IsFalse(set2.IsProperSubsetOf(this.readOnlySet));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that it's possible to determine whether a set is a subset
|
||||
/// or a superset of another set
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void CanDetermineSubsetAndSuperset() {
|
||||
this.set.Add(1);
|
||||
this.set.Add(2);
|
||||
this.set.Add(3);
|
||||
|
||||
var set2 = new HashSet<int>() { 1, 2, 3 };
|
||||
|
||||
Assert.IsTrue(this.readOnlySet.IsSupersetOf(set2));
|
||||
Assert.IsTrue(set2.IsSubsetOf(this.readOnlySet));
|
||||
|
||||
set2.Add(4);
|
||||
|
||||
Assert.IsFalse(this.readOnlySet.IsSupersetOf(set2));
|
||||
Assert.IsFalse(set2.IsSubsetOf(this.readOnlySet));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that a set can determine if another set overlaps with it
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void CanDetermineOverlap() {
|
||||
this.set.Add(1);
|
||||
this.set.Add(3);
|
||||
this.set.Add(5);
|
||||
|
||||
var set2 = new HashSet<int>() { 3 };
|
||||
|
||||
Assert.IsTrue(this.readOnlySet.Overlaps(set2));
|
||||
Assert.IsTrue(set2.Overlaps(this.readOnlySet));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that a set can determine if another set contains the same elements
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void CanDetermineSetEquality() {
|
||||
this.set.Add(1);
|
||||
this.set.Add(3);
|
||||
this.set.Add(5);
|
||||
|
||||
var set2 = new HashSet<int>() { 3, 1, 5 };
|
||||
|
||||
Assert.IsTrue(this.readOnlySet.SetEquals(set2));
|
||||
Assert.IsTrue(set2.SetEquals(this.readOnlySet));
|
||||
|
||||
this.set.Add(7);
|
||||
|
||||
Assert.IsFalse(this.readOnlySet.SetEquals(set2));
|
||||
Assert.IsFalse(set2.SetEquals(this.readOnlySet));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that any attempt to symmetrically except a read-only set
|
||||
/// causes an exception
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void SymmetricallyExceptingThrowsException() {
|
||||
Assert.Throws<NotSupportedException>(
|
||||
delegate() { ((ISet<int>)this.readOnlySet).SymmetricExceptWith(null); }
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that any attempt to union a read-only set causes an exception
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void UnioningThrowsException() {
|
||||
Assert.Throws<NotSupportedException>(
|
||||
delegate() { ((ISet<int>)this.readOnlySet).UnionWith(null); }
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>Set being wrapped in a read-only set</summary>
|
||||
private ISet<int> set;
|
||||
/// <summary>Read-only wrapper around the set</summary>
|
||||
private ReadOnlySet<int> readOnlySet;
|
||||
|
||||
}
|
||||
|
||||
} // namespace Nuclex.Support.Collections
|
||||
|
||||
#endif // UNITTEST
|
||||
|
||||
#endif // !NO_SETS
|
||||
|
|
|
|||
|
|
@ -1,216 +1,215 @@
|
|||
#region CPL License
|
||||
/*
|
||||
Nuclex Framework
|
||||
Copyright (C) 2002-2017 Nuclex Development Labs
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the IBM Common Public License as
|
||||
published by the IBM Corporation; either version 1.0 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
IBM Common Public License for more details.
|
||||
|
||||
You should have received a copy of the IBM Common Public
|
||||
License along with this library
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
#if !NO_SETS
|
||||
|
||||
namespace Nuclex.Support.Collections {
|
||||
|
||||
/// <summary>Wraps a set and prevents it from being modified</summary>
|
||||
/// <typeparam name="TItem">Type of items to manage in the set</typeparam>
|
||||
public class ReadOnlySet<TItem> : ISet<TItem>, ICollection<TItem> {
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new observable set forwarding operations to the specified set
|
||||
/// </summary>
|
||||
/// <param name="set">Set operations will be forwarded to</param>
|
||||
public ReadOnlySet(ISet<TItem> set) {
|
||||
this.set = set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the current set is a proper (strict) subset of a collection
|
||||
/// </summary>
|
||||
/// <param name="other">Collection against which the set will be tested</param>
|
||||
/// <returns>True if the set is a proper subset of the specified collection</returns>
|
||||
public bool IsProperSubsetOf(IEnumerable<TItem> other) {
|
||||
return this.set.IsProperSubsetOf(other);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the current set is a proper (strict) superset of a collection
|
||||
/// </summary>
|
||||
/// <param name="other">Collection against which the set will be tested</param>
|
||||
/// <returns>True if the set is a proper superset of the specified collection</returns>
|
||||
public bool IsProperSupersetOf(IEnumerable<TItem> other) {
|
||||
return this.set.IsProperSupersetOf(other);
|
||||
}
|
||||
|
||||
/// <summary>Determines whether the current set is a subset of a collection</summary>
|
||||
/// <param name="other">Collection against which the set will be tested</param>
|
||||
/// <returns>True if the set is a subset of the specified collection</returns>
|
||||
public bool IsSubsetOf(IEnumerable<TItem> other) {
|
||||
return this.set.IsSubsetOf(other);
|
||||
}
|
||||
|
||||
/// <summary>Determines whether the current set is a superset of a collection</summary>
|
||||
/// <param name="other">Collection against which the set will be tested</param>
|
||||
/// <returns>True if the set is a superset of the specified collection</returns>
|
||||
public bool IsSupersetOf(IEnumerable<TItem> other) {
|
||||
return this.set.IsSupersetOf(other);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines if the set shares at least one common element with the collection
|
||||
/// </summary>
|
||||
/// <param name="other">Collection the set will be tested against</param>
|
||||
/// <returns>
|
||||
/// True if the set shares at least one common element with the collection
|
||||
/// </returns>
|
||||
public bool Overlaps(IEnumerable<TItem> other) {
|
||||
return this.set.Overlaps(other);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the set contains the same elements as the specified collection
|
||||
/// </summary>
|
||||
/// <param name="other">Collection the set will be tested against</param>
|
||||
/// <returns>True if the set contains the same elements as the collection</returns>
|
||||
public bool SetEquals(IEnumerable<TItem> other) {
|
||||
return this.set.SetEquals(other);
|
||||
}
|
||||
|
||||
/// <summary>Determines whether the set contains the specified item</summary>
|
||||
/// <param name="item">Item the set will be tested for</param>
|
||||
/// <returns>True if the set contains the specified item</returns>
|
||||
public bool Contains(TItem item) {
|
||||
return this.set.Contains(item);
|
||||
}
|
||||
|
||||
/// <summary>Copies the contents of the set into an array</summary>
|
||||
/// <param name="array">Array the set's contents will be copied to</param>
|
||||
/// <param name="arrayIndex">
|
||||
/// Index in the array the first copied element will be written to
|
||||
/// </param>
|
||||
public void CopyTo(TItem[] array, int arrayIndex) {
|
||||
this.set.CopyTo(array, arrayIndex);
|
||||
}
|
||||
|
||||
/// <summary>Counts the number of items contained in the set</summary>
|
||||
public int Count {
|
||||
get { return this.set.Count; }
|
||||
}
|
||||
|
||||
/// <summary>Determines whether the set is readonly</summary>
|
||||
public bool IsReadOnly {
|
||||
get { return true; }
|
||||
}
|
||||
|
||||
|
||||
/// <summary>Creates an enumerator for the set's contents</summary>
|
||||
/// <returns>A new enumerator for the sets contents</returns>
|
||||
public IEnumerator<TItem> GetEnumerator() {
|
||||
return this.set.GetEnumerator();
|
||||
}
|
||||
|
||||
/// <summary>Creates an enumerator for the set's contents</summary>
|
||||
/// <returns>A new enumerator for the sets contents</returns>
|
||||
IEnumerator IEnumerable.GetEnumerator() {
|
||||
return this.set.GetEnumerator();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Modifies the current set so that it contains only elements that are present either
|
||||
/// in the current set or in the specified collection, but not both
|
||||
/// </summary>
|
||||
/// <param name="other">Collection the set will be excepted with</param>
|
||||
void ISet<TItem>.SymmetricExceptWith(IEnumerable<TItem> other) {
|
||||
throw new NotSupportedException(
|
||||
"Excepting is not supported by the read-only set"
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Modifies the current set so that it contains all elements that are present in both
|
||||
/// the current set and in the specified collection
|
||||
/// </summary>
|
||||
/// <param name="other">Collection an union will be built with</param>
|
||||
void ISet<TItem>.UnionWith(IEnumerable<TItem> other) {
|
||||
throw new NotSupportedException(
|
||||
"Unioning is not supported by the read-only set"
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>Removes all items from the set</summary>
|
||||
public void Clear() {
|
||||
throw new NotSupportedException(
|
||||
"Clearing is not supported by the read-only set"
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>Removes an item from the set</summary>
|
||||
/// <param name="item">Item that will be removed from the set</param>
|
||||
/// <returns>
|
||||
/// True if the item was contained in the set and is now removed
|
||||
/// </returns>
|
||||
bool ICollection<TItem>.Remove(TItem item) {
|
||||
throw new NotSupportedException(
|
||||
"Removing items is not supported by the read-only set"
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>Adds an item to the set</summary>
|
||||
/// <param name="item">Item that will be added to the set</param>
|
||||
/// <returns>
|
||||
/// True if the element was added, false if it was already contained in the set
|
||||
/// </returns>
|
||||
bool ISet<TItem>.Add(TItem item) {
|
||||
throw new NotSupportedException(
|
||||
"Adding items is not supported by the read-only set"
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>Removes all elements that are contained in the collection</summary>
|
||||
/// <param name="other">Collection whose elements will be removed from this set</param>
|
||||
void ISet<TItem>.ExceptWith(IEnumerable<TItem> other) {
|
||||
throw new NotSupportedException(
|
||||
"Excepting items is not supported by the read-only set"
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Only keeps those elements in this set that are contained in the collection
|
||||
/// </summary>
|
||||
/// <param name="other">Other set this set will be filtered by</param>
|
||||
void ISet<TItem>.IntersectWith(IEnumerable<TItem> other) {
|
||||
throw new NotSupportedException(
|
||||
"Intersecting items is not supported by the read-only set"
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>Adds an item to the set</summary>
|
||||
/// <param name="item">Item that will be added to the set</param>
|
||||
void ICollection<TItem>.Add(TItem item) {
|
||||
throw new NotSupportedException(
|
||||
"Adding is not supported by the read-only set"
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>The set being wrapped</summary>
|
||||
private ISet<TItem> set;
|
||||
|
||||
}
|
||||
|
||||
} // namespace Nuclex.Support.Collections
|
||||
|
||||
#endif // !NO_SETS
|
||||
#region Apache License 2.0
|
||||
/*
|
||||
Nuclex .NET Framework
|
||||
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
#endregion // Apache License 2.0
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
#if !NO_SETS
|
||||
|
||||
namespace Nuclex.Support.Collections {
|
||||
|
||||
/// <summary>Wraps a set and prevents it from being modified</summary>
|
||||
/// <typeparam name="TItem">Type of items to manage in the set</typeparam>
|
||||
public class ReadOnlySet<TItem> : ISet<TItem>, ICollection<TItem> {
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new observable set forwarding operations to the specified set
|
||||
/// </summary>
|
||||
/// <param name="set">Set operations will be forwarded to</param>
|
||||
public ReadOnlySet(ISet<TItem> set) {
|
||||
this.set = set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the current set is a proper (strict) subset of a collection
|
||||
/// </summary>
|
||||
/// <param name="other">Collection against which the set will be tested</param>
|
||||
/// <returns>True if the set is a proper subset of the specified collection</returns>
|
||||
public bool IsProperSubsetOf(IEnumerable<TItem> other) {
|
||||
return this.set.IsProperSubsetOf(other);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the current set is a proper (strict) superset of a collection
|
||||
/// </summary>
|
||||
/// <param name="other">Collection against which the set will be tested</param>
|
||||
/// <returns>True if the set is a proper superset of the specified collection</returns>
|
||||
public bool IsProperSupersetOf(IEnumerable<TItem> other) {
|
||||
return this.set.IsProperSupersetOf(other);
|
||||
}
|
||||
|
||||
/// <summary>Determines whether the current set is a subset of a collection</summary>
|
||||
/// <param name="other">Collection against which the set will be tested</param>
|
||||
/// <returns>True if the set is a subset of the specified collection</returns>
|
||||
public bool IsSubsetOf(IEnumerable<TItem> other) {
|
||||
return this.set.IsSubsetOf(other);
|
||||
}
|
||||
|
||||
/// <summary>Determines whether the current set is a superset of a collection</summary>
|
||||
/// <param name="other">Collection against which the set will be tested</param>
|
||||
/// <returns>True if the set is a superset of the specified collection</returns>
|
||||
public bool IsSupersetOf(IEnumerable<TItem> other) {
|
||||
return this.set.IsSupersetOf(other);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines if the set shares at least one common element with the collection
|
||||
/// </summary>
|
||||
/// <param name="other">Collection the set will be tested against</param>
|
||||
/// <returns>
|
||||
/// True if the set shares at least one common element with the collection
|
||||
/// </returns>
|
||||
public bool Overlaps(IEnumerable<TItem> other) {
|
||||
return this.set.Overlaps(other);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the set contains the same elements as the specified collection
|
||||
/// </summary>
|
||||
/// <param name="other">Collection the set will be tested against</param>
|
||||
/// <returns>True if the set contains the same elements as the collection</returns>
|
||||
public bool SetEquals(IEnumerable<TItem> other) {
|
||||
return this.set.SetEquals(other);
|
||||
}
|
||||
|
||||
/// <summary>Determines whether the set contains the specified item</summary>
|
||||
/// <param name="item">Item the set will be tested for</param>
|
||||
/// <returns>True if the set contains the specified item</returns>
|
||||
public bool Contains(TItem item) {
|
||||
return this.set.Contains(item);
|
||||
}
|
||||
|
||||
/// <summary>Copies the contents of the set into an array</summary>
|
||||
/// <param name="array">Array the set's contents will be copied to</param>
|
||||
/// <param name="arrayIndex">
|
||||
/// Index in the array the first copied element will be written to
|
||||
/// </param>
|
||||
public void CopyTo(TItem[] array, int arrayIndex) {
|
||||
this.set.CopyTo(array, arrayIndex);
|
||||
}
|
||||
|
||||
/// <summary>Counts the number of items contained in the set</summary>
|
||||
public int Count {
|
||||
get { return this.set.Count; }
|
||||
}
|
||||
|
||||
/// <summary>Determines whether the set is readonly</summary>
|
||||
public bool IsReadOnly {
|
||||
get { return true; }
|
||||
}
|
||||
|
||||
|
||||
/// <summary>Creates an enumerator for the set's contents</summary>
|
||||
/// <returns>A new enumerator for the sets contents</returns>
|
||||
public IEnumerator<TItem> GetEnumerator() {
|
||||
return this.set.GetEnumerator();
|
||||
}
|
||||
|
||||
/// <summary>Creates an enumerator for the set's contents</summary>
|
||||
/// <returns>A new enumerator for the sets contents</returns>
|
||||
IEnumerator IEnumerable.GetEnumerator() {
|
||||
return this.set.GetEnumerator();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Modifies the current set so that it contains only elements that are present either
|
||||
/// in the current set or in the specified collection, but not both
|
||||
/// </summary>
|
||||
/// <param name="other">Collection the set will be excepted with</param>
|
||||
void ISet<TItem>.SymmetricExceptWith(IEnumerable<TItem> other) {
|
||||
throw new NotSupportedException(
|
||||
"Excepting is not supported by the read-only set"
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Modifies the current set so that it contains all elements that are present in both
|
||||
/// the current set and in the specified collection
|
||||
/// </summary>
|
||||
/// <param name="other">Collection an union will be built with</param>
|
||||
void ISet<TItem>.UnionWith(IEnumerable<TItem> other) {
|
||||
throw new NotSupportedException(
|
||||
"Unioning is not supported by the read-only set"
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>Removes all items from the set</summary>
|
||||
public void Clear() {
|
||||
throw new NotSupportedException(
|
||||
"Clearing is not supported by the read-only set"
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>Removes an item from the set</summary>
|
||||
/// <param name="item">Item that will be removed from the set</param>
|
||||
/// <returns>
|
||||
/// True if the item was contained in the set and is now removed
|
||||
/// </returns>
|
||||
bool ICollection<TItem>.Remove(TItem item) {
|
||||
throw new NotSupportedException(
|
||||
"Removing items is not supported by the read-only set"
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>Adds an item to the set</summary>
|
||||
/// <param name="item">Item that will be added to the set</param>
|
||||
/// <returns>
|
||||
/// True if the element was added, false if it was already contained in the set
|
||||
/// </returns>
|
||||
bool ISet<TItem>.Add(TItem item) {
|
||||
throw new NotSupportedException(
|
||||
"Adding items is not supported by the read-only set"
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>Removes all elements that are contained in the collection</summary>
|
||||
/// <param name="other">Collection whose elements will be removed from this set</param>
|
||||
void ISet<TItem>.ExceptWith(IEnumerable<TItem> other) {
|
||||
throw new NotSupportedException(
|
||||
"Excepting items is not supported by the read-only set"
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Only keeps those elements in this set that are contained in the collection
|
||||
/// </summary>
|
||||
/// <param name="other">Other set this set will be filtered by</param>
|
||||
void ISet<TItem>.IntersectWith(IEnumerable<TItem> other) {
|
||||
throw new NotSupportedException(
|
||||
"Intersecting items is not supported by the read-only set"
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>Adds an item to the set</summary>
|
||||
/// <param name="item">Item that will be added to the set</param>
|
||||
void ICollection<TItem>.Add(TItem item) {
|
||||
throw new NotSupportedException(
|
||||
"Adding is not supported by the read-only set"
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>The set being wrapped</summary>
|
||||
private ISet<TItem> set;
|
||||
|
||||
}
|
||||
|
||||
} // namespace Nuclex.Support.Collections
|
||||
|
||||
#endif // !NO_SETS
|
||||
|
|
|
|||
|
|
@ -1,117 +1,116 @@
|
|||
#region CPL License
|
||||
/*
|
||||
Nuclex Framework
|
||||
Copyright (C) 2002-2017 Nuclex Development Labs
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the IBM Common Public License as
|
||||
published by the IBM Corporation; either version 1.0 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
IBM Common Public License for more details.
|
||||
|
||||
You should have received a copy of the IBM Common Public
|
||||
License along with this library
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
#if UNITTEST
|
||||
|
||||
using NUnit.Framework;
|
||||
|
||||
namespace Nuclex.Support.Collections {
|
||||
|
||||
/// <summary>Unit Test for the ReverseComparer helper class</summary>
|
||||
[TestFixture]
|
||||
internal class ReverseComparerTest {
|
||||
|
||||
#region class FortyTwoComparer
|
||||
|
||||
/// <summary>Special comparer in which 42 is larger than everything</summary>
|
||||
private class FortyTwoComparer : IComparer<int> {
|
||||
|
||||
/// <summary>Compares the left value to the right value</summary>
|
||||
/// <param name="left">Value on the left side</param>
|
||||
/// <param name="right">Value on the right side</param>
|
||||
/// <returns>The relationship of the two values</returns>
|
||||
public int Compare(int left, int right) {
|
||||
|
||||
// Is there a 42 in the arguments?
|
||||
if(left == 42) {
|
||||
if(right == 42) {
|
||||
return 0; // both are equal
|
||||
} else {
|
||||
return +1; // left is larger
|
||||
}
|
||||
} else if(right == 42) {
|
||||
return -1; // right is larger
|
||||
}
|
||||
|
||||
// No 42 encountered, proceed as normal
|
||||
return Math.Sign(left - right);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endregion // class FortyTwoComparer
|
||||
|
||||
/// <summary>
|
||||
/// Tests whether the default constructor of the reverse comparer works
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestDefaultConstructor() {
|
||||
new ReverseComparer<int>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests whether the full constructor of the reverse comparer works
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestFullConstructor() {
|
||||
new ReverseComparer<int>(new FortyTwoComparer());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests whether the full constructor of the reverse comparer works
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestReversedDefaultComparer() {
|
||||
Comparer<int> comparer = Comparer<int>.Default;
|
||||
ReverseComparer<int> reverseComparer = new ReverseComparer<int>(comparer);
|
||||
|
||||
Assert.Greater(0, comparer.Compare(10, 20));
|
||||
Assert.Less(0, comparer.Compare(20, 10));
|
||||
|
||||
Assert.Less(0, reverseComparer.Compare(10, 20));
|
||||
Assert.Greater(0, reverseComparer.Compare(20, 10));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests whether the full constructor of the reverse comparer works
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestReversedCustomComparer() {
|
||||
FortyTwoComparer fortyTwoComparer = new FortyTwoComparer();
|
||||
ReverseComparer<int> reverseFortyTwoComparer = new ReverseComparer<int>(
|
||||
fortyTwoComparer
|
||||
);
|
||||
|
||||
Assert.Less(0, fortyTwoComparer.Compare(42, 84));
|
||||
Assert.Greater(0, fortyTwoComparer.Compare(84, 42));
|
||||
|
||||
Assert.Greater(0, reverseFortyTwoComparer.Compare(42, 84));
|
||||
Assert.Less(0, reverseFortyTwoComparer.Compare(84, 42));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} // namespace Nuclex.Support.Collections
|
||||
|
||||
#endif // UNITTEST
|
||||
#region Apache License 2.0
|
||||
/*
|
||||
Nuclex .NET Framework
|
||||
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
#endregion // Apache License 2.0
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
#if UNITTEST
|
||||
|
||||
using NUnit.Framework;
|
||||
|
||||
namespace Nuclex.Support.Collections {
|
||||
|
||||
/// <summary>Unit Test for the ReverseComparer helper class</summary>
|
||||
[TestFixture]
|
||||
internal class ReverseComparerTest {
|
||||
|
||||
#region class FortyTwoComparer
|
||||
|
||||
/// <summary>Special comparer in which 42 is larger than everything</summary>
|
||||
private class FortyTwoComparer : IComparer<int> {
|
||||
|
||||
/// <summary>Compares the left value to the right value</summary>
|
||||
/// <param name="left">Value on the left side</param>
|
||||
/// <param name="right">Value on the right side</param>
|
||||
/// <returns>The relationship of the two values</returns>
|
||||
public int Compare(int left, int right) {
|
||||
|
||||
// Is there a 42 in the arguments?
|
||||
if(left == 42) {
|
||||
if(right == 42) {
|
||||
return 0; // both are equal
|
||||
} else {
|
||||
return +1; // left is larger
|
||||
}
|
||||
} else if(right == 42) {
|
||||
return -1; // right is larger
|
||||
}
|
||||
|
||||
// No 42 encountered, proceed as normal
|
||||
return Math.Sign(left - right);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endregion // class FortyTwoComparer
|
||||
|
||||
/// <summary>
|
||||
/// Tests whether the default constructor of the reverse comparer works
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestDefaultConstructor() {
|
||||
new ReverseComparer<int>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests whether the full constructor of the reverse comparer works
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestFullConstructor() {
|
||||
new ReverseComparer<int>(new FortyTwoComparer());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests whether the full constructor of the reverse comparer works
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestReversedDefaultComparer() {
|
||||
Comparer<int> comparer = Comparer<int>.Default;
|
||||
ReverseComparer<int> reverseComparer = new ReverseComparer<int>(comparer);
|
||||
|
||||
Assert.Greater(0, comparer.Compare(10, 20));
|
||||
Assert.Less(0, comparer.Compare(20, 10));
|
||||
|
||||
Assert.Less(0, reverseComparer.Compare(10, 20));
|
||||
Assert.Greater(0, reverseComparer.Compare(20, 10));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests whether the full constructor of the reverse comparer works
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestReversedCustomComparer() {
|
||||
FortyTwoComparer fortyTwoComparer = new FortyTwoComparer();
|
||||
ReverseComparer<int> reverseFortyTwoComparer = new ReverseComparer<int>(
|
||||
fortyTwoComparer
|
||||
);
|
||||
|
||||
Assert.Less(0, fortyTwoComparer.Compare(42, 84));
|
||||
Assert.Greater(0, fortyTwoComparer.Compare(84, 42));
|
||||
|
||||
Assert.Greater(0, reverseFortyTwoComparer.Compare(42, 84));
|
||||
Assert.Less(0, reverseFortyTwoComparer.Compare(84, 42));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} // namespace Nuclex.Support.Collections
|
||||
|
||||
#endif // UNITTEST
|
||||
|
|
|
|||
|
|
@ -1,56 +1,55 @@
|
|||
#region CPL License
|
||||
/*
|
||||
Nuclex Framework
|
||||
Copyright (C) 2002-2017 Nuclex Development Labs
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the IBM Common Public License as
|
||||
published by the IBM Corporation; either version 1.0 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
IBM Common Public License for more details.
|
||||
|
||||
You should have received a copy of the IBM Common Public
|
||||
License along with this library
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Nuclex.Support.Collections {
|
||||
|
||||
/// <summary>
|
||||
/// Compares two values in reverse or reverses the output of another comparer
|
||||
/// </summary>
|
||||
/// <typeparam name="TCompared">Type of values to be compared</typeparam>
|
||||
public class ReverseComparer<TCompared> : IComparer<TCompared> {
|
||||
|
||||
/// <summary>Initializes a new reverse comparer</summary>
|
||||
public ReverseComparer() : this(Comparer<TCompared>.Default) { }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the comparer to provide the inverse results of another comparer
|
||||
/// </summary>
|
||||
/// <param name="comparerToReverse">Comparer whose results will be inversed</param>
|
||||
public ReverseComparer(IComparer<TCompared> comparerToReverse) {
|
||||
this.comparer = comparerToReverse;
|
||||
}
|
||||
|
||||
/// <summary>Compares the left value to the right value</summary>
|
||||
/// <param name="left">Value on the left side</param>
|
||||
/// <param name="right">Value on the right side</param>
|
||||
/// <returns>The relationship of the two values</returns>
|
||||
public int Compare(TCompared left, TCompared right) {
|
||||
return this.comparer.Compare(right, left); // intentionally reversed
|
||||
}
|
||||
|
||||
/// <summary>The default comparer from the .NET framework</summary>
|
||||
private IComparer<TCompared> comparer;
|
||||
|
||||
}
|
||||
|
||||
} // namespace Nuclex.Support.Collections
|
||||
#region Apache License 2.0
|
||||
/*
|
||||
Nuclex .NET Framework
|
||||
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
#endregion // Apache License 2.0
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Nuclex.Support.Collections {
|
||||
|
||||
/// <summary>
|
||||
/// Compares two values in reverse or reverses the output of another comparer
|
||||
/// </summary>
|
||||
/// <typeparam name="TCompared">Type of values to be compared</typeparam>
|
||||
public class ReverseComparer<TCompared> : IComparer<TCompared> {
|
||||
|
||||
/// <summary>Initializes a new reverse comparer</summary>
|
||||
public ReverseComparer() : this(Comparer<TCompared>.Default) { }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the comparer to provide the inverse results of another comparer
|
||||
/// </summary>
|
||||
/// <param name="comparerToReverse">Comparer whose results will be inversed</param>
|
||||
public ReverseComparer(IComparer<TCompared> comparerToReverse) {
|
||||
this.comparer = comparerToReverse;
|
||||
}
|
||||
|
||||
/// <summary>Compares the left value to the right value</summary>
|
||||
/// <param name="left">Value on the left side</param>
|
||||
/// <param name="right">Value on the right side</param>
|
||||
/// <returns>The relationship of the two values</returns>
|
||||
public int Compare(TCompared left, TCompared right) {
|
||||
return this.comparer.Compare(right, left); // intentionally reversed
|
||||
}
|
||||
|
||||
/// <summary>The default comparer from the .NET framework</summary>
|
||||
private IComparer<TCompared> comparer;
|
||||
|
||||
}
|
||||
|
||||
} // namespace Nuclex.Support.Collections
|
||||
|
|
|
|||
|
|
@ -1,120 +1,119 @@
|
|||
#region CPL License
|
||||
/*
|
||||
Nuclex Framework
|
||||
Copyright (C) 2002-2017 Nuclex Development Labs
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the IBM Common Public License as
|
||||
published by the IBM Corporation; either version 1.0 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
IBM Common Public License for more details.
|
||||
|
||||
You should have received a copy of the IBM Common Public
|
||||
License along with this library
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Reflection;
|
||||
|
||||
#if UNITTEST
|
||||
|
||||
using NUnit.Framework;
|
||||
|
||||
namespace Nuclex.Support.Collections {
|
||||
|
||||
/// <summary>Unit Test for the SortableBindingList class</summary>
|
||||
[TestFixture]
|
||||
internal class SortableBindingListTest {
|
||||
|
||||
#region class TestRecord
|
||||
|
||||
/// <summary>Dummy record used to test the sortable binding list</summary>
|
||||
private class TestRecord {
|
||||
|
||||
/// <summary>A property of type integer</summary>
|
||||
public int IntegerValue { get; set; }
|
||||
|
||||
/// <summary>A property of type string</summary>
|
||||
public string StringValue { get; set; }
|
||||
|
||||
/// <summary>A property of type float</summary>
|
||||
public float FloatValue { get; set; }
|
||||
|
||||
}
|
||||
|
||||
#endregion // class TestRecord
|
||||
|
||||
/// <summary>Verifies that the sortable binding list is default constructible</summary>
|
||||
[Test]
|
||||
public void HasDefaultConstructor() {
|
||||
Assert.DoesNotThrow(
|
||||
delegate () { new SortableBindingList<TestRecord>(); }
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests whether the sortable binding list can copy an existing list
|
||||
/// when being constructed
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void HasEnumerableConstructor() {
|
||||
var items = new List<TestRecord>() {
|
||||
new TestRecord() { IntegerValue = 123 },
|
||||
new TestRecord() { IntegerValue = 456 }
|
||||
};
|
||||
|
||||
var testList = new SortableBindingList<TestRecord>(items);
|
||||
|
||||
Assert.AreEqual(2, testList.Count);
|
||||
Assert.AreSame(items[0], testList[0]);
|
||||
Assert.AreSame(items[1], testList[1]);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that the sortable binding list supports sorting</summary>
|
||||
[Test]
|
||||
public void SupportsSorting() {
|
||||
var testList = new SortableBindingList<TestRecord>();
|
||||
IBindingList testListAsBindingList = testList;
|
||||
|
||||
Assert.IsTrue(testListAsBindingList.SupportsSorting);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests whether the sortable binding list can sort its elements by different properties
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void CanSortItems() {
|
||||
var items = new List<TestRecord>() {
|
||||
new TestRecord() { IntegerValue = 456 },
|
||||
new TestRecord() { IntegerValue = 789 },
|
||||
new TestRecord() { IntegerValue = 123 }
|
||||
};
|
||||
|
||||
var testList = new SortableBindingList<TestRecord>(items);
|
||||
IBindingList testListAsBindingList = testList;
|
||||
|
||||
PropertyDescriptor integerValuePropertyDescriptor = (
|
||||
TypeDescriptor.GetProperties(typeof(TestRecord))[nameof(TestRecord.IntegerValue)]
|
||||
);
|
||||
testListAsBindingList.ApplySort(
|
||||
integerValuePropertyDescriptor, ListSortDirection.Ascending
|
||||
);
|
||||
|
||||
Assert.AreEqual(3, testList.Count);
|
||||
Assert.AreEqual(123, testList[0].IntegerValue);
|
||||
Assert.AreEqual(456, testList[1].IntegerValue);
|
||||
Assert.AreEqual(789, testList[2].IntegerValue);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} // namespace Nuclex.Support.Collections
|
||||
|
||||
#endif // UNITTEST
|
||||
#region Apache License 2.0
|
||||
/*
|
||||
Nuclex .NET Framework
|
||||
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
#endregion // Apache License 2.0
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Reflection;
|
||||
|
||||
#if UNITTEST
|
||||
|
||||
using NUnit.Framework;
|
||||
|
||||
namespace Nuclex.Support.Collections {
|
||||
|
||||
/// <summary>Unit Test for the SortableBindingList class</summary>
|
||||
[TestFixture]
|
||||
internal class SortableBindingListTest {
|
||||
|
||||
#region class TestRecord
|
||||
|
||||
/// <summary>Dummy record used to test the sortable binding list</summary>
|
||||
private class TestRecord {
|
||||
|
||||
/// <summary>A property of type integer</summary>
|
||||
public int IntegerValue { get; set; }
|
||||
|
||||
/// <summary>A property of type string</summary>
|
||||
public string StringValue { get; set; }
|
||||
|
||||
/// <summary>A property of type float</summary>
|
||||
public float FloatValue { get; set; }
|
||||
|
||||
}
|
||||
|
||||
#endregion // class TestRecord
|
||||
|
||||
/// <summary>Verifies that the sortable binding list is default constructible</summary>
|
||||
[Test]
|
||||
public void HasDefaultConstructor() {
|
||||
Assert.DoesNotThrow(
|
||||
delegate () { new SortableBindingList<TestRecord>(); }
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests whether the sortable binding list can copy an existing list
|
||||
/// when being constructed
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void HasEnumerableConstructor() {
|
||||
var items = new List<TestRecord>() {
|
||||
new TestRecord() { IntegerValue = 123 },
|
||||
new TestRecord() { IntegerValue = 456 }
|
||||
};
|
||||
|
||||
var testList = new SortableBindingList<TestRecord>(items);
|
||||
|
||||
Assert.AreEqual(2, testList.Count);
|
||||
Assert.AreSame(items[0], testList[0]);
|
||||
Assert.AreSame(items[1], testList[1]);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that the sortable binding list supports sorting</summary>
|
||||
[Test]
|
||||
public void SupportsSorting() {
|
||||
var testList = new SortableBindingList<TestRecord>();
|
||||
IBindingList testListAsBindingList = testList;
|
||||
|
||||
Assert.IsTrue(testListAsBindingList.SupportsSorting);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests whether the sortable binding list can sort its elements by different properties
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void CanSortItems() {
|
||||
var items = new List<TestRecord>() {
|
||||
new TestRecord() { IntegerValue = 456 },
|
||||
new TestRecord() { IntegerValue = 789 },
|
||||
new TestRecord() { IntegerValue = 123 }
|
||||
};
|
||||
|
||||
var testList = new SortableBindingList<TestRecord>(items);
|
||||
IBindingList testListAsBindingList = testList;
|
||||
|
||||
PropertyDescriptor integerValuePropertyDescriptor = (
|
||||
TypeDescriptor.GetProperties(typeof(TestRecord))[nameof(TestRecord.IntegerValue)]
|
||||
);
|
||||
testListAsBindingList.ApplySort(
|
||||
integerValuePropertyDescriptor, ListSortDirection.Ascending
|
||||
);
|
||||
|
||||
Assert.AreEqual(3, testList.Count);
|
||||
Assert.AreEqual(123, testList[0].IntegerValue);
|
||||
Assert.AreEqual(456, testList[1].IntegerValue);
|
||||
Assert.AreEqual(789, testList[2].IntegerValue);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} // namespace Nuclex.Support.Collections
|
||||
|
||||
#endif // UNITTEST
|
||||
|
|
|
|||
|
|
@ -1,223 +1,222 @@
|
|||
#region CPL License
|
||||
/*
|
||||
Nuclex Framework
|
||||
Copyright (C) 2002-2017 Nuclex Development Labs
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the IBM Common Public License as
|
||||
published by the IBM Corporation; either version 1.0 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
IBM Common Public License for more details.
|
||||
|
||||
You should have received a copy of the IBM Common Public
|
||||
License along with this library
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Nuclex.Support.Collections {
|
||||
|
||||
/// <summary>Variant of BindingList that supports sorting</summary>
|
||||
/// <typeparam name="TElement">Type of items the binding list will contain</typeparam>
|
||||
public class SortableBindingList<TElement> : BindingList<TElement> {
|
||||
|
||||
#region class PropertyComparer
|
||||
|
||||
/// <summary>Compares two elements based on a single preselected property</summary>
|
||||
private class PropertyComparer : IComparer<TElement> {
|
||||
|
||||
/// <summary>Initializes a new property comparer for the specified property</summary>
|
||||
/// <param name="property">Property based on which elements should be compared</param>
|
||||
/// <param name="direction">Direction in which elements should be sorted</param>
|
||||
public PropertyComparer(PropertyDescriptor property, ListSortDirection direction) {
|
||||
this.propertyDescriptor = property;
|
||||
|
||||
Type comparerForPropertyType = typeof(Comparer<>).MakeGenericType(property.PropertyType);
|
||||
this.comparer = (IComparer)comparerForPropertyType.InvokeMember(
|
||||
"Default",
|
||||
BindingFlags.Static | BindingFlags.GetProperty | BindingFlags.Public,
|
||||
null, // binder
|
||||
null, // target (none since method is static)
|
||||
null // argument array
|
||||
);
|
||||
|
||||
SetListSortDirection(direction);
|
||||
}
|
||||
|
||||
/// <summary>Compares two elements based on the comparer's chosen property</summary>
|
||||
/// <param name="first">First element for the comparison</param>
|
||||
/// <param name="second">Second element for the comparison</param>
|
||||
/// <returns>The relationship of the two elements to each other</returns>
|
||||
public int Compare(TElement first, TElement second) {
|
||||
return this.comparer.Compare(
|
||||
this.propertyDescriptor.GetValue(first),
|
||||
this.propertyDescriptor.GetValue(second)
|
||||
) * this.reverse;
|
||||
}
|
||||
|
||||
/// <summary>Selects the property based on which elements should be compared</summary>
|
||||
/// <param name="descriptor">Descriptor for the property to use for comparison</param>
|
||||
private void SetPropertyDescriptor(PropertyDescriptor descriptor) {
|
||||
this.propertyDescriptor = descriptor;
|
||||
}
|
||||
|
||||
/// <summary>Changes the sort direction</summary>
|
||||
/// <param name="direction">New sort direction</param>
|
||||
private void SetListSortDirection(ListSortDirection direction) {
|
||||
this.reverse = direction == ListSortDirection.Ascending ? 1 : -1;
|
||||
}
|
||||
|
||||
/// <summary>Updtes the sorted proeprty and the sort direction</summary>
|
||||
/// <param name="descriptor">Property based on which elements will be sorted</param>
|
||||
/// <param name="direction">Direction in which elements will be sorted</param>
|
||||
public void SetPropertyAndDirection(
|
||||
PropertyDescriptor descriptor, ListSortDirection direction
|
||||
) {
|
||||
SetPropertyDescriptor(descriptor);
|
||||
SetListSortDirection(direction);
|
||||
}
|
||||
|
||||
/// <summary>The default comparer for the type of the chosen property</summary>
|
||||
private readonly IComparer comparer;
|
||||
/// <summary>Descriptor for the chosen property</summary>
|
||||
private PropertyDescriptor propertyDescriptor;
|
||||
/// <summary>
|
||||
/// Either positive or negative 1 to change the sign of the comparison result
|
||||
/// </summary>
|
||||
private int reverse;
|
||||
|
||||
}
|
||||
|
||||
#endregion // class PropertyComparer
|
||||
|
||||
/// <summary>Initializes a new BindingList with support for sorting</summary>
|
||||
public SortableBindingList() : base(new List<TElement>()) {
|
||||
this.comparers = new Dictionary<Type, PropertyComparer>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a sortable BindingList, copying the contents of an existing list
|
||||
/// </summary>
|
||||
/// <param name="enumeration">Existing list whose contents will be shallo-wcopied</param>
|
||||
public SortableBindingList(IEnumerable<TElement> enumeration) :
|
||||
base(new List<TElement>(enumeration)) {
|
||||
this.comparers = new Dictionary<Type, PropertyComparer>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used by BindingList implementation to check whether sorting is supported
|
||||
/// </summary>
|
||||
protected override bool SupportsSortingCore {
|
||||
get { return true; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used by BindingList implementation to check whether the list is currently sorted
|
||||
/// </summary>
|
||||
protected override bool IsSortedCore {
|
||||
get { return this.isSorted; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used by BindingList implementation to track the property the list is sorted by
|
||||
/// </summary>
|
||||
protected override PropertyDescriptor SortPropertyCore {
|
||||
get { return this.propertyDescriptor; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used by BindingList implementation to track the direction in which the list is sortd
|
||||
/// </summary>
|
||||
protected override ListSortDirection SortDirectionCore {
|
||||
get { return this.listSortDirection; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used by BindingList implementation to check whether the list supports searching
|
||||
/// </summary>
|
||||
protected override bool SupportsSearchingCore {
|
||||
get { return true; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used by BindingList implementation to sort the elements in the backing collection
|
||||
/// </summary>
|
||||
protected override void ApplySortCore(PropertyDescriptor property, ListSortDirection direction) {
|
||||
|
||||
// Obtain a property comparer that sorts on the attributes the SortableBindingList
|
||||
// has been configured for its sort order
|
||||
PropertyComparer comparer;
|
||||
{
|
||||
Type propertyType = property.PropertyType;
|
||||
|
||||
if(!this.comparers.TryGetValue(propertyType, out comparer)) {
|
||||
comparer = new PropertyComparer(property, direction);
|
||||
this.comparers.Add(propertyType, comparer);
|
||||
}
|
||||
|
||||
// Direction may need to be updated
|
||||
comparer.SetPropertyAndDirection(property, direction);
|
||||
}
|
||||
|
||||
// Check to see if our base class is using a standard List<> in which case
|
||||
// we'll sneakily use the downcast to call the List<>.Sort() method, otherwise
|
||||
// there's still our own quicksort implementation for IList<>.
|
||||
List<TElement> itemsAsList = this.Items as List<TElement>;
|
||||
if(itemsAsList != null) {
|
||||
itemsAsList.Sort(comparer);
|
||||
} else {
|
||||
this.Items.QuickSort(0, this.Items.Count, comparer); // from IListExtensions
|
||||
}
|
||||
|
||||
this.propertyDescriptor = property;
|
||||
this.listSortDirection = direction;
|
||||
this.isSorted = true;
|
||||
|
||||
OnListChanged(new ListChangedEventArgs(ListChangedType.Reset, -1));
|
||||
}
|
||||
|
||||
/// <summary>Used by BindingList implementation to undo any sorting that took place</summary>
|
||||
protected override void RemoveSortCore() {
|
||||
this.isSorted = false;
|
||||
this.propertyDescriptor = base.SortPropertyCore;
|
||||
this.listSortDirection = base.SortDirectionCore;
|
||||
|
||||
OnListChanged(new ListChangedEventArgs(ListChangedType.Reset, -1));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used by BindingList implementation to run a search on any of the element's properties
|
||||
/// </summary>
|
||||
protected override int FindCore(PropertyDescriptor property, object key) {
|
||||
int count = this.Count;
|
||||
for(int index = 0; index < count; ++index) {
|
||||
TElement element = this[index];
|
||||
if(property.GetValue(element).Equals(key)) {
|
||||
return index;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
/// <summary>Cached property comparers, created for each element property as needed</summary>
|
||||
private readonly Dictionary<Type, PropertyComparer> comparers;
|
||||
/// <summary>Whether the binding list is currently sorted</summary>
|
||||
private bool isSorted;
|
||||
/// <summary>Direction in which the binding list is currently sorted</summary>
|
||||
private ListSortDirection listSortDirection;
|
||||
/// <summary>Descriptor for the property by which the binding list is currently sorted</summary>
|
||||
private PropertyDescriptor propertyDescriptor;
|
||||
|
||||
}
|
||||
|
||||
} // namespace Nuclex.Support.Collections
|
||||
#region Apache License 2.0
|
||||
/*
|
||||
Nuclex .NET Framework
|
||||
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
#endregion // Apache License 2.0
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Nuclex.Support.Collections {
|
||||
|
||||
/// <summary>Variant of BindingList that supports sorting</summary>
|
||||
/// <typeparam name="TElement">Type of items the binding list will contain</typeparam>
|
||||
public class SortableBindingList<TElement> : BindingList<TElement> {
|
||||
|
||||
#region class PropertyComparer
|
||||
|
||||
/// <summary>Compares two elements based on a single preselected property</summary>
|
||||
private class PropertyComparer : IComparer<TElement> {
|
||||
|
||||
/// <summary>Initializes a new property comparer for the specified property</summary>
|
||||
/// <param name="property">Property based on which elements should be compared</param>
|
||||
/// <param name="direction">Direction in which elements should be sorted</param>
|
||||
public PropertyComparer(PropertyDescriptor property, ListSortDirection direction) {
|
||||
this.propertyDescriptor = property;
|
||||
|
||||
Type comparerForPropertyType = typeof(Comparer<>).MakeGenericType(property.PropertyType);
|
||||
this.comparer = (IComparer)comparerForPropertyType.InvokeMember(
|
||||
"Default",
|
||||
BindingFlags.Static | BindingFlags.GetProperty | BindingFlags.Public,
|
||||
null, // binder
|
||||
null, // target (none since method is static)
|
||||
null // argument array
|
||||
);
|
||||
|
||||
SetListSortDirection(direction);
|
||||
}
|
||||
|
||||
/// <summary>Compares two elements based on the comparer's chosen property</summary>
|
||||
/// <param name="first">First element for the comparison</param>
|
||||
/// <param name="second">Second element for the comparison</param>
|
||||
/// <returns>The relationship of the two elements to each other</returns>
|
||||
public int Compare(TElement first, TElement second) {
|
||||
return this.comparer.Compare(
|
||||
this.propertyDescriptor.GetValue(first),
|
||||
this.propertyDescriptor.GetValue(second)
|
||||
) * this.reverse;
|
||||
}
|
||||
|
||||
/// <summary>Selects the property based on which elements should be compared</summary>
|
||||
/// <param name="descriptor">Descriptor for the property to use for comparison</param>
|
||||
private void SetPropertyDescriptor(PropertyDescriptor descriptor) {
|
||||
this.propertyDescriptor = descriptor;
|
||||
}
|
||||
|
||||
/// <summary>Changes the sort direction</summary>
|
||||
/// <param name="direction">New sort direction</param>
|
||||
private void SetListSortDirection(ListSortDirection direction) {
|
||||
this.reverse = direction == ListSortDirection.Ascending ? 1 : -1;
|
||||
}
|
||||
|
||||
/// <summary>Updtes the sorted proeprty and the sort direction</summary>
|
||||
/// <param name="descriptor">Property based on which elements will be sorted</param>
|
||||
/// <param name="direction">Direction in which elements will be sorted</param>
|
||||
public void SetPropertyAndDirection(
|
||||
PropertyDescriptor descriptor, ListSortDirection direction
|
||||
) {
|
||||
SetPropertyDescriptor(descriptor);
|
||||
SetListSortDirection(direction);
|
||||
}
|
||||
|
||||
/// <summary>The default comparer for the type of the chosen property</summary>
|
||||
private readonly IComparer comparer;
|
||||
/// <summary>Descriptor for the chosen property</summary>
|
||||
private PropertyDescriptor propertyDescriptor;
|
||||
/// <summary>
|
||||
/// Either positive or negative 1 to change the sign of the comparison result
|
||||
/// </summary>
|
||||
private int reverse;
|
||||
|
||||
}
|
||||
|
||||
#endregion // class PropertyComparer
|
||||
|
||||
/// <summary>Initializes a new BindingList with support for sorting</summary>
|
||||
public SortableBindingList() : base(new List<TElement>()) {
|
||||
this.comparers = new Dictionary<Type, PropertyComparer>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a sortable BindingList, copying the contents of an existing list
|
||||
/// </summary>
|
||||
/// <param name="enumeration">Existing list whose contents will be shallo-wcopied</param>
|
||||
public SortableBindingList(IEnumerable<TElement> enumeration) :
|
||||
base(new List<TElement>(enumeration)) {
|
||||
this.comparers = new Dictionary<Type, PropertyComparer>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used by BindingList implementation to check whether sorting is supported
|
||||
/// </summary>
|
||||
protected override bool SupportsSortingCore {
|
||||
get { return true; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used by BindingList implementation to check whether the list is currently sorted
|
||||
/// </summary>
|
||||
protected override bool IsSortedCore {
|
||||
get { return this.isSorted; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used by BindingList implementation to track the property the list is sorted by
|
||||
/// </summary>
|
||||
protected override PropertyDescriptor SortPropertyCore {
|
||||
get { return this.propertyDescriptor; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used by BindingList implementation to track the direction in which the list is sortd
|
||||
/// </summary>
|
||||
protected override ListSortDirection SortDirectionCore {
|
||||
get { return this.listSortDirection; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used by BindingList implementation to check whether the list supports searching
|
||||
/// </summary>
|
||||
protected override bool SupportsSearchingCore {
|
||||
get { return true; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used by BindingList implementation to sort the elements in the backing collection
|
||||
/// </summary>
|
||||
protected override void ApplySortCore(PropertyDescriptor property, ListSortDirection direction) {
|
||||
|
||||
// Obtain a property comparer that sorts on the attributes the SortableBindingList
|
||||
// has been configured for its sort order
|
||||
PropertyComparer comparer;
|
||||
{
|
||||
Type propertyType = property.PropertyType;
|
||||
|
||||
if(!this.comparers.TryGetValue(propertyType, out comparer)) {
|
||||
comparer = new PropertyComparer(property, direction);
|
||||
this.comparers.Add(propertyType, comparer);
|
||||
}
|
||||
|
||||
// Direction may need to be updated
|
||||
comparer.SetPropertyAndDirection(property, direction);
|
||||
}
|
||||
|
||||
// Check to see if our base class is using a standard List<> in which case
|
||||
// we'll sneakily use the downcast to call the List<>.Sort() method, otherwise
|
||||
// there's still our own quicksort implementation for IList<>.
|
||||
List<TElement> itemsAsList = this.Items as List<TElement>;
|
||||
if(itemsAsList != null) {
|
||||
itemsAsList.Sort(comparer);
|
||||
} else {
|
||||
this.Items.QuickSort(0, this.Items.Count, comparer); // from IListExtensions
|
||||
}
|
||||
|
||||
this.propertyDescriptor = property;
|
||||
this.listSortDirection = direction;
|
||||
this.isSorted = true;
|
||||
|
||||
OnListChanged(new ListChangedEventArgs(ListChangedType.Reset, -1));
|
||||
}
|
||||
|
||||
/// <summary>Used by BindingList implementation to undo any sorting that took place</summary>
|
||||
protected override void RemoveSortCore() {
|
||||
this.isSorted = false;
|
||||
this.propertyDescriptor = base.SortPropertyCore;
|
||||
this.listSortDirection = base.SortDirectionCore;
|
||||
|
||||
OnListChanged(new ListChangedEventArgs(ListChangedType.Reset, -1));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used by BindingList implementation to run a search on any of the element's properties
|
||||
/// </summary>
|
||||
protected override int FindCore(PropertyDescriptor property, object key) {
|
||||
int count = this.Count;
|
||||
for(int index = 0; index < count; ++index) {
|
||||
TElement element = this[index];
|
||||
if(property.GetValue(element).Equals(key)) {
|
||||
return index;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
/// <summary>Cached property comparers, created for each element property as needed</summary>
|
||||
private readonly Dictionary<Type, PropertyComparer> comparers;
|
||||
/// <summary>Whether the binding list is currently sorted</summary>
|
||||
private bool isSorted;
|
||||
/// <summary>Direction in which the binding list is currently sorted</summary>
|
||||
private ListSortDirection listSortDirection;
|
||||
/// <summary>Descriptor for the property by which the binding list is currently sorted</summary>
|
||||
private PropertyDescriptor propertyDescriptor;
|
||||
|
||||
}
|
||||
|
||||
} // namespace Nuclex.Support.Collections
|
||||
|
|
|
|||
|
|
@ -1,343 +1,342 @@
|
|||
#region CPL License
|
||||
/*
|
||||
Nuclex Framework
|
||||
Copyright (C) 2002-2017 Nuclex Development Labs
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the IBM Common Public License as
|
||||
published by the IBM Corporation; either version 1.0 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
IBM Common Public License for more details.
|
||||
|
||||
You should have received a copy of the IBM Common Public
|
||||
License along with this library
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Nuclex.Support.Collections {
|
||||
|
||||
// More than 400 lines of code just to implement the .NET collection interfaces.
|
||||
// Shows the niceties of having to support languages without generics and using
|
||||
// an inferior design to make collections "more convenient" for the user :/
|
||||
|
||||
partial class TransformingReadOnlyCollection<TContainedItem, TExposedItem> {
|
||||
|
||||
#region IList<TExposedItem> Members
|
||||
|
||||
/// <summary>
|
||||
/// Inserts an item to the TransformingReadOnlyCollection at the specified index.
|
||||
/// </summary>
|
||||
/// <param name="index">
|
||||
/// The zero-based index at which item should be inserted.
|
||||
/// </param>
|
||||
/// <param name="item">
|
||||
/// The object to insert into the TransformingReadOnlyCollection
|
||||
/// </param>
|
||||
/// <exception cref="System.NotSupportedException">
|
||||
/// The TransformingReadOnlyCollection is read-only.
|
||||
/// </exception>
|
||||
/// <exception cref="System.ArgumentOutOfRangeException">
|
||||
/// index is not a valid index in the TransformingReadOnlyCollection.
|
||||
/// </exception>
|
||||
void IList<TExposedItem>.Insert(int index, TExposedItem item) {
|
||||
throw new NotSupportedException("The collection is ready-only");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes the TransformingReadOnlyCollection item at the specified index.
|
||||
/// </summary>
|
||||
/// <param name="index">The zero-based index of the item to remove.</param>
|
||||
/// <exception cref="System.NotSupportedException">
|
||||
/// The TransformingReadOnlyCollection is read-only.
|
||||
/// </exception>
|
||||
/// <exception cref="System.ArgumentOutOfRangeException">
|
||||
/// Index is not a valid index in the TransformingReadOnlyCollection.
|
||||
/// </exception>
|
||||
void IList<TExposedItem>.RemoveAt(int index) {
|
||||
throw new NotSupportedException("The collection is ready-only");
|
||||
}
|
||||
|
||||
/// <summary>Gets or sets the element at the specified index.</summary>
|
||||
/// <param name="index">The zero-based index of the element to get or set.</param>
|
||||
/// <returns>The element at the specified index.</returns>
|
||||
/// <exception cref="System.ArgumentOutOfRangeException">
|
||||
/// Index is not a valid index in the TransformingReadOnlyCollection.
|
||||
/// </exception>
|
||||
/// <exception cref="System.NotSupportedException">
|
||||
/// The property is set and the TransformingReadOnlyCollection is read-only
|
||||
/// </exception>
|
||||
TExposedItem IList<TExposedItem>.this[int index] {
|
||||
get {
|
||||
return this[index];
|
||||
}
|
||||
set {
|
||||
throw new NotSupportedException("The collection is ready-only");
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region ICollection<TExposedItem> Members
|
||||
|
||||
/// <summary>Adds an item to the TransformingReadOnlyCollection.</summary>
|
||||
/// <param name="item">The object to add to the TransformingReadOnlyCollection</param>
|
||||
/// <exception cref="System.NotSupportedException">
|
||||
/// The TransformingReadOnlyCollection is read-only.
|
||||
/// </exception>
|
||||
void ICollection<TExposedItem>.Add(TExposedItem item) {
|
||||
throw new NotSupportedException("The collection is ready-only");
|
||||
}
|
||||
|
||||
/// <summary>Removes all items from the TransformingReadOnlyCollection</summary>
|
||||
/// <exception cref="System.NotSupportedException">
|
||||
/// The TransformingReadOnlyCollection is read-only.
|
||||
/// </exception>
|
||||
void ICollection<TExposedItem>.Clear() {
|
||||
throw new NotSupportedException("The collection is ready-only");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes the first occurrence of a specific object from the
|
||||
/// TransformingReadOnlyCollection.
|
||||
/// </summary>
|
||||
/// <param name="item">
|
||||
/// The object to remove from the TransformingReadOnlyCollection
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// True if item was successfully removed from the TransformingReadOnlyCollection;
|
||||
/// otherwise, false. This method also returns false if item is not found in the
|
||||
/// original TransformingReadOnlyCollection.
|
||||
/// </returns>
|
||||
/// <exception cref="System.NotSupportedException">
|
||||
/// The TransformingReadOnlyCollection is read-only.
|
||||
/// </exception>
|
||||
bool ICollection<TExposedItem>.Remove(TExposedItem item) {
|
||||
throw new NotSupportedException("The collection is ready-only");
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IEnumerable Members
|
||||
|
||||
/// <summary>Returns an enumerator that iterates through a collection.</summary>
|
||||
/// <returns>
|
||||
/// A System.Collections.IEnumerator object that can be used to iterate through
|
||||
/// the collection.
|
||||
/// </returns>
|
||||
IEnumerator IEnumerable.GetEnumerator() {
|
||||
return GetEnumerator();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IList Members
|
||||
|
||||
/// <summary>Adds an item to the TransformingReadOnlyCollection.</summary>
|
||||
/// <param name="value">
|
||||
/// The System.Object to add to the TransformingReadOnlyCollection.
|
||||
/// </param>
|
||||
/// <returns>The position into which the new element was inserted.</returns>
|
||||
/// <exception cref="System.NotSupportedException">
|
||||
/// The System.Collections.IList is read-only or the TransformingReadOnlyCollection
|
||||
/// has a fixed size.
|
||||
/// </exception>
|
||||
int IList.Add(object value) {
|
||||
throw new NotSupportedException("The collection is ready-only");
|
||||
}
|
||||
|
||||
/// <summary>Removes all items from the TransformingReadOnlyCollection.</summary>
|
||||
/// <exception cref="System.NotSupportedException">
|
||||
/// The TransformingReadOnlyCollection is read-only.
|
||||
/// </exception>
|
||||
void IList.Clear() {
|
||||
throw new NotSupportedException("The collection is ready-only");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the TransformingReadOnlyCollection contains a specific value.
|
||||
/// </summary>
|
||||
/// <param name="value">
|
||||
/// The System.Object to locate in the TransformingReadOnlyCollection.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// True if the System.Object is found in the TransformingReadOnlyCollection;
|
||||
/// otherwise, false.
|
||||
/// </returns>
|
||||
bool IList.Contains(object value) {
|
||||
return Contains((TExposedItem)value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines the index of a specific item in the TransformingReadOnlyCollection.
|
||||
/// </summary>
|
||||
/// <param name="value">
|
||||
/// The System.Object to locate in the TransformingReadOnlyCollection.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// The index of value if found in the list; otherwise, -1.
|
||||
/// </returns>
|
||||
int IList.IndexOf(object value) {
|
||||
return IndexOf((TExposedItem)value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Inserts an item to the TransformingReadOnlyCollection at the specified index.
|
||||
/// </summary>
|
||||
/// <param name="index">
|
||||
/// The zero-based index at which value should be inserted.
|
||||
/// </param>
|
||||
/// <param name="value">
|
||||
/// The System.Object to insert into the TransformingReadOnlyCollection.
|
||||
/// </param>
|
||||
/// <exception cref="System.ArgumentOutOfRangeException">
|
||||
/// Index is not a valid index in the TransformingReadOnlyCollection.
|
||||
/// </exception>
|
||||
/// <exception cref="System.NotSupportedException">
|
||||
/// The System.Collections.IList is read-only or the TransformingReadOnlyCollection
|
||||
/// has a fixed size.
|
||||
/// </exception>
|
||||
/// <exception cref="System.NullReferenceException">
|
||||
/// Value is null reference in the TransformingReadOnlyCollection.
|
||||
/// </exception>
|
||||
void IList.Insert(int index, object value) {
|
||||
throw new NotSupportedException("The collection is ready-only");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A value indicating whether the TransformingReadOnlyCollection has a fixed
|
||||
/// size.
|
||||
/// </summary>
|
||||
bool IList.IsFixedSize {
|
||||
get { return true; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes the first occurrence of a specific object from the
|
||||
/// TransformingReadOnlyCollection.
|
||||
/// </summary>
|
||||
/// <param name="value">
|
||||
/// The System.Object to remove from the TransformingReadOnlyCollection.
|
||||
/// </param>
|
||||
/// <exception cref="System.NotSupportedException">
|
||||
/// The TransformingReadOnlyCollection is read-only or the
|
||||
/// TransformingReadOnlyCollection has a fixed size.
|
||||
/// </exception>
|
||||
void IList.Remove(object value) {
|
||||
throw new NotSupportedException("The collection is ready-only");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes the TransformingReadOnlyCollection item at the specified index.
|
||||
/// </summary>
|
||||
/// <param name="index">The zero-based index of the item to remove.</param>
|
||||
/// <exception cref="System.ArgumentOutOfRangeException">
|
||||
/// Index is not a valid index in the TransformingReadOnlyCollection.
|
||||
/// </exception>
|
||||
/// <exception cref="System.NotSupportedException">
|
||||
/// The TransformingReadOnlyCollection is read-only or the
|
||||
/// TransformingReadOnlyCollection has a fixed size.
|
||||
/// </exception>
|
||||
void IList.RemoveAt(int index) {
|
||||
throw new NotSupportedException("The collection is ready-only");
|
||||
}
|
||||
|
||||
/// <summary>Gets or sets the element at the specified index.</summary>
|
||||
/// <param name="index">The zero-based index of the element to get or set.</param>
|
||||
/// <returns>The element at the specified index</returns>
|
||||
/// <exception cref="System.ArgumentOutOfRangeException">
|
||||
/// Index is not a valid index in the TransformingReadOnlyCollection
|
||||
/// </exception>
|
||||
/// <exception cref="System.NotSupportedException">
|
||||
/// The property is set and the TransformingReadOnlyCollection is read-only.
|
||||
/// </exception>
|
||||
object IList.this[int index] {
|
||||
get {
|
||||
return this[index];
|
||||
}
|
||||
set {
|
||||
throw new NotSupportedException("The collection is ready-only");
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region ICollection Members
|
||||
|
||||
/// <summary>
|
||||
/// Copies the elements of the TransformingReadOnlyCollection to an System.Array,
|
||||
/// starting at a particular System.Array index.
|
||||
/// </summary>
|
||||
/// <param name="array">
|
||||
/// The one-dimensional System.Array that is the destination of the elements
|
||||
/// copied from TransformingReadOnlyCollection. The System.Array must have zero-based
|
||||
/// indexing.
|
||||
/// </param>
|
||||
/// <param name="index">The zero-based index in array at which copying begins.</param>
|
||||
/// <exception cref="System.ArgumentNullException">
|
||||
/// Array is null.
|
||||
/// </exception>
|
||||
/// <exception cref="System.ArgumentOutOfRangeException">
|
||||
/// Index is less than zero.
|
||||
/// </exception>
|
||||
/// <exception cref="System.ArgumentException">
|
||||
/// Array is multidimensional or index is equal to or greater than the length
|
||||
/// of array or the number of elements in the source TransformingReadOnlyCollection
|
||||
/// is greater than the available space from index to the end of the destination
|
||||
/// array.
|
||||
/// </exception>
|
||||
/// <exception cref="System.InvalidCastException">
|
||||
/// The type of the source TransformingReadOnlyCollection cannot be cast
|
||||
/// automatically to the type of the destination array.
|
||||
/// </exception>
|
||||
void ICollection.CopyTo(Array array, int index) {
|
||||
CopyTo((TExposedItem[])array, index);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The number of elements contained in the TransformingReadOnlyCollection.
|
||||
/// </summary>
|
||||
int ICollection.Count {
|
||||
get { return Count; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A value indicating whether access to the TransformingReadOnlyCollection
|
||||
/// is synchronized (thread safe).
|
||||
/// </summary>
|
||||
bool ICollection.IsSynchronized {
|
||||
get { return false; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An object that can be used to synchronize access to the
|
||||
/// TransformingReadOnlyCollection.
|
||||
/// </summary>
|
||||
object ICollection.SyncRoot {
|
||||
get {
|
||||
if(this.syncRoot == null) {
|
||||
ICollection is2 = this.items as ICollection;
|
||||
if(is2 != null) {
|
||||
this.syncRoot = is2.SyncRoot;
|
||||
} else {
|
||||
Interlocked.CompareExchange(ref this.syncRoot, new object(), null);
|
||||
}
|
||||
}
|
||||
|
||||
return this.syncRoot;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
||||
|
||||
} // namespace Nuclex.Support.Collections
|
||||
#region Apache License 2.0
|
||||
/*
|
||||
Nuclex .NET Framework
|
||||
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
#endregion // Apache License 2.0
|
||||
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Nuclex.Support.Collections {
|
||||
|
||||
// More than 400 lines of code just to implement the .NET collection interfaces.
|
||||
// Shows the niceties of having to support languages without generics and using
|
||||
// an inferior design to make collections "more convenient" for the user :/
|
||||
|
||||
partial class TransformingReadOnlyCollection<TContainedItem, TExposedItem> {
|
||||
|
||||
#region IList<TExposedItem> Members
|
||||
|
||||
/// <summary>
|
||||
/// Inserts an item to the TransformingReadOnlyCollection at the specified index.
|
||||
/// </summary>
|
||||
/// <param name="index">
|
||||
/// The zero-based index at which item should be inserted.
|
||||
/// </param>
|
||||
/// <param name="item">
|
||||
/// The object to insert into the TransformingReadOnlyCollection
|
||||
/// </param>
|
||||
/// <exception cref="System.NotSupportedException">
|
||||
/// The TransformingReadOnlyCollection is read-only.
|
||||
/// </exception>
|
||||
/// <exception cref="System.ArgumentOutOfRangeException">
|
||||
/// index is not a valid index in the TransformingReadOnlyCollection.
|
||||
/// </exception>
|
||||
void IList<TExposedItem>.Insert(int index, TExposedItem item) {
|
||||
throw new NotSupportedException("The collection is ready-only");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes the TransformingReadOnlyCollection item at the specified index.
|
||||
/// </summary>
|
||||
/// <param name="index">The zero-based index of the item to remove.</param>
|
||||
/// <exception cref="System.NotSupportedException">
|
||||
/// The TransformingReadOnlyCollection is read-only.
|
||||
/// </exception>
|
||||
/// <exception cref="System.ArgumentOutOfRangeException">
|
||||
/// Index is not a valid index in the TransformingReadOnlyCollection.
|
||||
/// </exception>
|
||||
void IList<TExposedItem>.RemoveAt(int index) {
|
||||
throw new NotSupportedException("The collection is ready-only");
|
||||
}
|
||||
|
||||
/// <summary>Gets or sets the element at the specified index.</summary>
|
||||
/// <param name="index">The zero-based index of the element to get or set.</param>
|
||||
/// <returns>The element at the specified index.</returns>
|
||||
/// <exception cref="System.ArgumentOutOfRangeException">
|
||||
/// Index is not a valid index in the TransformingReadOnlyCollection.
|
||||
/// </exception>
|
||||
/// <exception cref="System.NotSupportedException">
|
||||
/// The property is set and the TransformingReadOnlyCollection is read-only
|
||||
/// </exception>
|
||||
TExposedItem IList<TExposedItem>.this[int index] {
|
||||
get {
|
||||
return this[index];
|
||||
}
|
||||
set {
|
||||
throw new NotSupportedException("The collection is ready-only");
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region ICollection<TExposedItem> Members
|
||||
|
||||
/// <summary>Adds an item to the TransformingReadOnlyCollection.</summary>
|
||||
/// <param name="item">The object to add to the TransformingReadOnlyCollection</param>
|
||||
/// <exception cref="System.NotSupportedException">
|
||||
/// The TransformingReadOnlyCollection is read-only.
|
||||
/// </exception>
|
||||
void ICollection<TExposedItem>.Add(TExposedItem item) {
|
||||
throw new NotSupportedException("The collection is ready-only");
|
||||
}
|
||||
|
||||
/// <summary>Removes all items from the TransformingReadOnlyCollection</summary>
|
||||
/// <exception cref="System.NotSupportedException">
|
||||
/// The TransformingReadOnlyCollection is read-only.
|
||||
/// </exception>
|
||||
void ICollection<TExposedItem>.Clear() {
|
||||
throw new NotSupportedException("The collection is ready-only");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes the first occurrence of a specific object from the
|
||||
/// TransformingReadOnlyCollection.
|
||||
/// </summary>
|
||||
/// <param name="item">
|
||||
/// The object to remove from the TransformingReadOnlyCollection
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// True if item was successfully removed from the TransformingReadOnlyCollection;
|
||||
/// otherwise, false. This method also returns false if item is not found in the
|
||||
/// original TransformingReadOnlyCollection.
|
||||
/// </returns>
|
||||
/// <exception cref="System.NotSupportedException">
|
||||
/// The TransformingReadOnlyCollection is read-only.
|
||||
/// </exception>
|
||||
bool ICollection<TExposedItem>.Remove(TExposedItem item) {
|
||||
throw new NotSupportedException("The collection is ready-only");
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IEnumerable Members
|
||||
|
||||
/// <summary>Returns an enumerator that iterates through a collection.</summary>
|
||||
/// <returns>
|
||||
/// A System.Collections.IEnumerator object that can be used to iterate through
|
||||
/// the collection.
|
||||
/// </returns>
|
||||
IEnumerator IEnumerable.GetEnumerator() {
|
||||
return GetEnumerator();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IList Members
|
||||
|
||||
/// <summary>Adds an item to the TransformingReadOnlyCollection.</summary>
|
||||
/// <param name="value">
|
||||
/// The System.Object to add to the TransformingReadOnlyCollection.
|
||||
/// </param>
|
||||
/// <returns>The position into which the new element was inserted.</returns>
|
||||
/// <exception cref="System.NotSupportedException">
|
||||
/// The System.Collections.IList is read-only or the TransformingReadOnlyCollection
|
||||
/// has a fixed size.
|
||||
/// </exception>
|
||||
int IList.Add(object value) {
|
||||
throw new NotSupportedException("The collection is ready-only");
|
||||
}
|
||||
|
||||
/// <summary>Removes all items from the TransformingReadOnlyCollection.</summary>
|
||||
/// <exception cref="System.NotSupportedException">
|
||||
/// The TransformingReadOnlyCollection is read-only.
|
||||
/// </exception>
|
||||
void IList.Clear() {
|
||||
throw new NotSupportedException("The collection is ready-only");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the TransformingReadOnlyCollection contains a specific value.
|
||||
/// </summary>
|
||||
/// <param name="value">
|
||||
/// The System.Object to locate in the TransformingReadOnlyCollection.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// True if the System.Object is found in the TransformingReadOnlyCollection;
|
||||
/// otherwise, false.
|
||||
/// </returns>
|
||||
bool IList.Contains(object value) {
|
||||
return Contains((TExposedItem)value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines the index of a specific item in the TransformingReadOnlyCollection.
|
||||
/// </summary>
|
||||
/// <param name="value">
|
||||
/// The System.Object to locate in the TransformingReadOnlyCollection.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// The index of value if found in the list; otherwise, -1.
|
||||
/// </returns>
|
||||
int IList.IndexOf(object value) {
|
||||
return IndexOf((TExposedItem)value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Inserts an item to the TransformingReadOnlyCollection at the specified index.
|
||||
/// </summary>
|
||||
/// <param name="index">
|
||||
/// The zero-based index at which value should be inserted.
|
||||
/// </param>
|
||||
/// <param name="value">
|
||||
/// The System.Object to insert into the TransformingReadOnlyCollection.
|
||||
/// </param>
|
||||
/// <exception cref="System.ArgumentOutOfRangeException">
|
||||
/// Index is not a valid index in the TransformingReadOnlyCollection.
|
||||
/// </exception>
|
||||
/// <exception cref="System.NotSupportedException">
|
||||
/// The System.Collections.IList is read-only or the TransformingReadOnlyCollection
|
||||
/// has a fixed size.
|
||||
/// </exception>
|
||||
/// <exception cref="System.NullReferenceException">
|
||||
/// Value is null reference in the TransformingReadOnlyCollection.
|
||||
/// </exception>
|
||||
void IList.Insert(int index, object value) {
|
||||
throw new NotSupportedException("The collection is ready-only");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A value indicating whether the TransformingReadOnlyCollection has a fixed
|
||||
/// size.
|
||||
/// </summary>
|
||||
bool IList.IsFixedSize {
|
||||
get { return true; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes the first occurrence of a specific object from the
|
||||
/// TransformingReadOnlyCollection.
|
||||
/// </summary>
|
||||
/// <param name="value">
|
||||
/// The System.Object to remove from the TransformingReadOnlyCollection.
|
||||
/// </param>
|
||||
/// <exception cref="System.NotSupportedException">
|
||||
/// The TransformingReadOnlyCollection is read-only or the
|
||||
/// TransformingReadOnlyCollection has a fixed size.
|
||||
/// </exception>
|
||||
void IList.Remove(object value) {
|
||||
throw new NotSupportedException("The collection is ready-only");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes the TransformingReadOnlyCollection item at the specified index.
|
||||
/// </summary>
|
||||
/// <param name="index">The zero-based index of the item to remove.</param>
|
||||
/// <exception cref="System.ArgumentOutOfRangeException">
|
||||
/// Index is not a valid index in the TransformingReadOnlyCollection.
|
||||
/// </exception>
|
||||
/// <exception cref="System.NotSupportedException">
|
||||
/// The TransformingReadOnlyCollection is read-only or the
|
||||
/// TransformingReadOnlyCollection has a fixed size.
|
||||
/// </exception>
|
||||
void IList.RemoveAt(int index) {
|
||||
throw new NotSupportedException("The collection is ready-only");
|
||||
}
|
||||
|
||||
/// <summary>Gets or sets the element at the specified index.</summary>
|
||||
/// <param name="index">The zero-based index of the element to get or set.</param>
|
||||
/// <returns>The element at the specified index</returns>
|
||||
/// <exception cref="System.ArgumentOutOfRangeException">
|
||||
/// Index is not a valid index in the TransformingReadOnlyCollection
|
||||
/// </exception>
|
||||
/// <exception cref="System.NotSupportedException">
|
||||
/// The property is set and the TransformingReadOnlyCollection is read-only.
|
||||
/// </exception>
|
||||
object IList.this[int index] {
|
||||
get {
|
||||
return this[index];
|
||||
}
|
||||
set {
|
||||
throw new NotSupportedException("The collection is ready-only");
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region ICollection Members
|
||||
|
||||
/// <summary>
|
||||
/// Copies the elements of the TransformingReadOnlyCollection to an System.Array,
|
||||
/// starting at a particular System.Array index.
|
||||
/// </summary>
|
||||
/// <param name="array">
|
||||
/// The one-dimensional System.Array that is the destination of the elements
|
||||
/// copied from TransformingReadOnlyCollection. The System.Array must have zero-based
|
||||
/// indexing.
|
||||
/// </param>
|
||||
/// <param name="index">The zero-based index in array at which copying begins.</param>
|
||||
/// <exception cref="System.ArgumentNullException">
|
||||
/// Array is null.
|
||||
/// </exception>
|
||||
/// <exception cref="System.ArgumentOutOfRangeException">
|
||||
/// Index is less than zero.
|
||||
/// </exception>
|
||||
/// <exception cref="System.ArgumentException">
|
||||
/// Array is multidimensional or index is equal to or greater than the length
|
||||
/// of array or the number of elements in the source TransformingReadOnlyCollection
|
||||
/// is greater than the available space from index to the end of the destination
|
||||
/// array.
|
||||
/// </exception>
|
||||
/// <exception cref="System.InvalidCastException">
|
||||
/// The type of the source TransformingReadOnlyCollection cannot be cast
|
||||
/// automatically to the type of the destination array.
|
||||
/// </exception>
|
||||
void ICollection.CopyTo(Array array, int index) {
|
||||
CopyTo((TExposedItem[])array, index);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The number of elements contained in the TransformingReadOnlyCollection.
|
||||
/// </summary>
|
||||
int ICollection.Count {
|
||||
get { return Count; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A value indicating whether access to the TransformingReadOnlyCollection
|
||||
/// is synchronized (thread safe).
|
||||
/// </summary>
|
||||
bool ICollection.IsSynchronized {
|
||||
get { return false; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An object that can be used to synchronize access to the
|
||||
/// TransformingReadOnlyCollection.
|
||||
/// </summary>
|
||||
object ICollection.SyncRoot {
|
||||
get {
|
||||
if(this.syncRoot == null) {
|
||||
ICollection is2 = this.items as ICollection;
|
||||
if(is2 != null) {
|
||||
this.syncRoot = is2.SyncRoot;
|
||||
} else {
|
||||
Interlocked.CompareExchange(ref this.syncRoot, new object(), null);
|
||||
}
|
||||
}
|
||||
|
||||
return this.syncRoot;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
||||
|
||||
} // namespace Nuclex.Support.Collections
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -1,289 +1,288 @@
|
|||
#region CPL License
|
||||
/*
|
||||
Nuclex Framework
|
||||
Copyright (C) 2002-2017 Nuclex Development Labs
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the IBM Common Public License as
|
||||
published by the IBM Corporation; either version 1.0 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
IBM Common Public License for more details.
|
||||
|
||||
You should have received a copy of the IBM Common Public
|
||||
License along with this library
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Nuclex.Support.Collections {
|
||||
|
||||
/// <summary>Collection that transforms the contents of another collection.</summary>
|
||||
/// <typeparam name="TContainedItem">
|
||||
/// Type of the items contained in the wrapped collection.
|
||||
/// </typeparam>
|
||||
/// <typeparam name="TExposedItem">
|
||||
/// Type this collection exposes its items as.
|
||||
/// </typeparam>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// This collection is useful if you want to expose the objects of an arbitrary
|
||||
/// collection under a different type. It can be used, for example, to construct
|
||||
/// wrappers for the items in a collection on-the-fly, eliminating the need to
|
||||
/// manage the wrappers in parallel to the real items and improving performance
|
||||
/// by only constructing a wrapper when an item is actually requested.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Another common use would be if you have a private collection of a non-public
|
||||
/// type that's derived from some publicly visible type. By using this collection,
|
||||
/// you can return the items under the publicly visible type while still having
|
||||
/// your private collection under the non-public type, eliminating the need to
|
||||
/// downcast each time you need to access elements of the non-public type.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public abstract partial class TransformingReadOnlyCollection<
|
||||
TContainedItem, TExposedItem
|
||||
> : IList<TExposedItem>, IList {
|
||||
|
||||
#region class TransformingEnumerator
|
||||
|
||||
/// <summary>
|
||||
/// An enumerator that transforms the items returned by an enumerator of the
|
||||
/// wrapped collection into the exposed type on-the-fly.
|
||||
/// </summary>
|
||||
private class TransformingEnumerator : IEnumerator<TExposedItem> {
|
||||
|
||||
/// <summary>Initializes a new transforming enumerator</summary>
|
||||
/// <param name="transformer">Owner; used to invoke the Transform() method</param>
|
||||
/// <param name="containedTypeEnumerator">Enumerator of the wrapped collection</param>
|
||||
public TransformingEnumerator(
|
||||
TransformingReadOnlyCollection<TContainedItem, TExposedItem> transformer,
|
||||
IEnumerator<TContainedItem> containedTypeEnumerator
|
||||
) {
|
||||
this.transformer = transformer;
|
||||
this.containedTypeEnumerator = containedTypeEnumerator;
|
||||
}
|
||||
|
||||
/// <summary>Immediately releases all resources used by the instance</summary>
|
||||
public void Dispose() {
|
||||
this.containedTypeEnumerator.Dispose();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The element in the collection at the current position of the enumerator.
|
||||
/// </summary>
|
||||
public TExposedItem Current {
|
||||
get {
|
||||
return this.transformer.Transform(this.containedTypeEnumerator.Current);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Gets the current element in the collection.</summary>
|
||||
/// <returns>The current element in the collection.</returns>
|
||||
/// <exception cref="System.InvalidOperationException">
|
||||
/// The enumerator is positioned before the first element of the collection
|
||||
/// or after the last element.
|
||||
/// </exception>
|
||||
public bool MoveNext() {
|
||||
return this.containedTypeEnumerator.MoveNext();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the enumerator to its initial position, which is before the first element
|
||||
/// in the collection.
|
||||
/// </summary>
|
||||
/// <exception cref="System.InvalidOperationException">
|
||||
/// The collection was modified after the enumerator was created.
|
||||
/// </exception>
|
||||
public void Reset() {
|
||||
this.containedTypeEnumerator.Reset();
|
||||
}
|
||||
|
||||
/// <summary>The current element in the collection.</summary>
|
||||
/// <exception cref="System.InvalidOperationException">
|
||||
/// The enumerator is positioned before the first element of the collection
|
||||
/// or after the last element.
|
||||
/// </exception>
|
||||
object IEnumerator.Current {
|
||||
get { return Current; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Collection that owns this enumerator; required to invoke the item
|
||||
/// transformation method.
|
||||
/// </summary>
|
||||
private TransformingReadOnlyCollection<TContainedItem, TExposedItem> transformer;
|
||||
/// <summary>An enumerator from the wrapped collection</summary>
|
||||
private IEnumerator<TContainedItem> containedTypeEnumerator;
|
||||
|
||||
}
|
||||
|
||||
#endregion // class TransformingEnumerator
|
||||
|
||||
/// <summary>Initializes a new transforming collection wrapper</summary>
|
||||
/// <param name="items">
|
||||
/// Internal list of items that are transformed into the exposed type when
|
||||
/// accessed through the TransformingReadOnlyCollection.
|
||||
/// </param>
|
||||
public TransformingReadOnlyCollection(IList<TContainedItem> items) {
|
||||
this.items = items;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether an element is in the TransformingReadOnlyCollection
|
||||
/// </summary>
|
||||
/// <param name="item">
|
||||
/// The object to locate in the TransformingReadOnlyCollection.
|
||||
/// The value can be null for reference types.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// True if value is found in the TransformingReadOnlyCollection; otherwise, false.
|
||||
/// </returns>
|
||||
/// <remarks>
|
||||
/// The default implementation of this method is very unoptimized and will
|
||||
/// enumerate all the items in the collection, transforming one after another
|
||||
/// to check whether the transformed item matches the item the user was
|
||||
/// looking for. It is recommended to provide a custom implementation of
|
||||
/// this method, if possible.
|
||||
/// </remarks>
|
||||
public virtual bool Contains(TExposedItem item) {
|
||||
return (IndexOf(item) != -1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies the entire TransformingReadOnlyCollection to a compatible one-dimensional
|
||||
/// System.Array, starting at the specified index of the target array.
|
||||
/// </summary>
|
||||
/// <param name="array">
|
||||
/// The one-dimensional System.Array that is the destination of the elements copied
|
||||
/// from the TransformingReadOnlyCollection. The System.Array must have
|
||||
/// zero-based indexing.
|
||||
/// </param>
|
||||
/// <param name="index">
|
||||
/// The zero-based index in array at which copying begins.
|
||||
/// </param>
|
||||
/// <exception cref="System.ArgumentException">
|
||||
/// Index is equal to or greater than the length of array or the number of elements
|
||||
/// in the source TransformingReadOnlyCollection is greater than the available space
|
||||
/// from index to the end of the destination array.
|
||||
/// </exception>
|
||||
/// <exception cref="System.ArgumentOutOfRangeException">
|
||||
/// Index is less than zero.
|
||||
/// </exception>
|
||||
/// <exception cref="System.ArgumentNullException">
|
||||
/// Array is null.
|
||||
/// </exception>
|
||||
public void CopyTo(TExposedItem[] array, int index) {
|
||||
if(this.items.Count > (array.Length - index)) {
|
||||
throw new ArgumentException(
|
||||
"Array too small to fit the collection items starting at the specified index"
|
||||
);
|
||||
}
|
||||
|
||||
for(int itemIndex = 0; itemIndex < this.items.Count; ++itemIndex) {
|
||||
array[itemIndex + index] = Transform(this.items[itemIndex]);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns an enumerator that iterates through the TransformingReadOnlyCollection.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// An enumerator or the TransformingReadOnlyCollection.
|
||||
/// </returns>
|
||||
public IEnumerator<TExposedItem> GetEnumerator() {
|
||||
return new TransformingEnumerator(this, this.items.GetEnumerator());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Searches for the specified object and returns the zero-based index of the
|
||||
/// first occurrence within the entire TransformingReadOnlyCollection.
|
||||
/// </summary>
|
||||
/// <param name="item">
|
||||
/// The object to locate in the TransformingReadOnlyCollection. The value can
|
||||
/// be null for reference types.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// The zero-based index of the first occurrence of item within the entire
|
||||
/// TransformingReadOnlyCollection, if found; otherwise, -1.
|
||||
/// </returns>
|
||||
/// <remarks>
|
||||
/// The default implementation of this method is very unoptimized and will
|
||||
/// enumerate all the items in the collection, transforming one after another
|
||||
/// to check whether the transformed item matches the item the user was
|
||||
/// looking for. It is recommended to provide a custom implementation of
|
||||
/// this method, if possible.
|
||||
/// </remarks>
|
||||
public int IndexOf(TExposedItem item) {
|
||||
|
||||
if(item == null) {
|
||||
|
||||
for(int index = 0; index < this.items.Count; ++index) {
|
||||
if(Transform(this.items[index]) == null) {
|
||||
return index;
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
var comparer = EqualityComparer<TExposedItem>.Default;
|
||||
for(int index = 0; index < this.items.Count; ++index) {
|
||||
if(comparer.Equals(Transform(this.items[index]), item)) {
|
||||
return index;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return -1;
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The number of elements contained in the TransformingReadOnlyCollection instance
|
||||
/// </summary>
|
||||
public int Count {
|
||||
get { return this.items.Count; }
|
||||
}
|
||||
|
||||
/// <summary>Gets the element at the specified index.</summary>
|
||||
/// <param name="index">The zero-based index of the element to get.</param>
|
||||
/// <returns>The element at the specified index.</returns>
|
||||
/// <exception cref="System.ArgumentOutOfRangeException">
|
||||
/// Index is less than zero or index is equal to or greater than
|
||||
/// TransformingReadOnlyCollection.Count.
|
||||
/// </exception>
|
||||
public TExposedItem this[int index] {
|
||||
get { return Transform(this.items[index]); }
|
||||
}
|
||||
|
||||
/// <summary>Whether the List is write-protected</summary>
|
||||
public bool IsReadOnly {
|
||||
get { return true; }
|
||||
}
|
||||
|
||||
/// <summary>Transforms an item into the exposed type</summary>
|
||||
/// <param name="item">Item to be transformed</param>
|
||||
/// <returns>The transformed item</returns>
|
||||
/// <remarks>
|
||||
/// This method is used to transform an item in the wrapped collection into
|
||||
/// the exposed item type whenever the user accesses an item. Expect it to
|
||||
/// be called frequently, because the TransformingReadOnlyCollection does
|
||||
/// not cache or otherwise store the transformed items.
|
||||
/// </remarks>
|
||||
protected abstract TExposedItem Transform(TContainedItem item);
|
||||
|
||||
/// <summary>Items being transformed upon exposure by this collection</summary>
|
||||
private IList<TContainedItem> items;
|
||||
/// <summary>Synchronization root for threaded accesses to this collection</summary>
|
||||
private object syncRoot;
|
||||
|
||||
}
|
||||
|
||||
} // namespace Nuclex.Support.Collections
|
||||
#region Apache License 2.0
|
||||
/*
|
||||
Nuclex .NET Framework
|
||||
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
#endregion // Apache License 2.0
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Nuclex.Support.Collections {
|
||||
|
||||
/// <summary>Collection that transforms the contents of another collection.</summary>
|
||||
/// <typeparam name="TContainedItem">
|
||||
/// Type of the items contained in the wrapped collection.
|
||||
/// </typeparam>
|
||||
/// <typeparam name="TExposedItem">
|
||||
/// Type this collection exposes its items as.
|
||||
/// </typeparam>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// This collection is useful if you want to expose the objects of an arbitrary
|
||||
/// collection under a different type. It can be used, for example, to construct
|
||||
/// wrappers for the items in a collection on-the-fly, eliminating the need to
|
||||
/// manage the wrappers in parallel to the real items and improving performance
|
||||
/// by only constructing a wrapper when an item is actually requested.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Another common use would be if you have a private collection of a non-public
|
||||
/// type that's derived from some publicly visible type. By using this collection,
|
||||
/// you can return the items under the publicly visible type while still having
|
||||
/// your private collection under the non-public type, eliminating the need to
|
||||
/// downcast each time you need to access elements of the non-public type.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public abstract partial class TransformingReadOnlyCollection<
|
||||
TContainedItem, TExposedItem
|
||||
> : IList<TExposedItem>, IList {
|
||||
|
||||
#region class TransformingEnumerator
|
||||
|
||||
/// <summary>
|
||||
/// An enumerator that transforms the items returned by an enumerator of the
|
||||
/// wrapped collection into the exposed type on-the-fly.
|
||||
/// </summary>
|
||||
private class TransformingEnumerator : IEnumerator<TExposedItem> {
|
||||
|
||||
/// <summary>Initializes a new transforming enumerator</summary>
|
||||
/// <param name="transformer">Owner; used to invoke the Transform() method</param>
|
||||
/// <param name="containedTypeEnumerator">Enumerator of the wrapped collection</param>
|
||||
public TransformingEnumerator(
|
||||
TransformingReadOnlyCollection<TContainedItem, TExposedItem> transformer,
|
||||
IEnumerator<TContainedItem> containedTypeEnumerator
|
||||
) {
|
||||
this.transformer = transformer;
|
||||
this.containedTypeEnumerator = containedTypeEnumerator;
|
||||
}
|
||||
|
||||
/// <summary>Immediately releases all resources used by the instance</summary>
|
||||
public void Dispose() {
|
||||
this.containedTypeEnumerator.Dispose();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The element in the collection at the current position of the enumerator.
|
||||
/// </summary>
|
||||
public TExposedItem Current {
|
||||
get {
|
||||
return this.transformer.Transform(this.containedTypeEnumerator.Current);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Gets the current element in the collection.</summary>
|
||||
/// <returns>The current element in the collection.</returns>
|
||||
/// <exception cref="System.InvalidOperationException">
|
||||
/// The enumerator is positioned before the first element of the collection
|
||||
/// or after the last element.
|
||||
/// </exception>
|
||||
public bool MoveNext() {
|
||||
return this.containedTypeEnumerator.MoveNext();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the enumerator to its initial position, which is before the first element
|
||||
/// in the collection.
|
||||
/// </summary>
|
||||
/// <exception cref="System.InvalidOperationException">
|
||||
/// The collection was modified after the enumerator was created.
|
||||
/// </exception>
|
||||
public void Reset() {
|
||||
this.containedTypeEnumerator.Reset();
|
||||
}
|
||||
|
||||
/// <summary>The current element in the collection.</summary>
|
||||
/// <exception cref="System.InvalidOperationException">
|
||||
/// The enumerator is positioned before the first element of the collection
|
||||
/// or after the last element.
|
||||
/// </exception>
|
||||
object IEnumerator.Current {
|
||||
get { return Current; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Collection that owns this enumerator; required to invoke the item
|
||||
/// transformation method.
|
||||
/// </summary>
|
||||
private TransformingReadOnlyCollection<TContainedItem, TExposedItem> transformer;
|
||||
/// <summary>An enumerator from the wrapped collection</summary>
|
||||
private IEnumerator<TContainedItem> containedTypeEnumerator;
|
||||
|
||||
}
|
||||
|
||||
#endregion // class TransformingEnumerator
|
||||
|
||||
/// <summary>Initializes a new transforming collection wrapper</summary>
|
||||
/// <param name="items">
|
||||
/// Internal list of items that are transformed into the exposed type when
|
||||
/// accessed through the TransformingReadOnlyCollection.
|
||||
/// </param>
|
||||
public TransformingReadOnlyCollection(IList<TContainedItem> items) {
|
||||
this.items = items;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether an element is in the TransformingReadOnlyCollection
|
||||
/// </summary>
|
||||
/// <param name="item">
|
||||
/// The object to locate in the TransformingReadOnlyCollection.
|
||||
/// The value can be null for reference types.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// True if value is found in the TransformingReadOnlyCollection; otherwise, false.
|
||||
/// </returns>
|
||||
/// <remarks>
|
||||
/// The default implementation of this method is very unoptimized and will
|
||||
/// enumerate all the items in the collection, transforming one after another
|
||||
/// to check whether the transformed item matches the item the user was
|
||||
/// looking for. It is recommended to provide a custom implementation of
|
||||
/// this method, if possible.
|
||||
/// </remarks>
|
||||
public virtual bool Contains(TExposedItem item) {
|
||||
return (IndexOf(item) != -1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies the entire TransformingReadOnlyCollection to a compatible one-dimensional
|
||||
/// System.Array, starting at the specified index of the target array.
|
||||
/// </summary>
|
||||
/// <param name="array">
|
||||
/// The one-dimensional System.Array that is the destination of the elements copied
|
||||
/// from the TransformingReadOnlyCollection. The System.Array must have
|
||||
/// zero-based indexing.
|
||||
/// </param>
|
||||
/// <param name="index">
|
||||
/// The zero-based index in array at which copying begins.
|
||||
/// </param>
|
||||
/// <exception cref="System.ArgumentException">
|
||||
/// Index is equal to or greater than the length of array or the number of elements
|
||||
/// in the source TransformingReadOnlyCollection is greater than the available space
|
||||
/// from index to the end of the destination array.
|
||||
/// </exception>
|
||||
/// <exception cref="System.ArgumentOutOfRangeException">
|
||||
/// Index is less than zero.
|
||||
/// </exception>
|
||||
/// <exception cref="System.ArgumentNullException">
|
||||
/// Array is null.
|
||||
/// </exception>
|
||||
public void CopyTo(TExposedItem[] array, int index) {
|
||||
if(this.items.Count > (array.Length - index)) {
|
||||
throw new ArgumentException(
|
||||
"Array too small to fit the collection items starting at the specified index"
|
||||
);
|
||||
}
|
||||
|
||||
for(int itemIndex = 0; itemIndex < this.items.Count; ++itemIndex) {
|
||||
array[itemIndex + index] = Transform(this.items[itemIndex]);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns an enumerator that iterates through the TransformingReadOnlyCollection.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// An enumerator or the TransformingReadOnlyCollection.
|
||||
/// </returns>
|
||||
public IEnumerator<TExposedItem> GetEnumerator() {
|
||||
return new TransformingEnumerator(this, this.items.GetEnumerator());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Searches for the specified object and returns the zero-based index of the
|
||||
/// first occurrence within the entire TransformingReadOnlyCollection.
|
||||
/// </summary>
|
||||
/// <param name="item">
|
||||
/// The object to locate in the TransformingReadOnlyCollection. The value can
|
||||
/// be null for reference types.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// The zero-based index of the first occurrence of item within the entire
|
||||
/// TransformingReadOnlyCollection, if found; otherwise, -1.
|
||||
/// </returns>
|
||||
/// <remarks>
|
||||
/// The default implementation of this method is very unoptimized and will
|
||||
/// enumerate all the items in the collection, transforming one after another
|
||||
/// to check whether the transformed item matches the item the user was
|
||||
/// looking for. It is recommended to provide a custom implementation of
|
||||
/// this method, if possible.
|
||||
/// </remarks>
|
||||
public int IndexOf(TExposedItem item) {
|
||||
|
||||
if(item == null) {
|
||||
|
||||
for(int index = 0; index < this.items.Count; ++index) {
|
||||
if(Transform(this.items[index]) == null) {
|
||||
return index;
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
var comparer = EqualityComparer<TExposedItem>.Default;
|
||||
for(int index = 0; index < this.items.Count; ++index) {
|
||||
if(comparer.Equals(Transform(this.items[index]), item)) {
|
||||
return index;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return -1;
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The number of elements contained in the TransformingReadOnlyCollection instance
|
||||
/// </summary>
|
||||
public int Count {
|
||||
get { return this.items.Count; }
|
||||
}
|
||||
|
||||
/// <summary>Gets the element at the specified index.</summary>
|
||||
/// <param name="index">The zero-based index of the element to get.</param>
|
||||
/// <returns>The element at the specified index.</returns>
|
||||
/// <exception cref="System.ArgumentOutOfRangeException">
|
||||
/// Index is less than zero or index is equal to or greater than
|
||||
/// TransformingReadOnlyCollection.Count.
|
||||
/// </exception>
|
||||
public TExposedItem this[int index] {
|
||||
get { return Transform(this.items[index]); }
|
||||
}
|
||||
|
||||
/// <summary>Whether the List is write-protected</summary>
|
||||
public bool IsReadOnly {
|
||||
get { return true; }
|
||||
}
|
||||
|
||||
/// <summary>Transforms an item into the exposed type</summary>
|
||||
/// <param name="item">Item to be transformed</param>
|
||||
/// <returns>The transformed item</returns>
|
||||
/// <remarks>
|
||||
/// This method is used to transform an item in the wrapped collection into
|
||||
/// the exposed item type whenever the user accesses an item. Expect it to
|
||||
/// be called frequently, because the TransformingReadOnlyCollection does
|
||||
/// not cache or otherwise store the transformed items.
|
||||
/// </remarks>
|
||||
protected abstract TExposedItem Transform(TContainedItem item);
|
||||
|
||||
/// <summary>Items being transformed upon exposure by this collection</summary>
|
||||
private IList<TContainedItem> items;
|
||||
/// <summary>Synchronization root for threaded accesses to this collection</summary>
|
||||
private object syncRoot;
|
||||
|
||||
}
|
||||
|
||||
} // namespace Nuclex.Support.Collections
|
||||
|
|
|
|||
|
|
@ -1,88 +1,87 @@
|
|||
#region CPL License
|
||||
/*
|
||||
Nuclex Framework
|
||||
Copyright (C) 2002-2017 Nuclex Development Labs
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the IBM Common Public License as
|
||||
published by the IBM Corporation; either version 1.0 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
IBM Common Public License for more details.
|
||||
|
||||
You should have received a copy of the IBM Common Public
|
||||
License along with this library
|
||||
*/
|
||||
#endregion
|
||||
|
||||
#if !NO_SETS
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
#if UNITTEST
|
||||
|
||||
using NUnit.Framework;
|
||||
|
||||
namespace Nuclex.Support.Collections {
|
||||
|
||||
/// <summary>Unit Test for the Variegator multi dictionary</summary>
|
||||
[TestFixture]
|
||||
internal class VariegatorTest {
|
||||
|
||||
/// <summary>
|
||||
/// Tests whether the default constructor of the reverse comparer works
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void InstancesCanBeCreated() {
|
||||
new Variegator<int, string>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that querying for a missing value leads to an exception being thrown
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void QueryingMissingValueThrowsException() {
|
||||
var variegator = new Variegator<int, string>();
|
||||
Assert.Throws<KeyNotFoundException>(
|
||||
() => {
|
||||
variegator.Get(123);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that the variegator resolves ambiguous matches according to its design
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void AmbiguityResolvesToLeastRecentValue() {
|
||||
var variegator = new Variegator<int, string>();
|
||||
variegator.Add(1, "one");
|
||||
variegator.Add(1, "eins");
|
||||
|
||||
string first = variegator.Get(1);
|
||||
string second = variegator.Get(1);
|
||||
|
||||
// The variegator should have selected the first value by random and then
|
||||
// returned the other value on the second query
|
||||
Assert.AreNotEqual(first, second);
|
||||
|
||||
// Now the variegator should return the first value again because it is
|
||||
// the least recently used value
|
||||
Assert.AreEqual(first, variegator.Get(1));
|
||||
|
||||
// Repeating the query, the second should be returned again because now
|
||||
// it has become the least recently used value
|
||||
Assert.AreEqual(second, variegator.Get(1));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} // namespace Nuclex.Support.Collections
|
||||
|
||||
#endif // UNITTEST
|
||||
|
||||
#endif // !NO_SETS
|
||||
#region Apache License 2.0
|
||||
/*
|
||||
Nuclex .NET Framework
|
||||
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
#endregion // Apache License 2.0
|
||||
|
||||
#if !NO_SETS
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
#if UNITTEST
|
||||
|
||||
using NUnit.Framework;
|
||||
|
||||
namespace Nuclex.Support.Collections {
|
||||
|
||||
/// <summary>Unit Test for the Variegator multi dictionary</summary>
|
||||
[TestFixture]
|
||||
internal class VariegatorTest {
|
||||
|
||||
/// <summary>
|
||||
/// Tests whether the default constructor of the reverse comparer works
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void InstancesCanBeCreated() {
|
||||
new Variegator<int, string>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that querying for a missing value leads to an exception being thrown
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void QueryingMissingValueThrowsException() {
|
||||
var variegator = new Variegator<int, string>();
|
||||
Assert.Throws<KeyNotFoundException>(
|
||||
() => {
|
||||
variegator.Get(123);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that the variegator resolves ambiguous matches according to its design
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void AmbiguityResolvesToLeastRecentValue() {
|
||||
var variegator = new Variegator<int, string>();
|
||||
variegator.Add(1, "one");
|
||||
variegator.Add(1, "eins");
|
||||
|
||||
string first = variegator.Get(1);
|
||||
string second = variegator.Get(1);
|
||||
|
||||
// The variegator should have selected the first value by random and then
|
||||
// returned the other value on the second query
|
||||
Assert.AreNotEqual(first, second);
|
||||
|
||||
// Now the variegator should return the first value again because it is
|
||||
// the least recently used value
|
||||
Assert.AreEqual(first, variegator.Get(1));
|
||||
|
||||
// Repeating the query, the second should be returned again because now
|
||||
// it has become the least recently used value
|
||||
Assert.AreEqual(second, variegator.Get(1));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} // namespace Nuclex.Support.Collections
|
||||
|
||||
#endif // UNITTEST
|
||||
|
||||
#endif // !NO_SETS
|
||||
|
|
|
|||
|
|
@ -1,287 +1,286 @@
|
|||
#region CPL License
|
||||
/*
|
||||
Nuclex Native Framework
|
||||
Copyright (C) 2002-2017 Nuclex Development Labs
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the IBM Common Public License as
|
||||
published by the IBM Corporation; either version 1.0 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
IBM Common Public License for more details.
|
||||
|
||||
You should have received a copy of the IBM Common Public
|
||||
License along with this library
|
||||
*/
|
||||
#endregion // CPL License
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
#if !NO_SETS
|
||||
|
||||
namespace Nuclex.Support.Collections {
|
||||
|
||||
/// <summary>Randomly selects between different options, trying to avoid repetition</summary>
|
||||
/// <typeparam name="TKey">Type of keys through which values can be looked up</typeparam>
|
||||
/// <typeparam name="TValue">Type of values provided by the variegator</typeparam>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// This class is useful wherever randomness is involved in a game: picking random
|
||||
/// actions for an NPC to execute, selecting different songs to play, displaying
|
||||
/// different dialogue and more.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// In principle, it works like a multimap, associating keys with a number of values
|
||||
/// and allowing you to look up values by their keys. Unlike a multimap, it will try
|
||||
/// to avoid handing out a previously provided value again as long as possible.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// A typical usage would be to set up a mapping between situations and dialogue lines.
|
||||
/// Upon calling <see cref="Get(TKey)" /> with the situation 'detected-player-stealing',
|
||||
/// the variegator would return a random (but not recently used) value which in this case
|
||||
/// might contain a commentary an NPC might make upon encountering that situation.
|
||||
/// Other NPCs requesting dialogue lines for the same situation would receive different
|
||||
/// random commentary for as long as long as available data allows.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public class Variegator<TKey, TValue> {
|
||||
|
||||
/// <summary>Initializes a new variegator using the default history length</summary>
|
||||
public Variegator() : this(64) {}
|
||||
|
||||
/// <summary>Initializes a new variegator</summary>
|
||||
/// <param name="historyLength">
|
||||
/// How far into the past the variegator will look to avoid repetition
|
||||
/// </param>
|
||||
public Variegator(int historyLength) {
|
||||
this.historyLength = historyLength;
|
||||
this.history = new TValue[historyLength];
|
||||
this.values = new MultiDictionary<TKey, TValue>();
|
||||
|
||||
this.randomNumberGenerator = new Random();
|
||||
}
|
||||
|
||||
/// <summary>Removes all entries from the variegator</summary>
|
||||
/// <remarks>
|
||||
/// This is mainly useful if you are storing smart pointers to values of substantial
|
||||
/// size (eg. audio clips instead of just resource proxies or paths) and need to
|
||||
/// reclaim memory.
|
||||
/// </remarks>
|
||||
public void Clear() {
|
||||
freeHistory();
|
||||
this.historyFull = false;
|
||||
this.historyTailIndex = 0;
|
||||
}
|
||||
|
||||
/// <summary>Checks whether the variegator is empty</summary>
|
||||
/// <returns>True if there are no entries in the variegator</returns>
|
||||
public bool IsEmpty {
|
||||
get { return (Count == 0); }
|
||||
}
|
||||
|
||||
/// <summary>Returns the number of values in the variegator</summary>
|
||||
/// <returns>The number of values stored in the variegator</returns>
|
||||
/// <remarks>
|
||||
/// If the same value is added with different keys (a situation that doesn't make
|
||||
/// sense because such reuse should be covered by specifying multiple keys in
|
||||
/// a query), it will be counted multiple times.
|
||||
/// </remarks>
|
||||
public int Count {
|
||||
get { return ((System.Collections.ICollection)this.values).Count; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Insert a new value that can be returned when requesting the specified key
|
||||
/// </summary>
|
||||
/// <param name="key">Key of the value that will be inserted</param>
|
||||
/// <param name="value">Value that will be inserted under the provided key</param>
|
||||
public void Add(TKey key, TValue value) {
|
||||
this.values.Add(key, value);
|
||||
}
|
||||
|
||||
/// <summary>Retrieves a random value associated with the specified key</summary>
|
||||
/// <param name="key">For for which a value will be looked up</param>
|
||||
/// <returns>A random value associated with the specified key</returns>
|
||||
public TValue Get(TKey key) {
|
||||
ISet<TValue> candidates = new HashSet<TValue>();
|
||||
{
|
||||
ICollection<TValue> valueRange = this.values[key];
|
||||
|
||||
// If possible access the values by index because it's faster and produces less
|
||||
// garbage, otherwise fall back to using an enumerator
|
||||
var indexableValueRange = valueRange as IList<TValue>;
|
||||
if(indexableValueRange == null) {
|
||||
foreach(TValue value in valueRange) {
|
||||
candidates.Add(value);
|
||||
}
|
||||
} else {
|
||||
for(int valueIndex = 0; valueIndex < indexableValueRange.Count; ++valueIndex) {
|
||||
candidates.Add(indexableValueRange[valueIndex]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TValue result = destructivePickCandidateValue(candidates);
|
||||
addRecentlyUsedValue(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>Retrieves a random value associated with one of the specified keys</summary>
|
||||
/// <param name="keys">Keys that will be considered</param>
|
||||
/// <remarks>
|
||||
/// In many cases, you have generic situations (such as 'detected-player-stealing',
|
||||
/// 'observed-hostile-action') and specified situations (such as
|
||||
/// 'detected-player-stealing-from-beggar', 'observed-hostile-action-on-cop')
|
||||
/// where a values from both pools should be considered. This method allows you
|
||||
/// to specify any number of keys, creating a greater set of values the variegator
|
||||
/// can pick between.
|
||||
/// </remarks>
|
||||
public TValue Get(params TKey[] keys) {
|
||||
ISet<TValue> candidates = new HashSet<TValue>();
|
||||
|
||||
for(int index = 0; index < keys.Length; ++index) {
|
||||
ICollection<TValue> valueRange = this.values[keys[index]];
|
||||
|
||||
// If possible access the values by index because it's faster and produces less
|
||||
// garbage, otherwise fall back to using an enumerator
|
||||
var indexableValueRange = valueRange as IList<TValue>;
|
||||
if(indexableValueRange == null) {
|
||||
foreach(TValue value in valueRange) {
|
||||
candidates.Add(value);
|
||||
}
|
||||
} else {
|
||||
for(int valueIndex = 0; valueIndex < indexableValueRange.Count; ++valueIndex) {
|
||||
candidates.Add(indexableValueRange[valueIndex]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TValue result = destructivePickCandidateValue(candidates);
|
||||
addRecentlyUsedValue(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>Picks amongst the values in a set</summary>
|
||||
/// <param name="candidates">
|
||||
/// Set containing the candidats values to consider. Will be destroyed.
|
||||
/// </param>
|
||||
/// <returns>The least recently used candidate value or a random one</returns>
|
||||
private TValue destructivePickCandidateValue(ISet<TValue> candidates) {
|
||||
removeRecentlyUsedValues(candidates);
|
||||
|
||||
switch(candidates.Count) {
|
||||
case 0: {
|
||||
throw new InvalidOperationException("No values mapped to this key");
|
||||
}
|
||||
case 1: {
|
||||
using(IEnumerator<TValue> enumerator = candidates.GetEnumerator()) {
|
||||
enumerator.MoveNext(); // We can be sure this one returns true
|
||||
return enumerator.Current;
|
||||
}
|
||||
}
|
||||
default: {
|
||||
int index = this.randomNumberGenerator.Next(candidates.Count);
|
||||
using(IEnumerator<TValue> enumerator = candidates.GetEnumerator()) {
|
||||
do {
|
||||
--index;
|
||||
enumerator.MoveNext(); // We can be sure this one returns true
|
||||
} while(index >= 0);
|
||||
|
||||
return enumerator.Current;
|
||||
}
|
||||
|
||||
throw new InvalidOperationException(
|
||||
"ISet.Count was off or random number generator malfunctioned"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Adds a recently used value to the history</summary>
|
||||
/// <param name="value">Value that will be added to the history</param>
|
||||
private void addRecentlyUsedValue(TValue value) {
|
||||
if(this.historyTailIndex == this.historyLength) {
|
||||
this.historyFull = true;
|
||||
this.history[0] = value;
|
||||
this.historyTailIndex = 1;
|
||||
} else {
|
||||
this.history[this.historyTailIndex] = value;
|
||||
++this.historyTailIndex;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Removes all values that are in the recent use list from a set</summary>
|
||||
/// <param name="candidates">Set from which recently used values are removed</param>
|
||||
/// <remarks>
|
||||
/// Stops removing values when there's only 1 value left in the set
|
||||
/// </remarks>
|
||||
private void removeRecentlyUsedValues(ISet<TValue> candidates) {
|
||||
if(candidates.Count <= 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(this.historyFull) { // History buffer has wrapped around
|
||||
int index = this.historyTailIndex;
|
||||
while(index > 0) {
|
||||
--index;
|
||||
if(candidates.Remove(this.history[index])) {
|
||||
if(candidates.Count <= 1) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
index = this.historyLength;
|
||||
while(index > this.historyTailIndex) {
|
||||
--index;
|
||||
if(candidates.Remove(this.history[index])) {
|
||||
if(candidates.Count <= 1) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else { // History buffer was not full yet
|
||||
int index = this.historyTailIndex;
|
||||
while(index > 0) {
|
||||
--index;
|
||||
if(candidates.Remove(this.history[index])) {
|
||||
if(candidates.Count <= 1) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Frees all memory used by the individual history entries</summary>
|
||||
/// <remarks>
|
||||
/// The history array itself is kept alive and the tail index + full flag will
|
||||
/// not be reset.
|
||||
/// </remarks>
|
||||
private void freeHistory() {
|
||||
Array.Clear(this.history, 0, this.historyLength);
|
||||
}
|
||||
|
||||
/// <summary>Stores the entries the variegator can select from by their keys</summary>
|
||||
private IMultiDictionary<TKey, TValue> values;
|
||||
|
||||
/// <summary>Random number generator that will be used to pick random values</summary>
|
||||
private Random randomNumberGenerator;
|
||||
/// <summary>Number of entries in the recently used list</summary>
|
||||
private int historyLength;
|
||||
|
||||
/// <summary>Array containing the most recently provided values</summary>
|
||||
private TValue[] history;
|
||||
/// <summary>Index of the tail in the recently used value array</summary>
|
||||
private int historyTailIndex;
|
||||
/// <summary>Whether the recently used value history is at capacity</summary>
|
||||
private bool historyFull;
|
||||
|
||||
}
|
||||
|
||||
} // namespace Nuclex.Support.Collections
|
||||
|
||||
#endif // !NO_SETS
|
||||
#region Apache License 2.0
|
||||
/*
|
||||
Nuclex .NET Framework
|
||||
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
#endregion // Apache License 2.0
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
#if !NO_SETS
|
||||
|
||||
namespace Nuclex.Support.Collections {
|
||||
|
||||
/// <summary>Randomly selects between different options, trying to avoid repetition</summary>
|
||||
/// <typeparam name="TKey">Type of keys through which values can be looked up</typeparam>
|
||||
/// <typeparam name="TValue">Type of values provided by the variegator</typeparam>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// This class is useful wherever randomness is involved in a game: picking random
|
||||
/// actions for an NPC to execute, selecting different songs to play, displaying
|
||||
/// different dialogue and more.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// In principle, it works like a multimap, associating keys with a number of values
|
||||
/// and allowing you to look up values by their keys. Unlike a multimap, it will try
|
||||
/// to avoid handing out a previously provided value again as long as possible.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// A typical usage would be to set up a mapping between situations and dialogue lines.
|
||||
/// Upon calling <see cref="Get(TKey)" /> with the situation 'detected-player-stealing',
|
||||
/// the variegator would return a random (but not recently used) value which in this case
|
||||
/// might contain a commentary an NPC might make upon encountering that situation.
|
||||
/// Other NPCs requesting dialogue lines for the same situation would receive different
|
||||
/// random commentary for as long as long as available data allows.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public class Variegator<TKey, TValue> {
|
||||
|
||||
/// <summary>Initializes a new variegator using the default history length</summary>
|
||||
public Variegator() : this(64) {}
|
||||
|
||||
/// <summary>Initializes a new variegator</summary>
|
||||
/// <param name="historyLength">
|
||||
/// How far into the past the variegator will look to avoid repetition
|
||||
/// </param>
|
||||
public Variegator(int historyLength) {
|
||||
this.historyLength = historyLength;
|
||||
this.history = new TValue[historyLength];
|
||||
this.values = new MultiDictionary<TKey, TValue>();
|
||||
|
||||
this.randomNumberGenerator = new Random();
|
||||
}
|
||||
|
||||
/// <summary>Removes all entries from the variegator</summary>
|
||||
/// <remarks>
|
||||
/// This is mainly useful if you are storing smart pointers to values of substantial
|
||||
/// size (eg. audio clips instead of just resource proxies or paths) and need to
|
||||
/// reclaim memory.
|
||||
/// </remarks>
|
||||
public void Clear() {
|
||||
freeHistory();
|
||||
this.historyFull = false;
|
||||
this.historyTailIndex = 0;
|
||||
}
|
||||
|
||||
/// <summary>Checks whether the variegator is empty</summary>
|
||||
/// <returns>True if there are no entries in the variegator</returns>
|
||||
public bool IsEmpty {
|
||||
get { return (Count == 0); }
|
||||
}
|
||||
|
||||
/// <summary>Returns the number of values in the variegator</summary>
|
||||
/// <returns>The number of values stored in the variegator</returns>
|
||||
/// <remarks>
|
||||
/// If the same value is added with different keys (a situation that doesn't make
|
||||
/// sense because such reuse should be covered by specifying multiple keys in
|
||||
/// a query), it will be counted multiple times.
|
||||
/// </remarks>
|
||||
public int Count {
|
||||
get { return ((System.Collections.ICollection)this.values).Count; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Insert a new value that can be returned when requesting the specified key
|
||||
/// </summary>
|
||||
/// <param name="key">Key of the value that will be inserted</param>
|
||||
/// <param name="value">Value that will be inserted under the provided key</param>
|
||||
public void Add(TKey key, TValue value) {
|
||||
this.values.Add(key, value);
|
||||
}
|
||||
|
||||
/// <summary>Retrieves a random value associated with the specified key</summary>
|
||||
/// <param name="key">For for which a value will be looked up</param>
|
||||
/// <returns>A random value associated with the specified key</returns>
|
||||
public TValue Get(TKey key) {
|
||||
ISet<TValue> candidates = new HashSet<TValue>();
|
||||
{
|
||||
ICollection<TValue> valueRange = this.values[key];
|
||||
|
||||
// If possible access the values by index because it's faster and produces less
|
||||
// garbage, otherwise fall back to using an enumerator
|
||||
var indexableValueRange = valueRange as IList<TValue>;
|
||||
if(indexableValueRange == null) {
|
||||
foreach(TValue value in valueRange) {
|
||||
candidates.Add(value);
|
||||
}
|
||||
} else {
|
||||
for(int valueIndex = 0; valueIndex < indexableValueRange.Count; ++valueIndex) {
|
||||
candidates.Add(indexableValueRange[valueIndex]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TValue result = destructivePickCandidateValue(candidates);
|
||||
addRecentlyUsedValue(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>Retrieves a random value associated with one of the specified keys</summary>
|
||||
/// <param name="keys">Keys that will be considered</param>
|
||||
/// <remarks>
|
||||
/// In many cases, you have generic situations (such as 'detected-player-stealing',
|
||||
/// 'observed-hostile-action') and specified situations (such as
|
||||
/// 'detected-player-stealing-from-beggar', 'observed-hostile-action-on-cop')
|
||||
/// where a values from both pools should be considered. This method allows you
|
||||
/// to specify any number of keys, creating a greater set of values the variegator
|
||||
/// can pick between.
|
||||
/// </remarks>
|
||||
public TValue Get(params TKey[] keys) {
|
||||
ISet<TValue> candidates = new HashSet<TValue>();
|
||||
|
||||
for(int index = 0; index < keys.Length; ++index) {
|
||||
ICollection<TValue> valueRange = this.values[keys[index]];
|
||||
|
||||
// If possible access the values by index because it's faster and produces less
|
||||
// garbage, otherwise fall back to using an enumerator
|
||||
var indexableValueRange = valueRange as IList<TValue>;
|
||||
if(indexableValueRange == null) {
|
||||
foreach(TValue value in valueRange) {
|
||||
candidates.Add(value);
|
||||
}
|
||||
} else {
|
||||
for(int valueIndex = 0; valueIndex < indexableValueRange.Count; ++valueIndex) {
|
||||
candidates.Add(indexableValueRange[valueIndex]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TValue result = destructivePickCandidateValue(candidates);
|
||||
addRecentlyUsedValue(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>Picks amongst the values in a set</summary>
|
||||
/// <param name="candidates">
|
||||
/// Set containing the candidats values to consider. Will be destroyed.
|
||||
/// </param>
|
||||
/// <returns>The least recently used candidate value or a random one</returns>
|
||||
private TValue destructivePickCandidateValue(ISet<TValue> candidates) {
|
||||
removeRecentlyUsedValues(candidates);
|
||||
|
||||
switch(candidates.Count) {
|
||||
case 0: {
|
||||
throw new InvalidOperationException("No values mapped to this key");
|
||||
}
|
||||
case 1: {
|
||||
using(IEnumerator<TValue> enumerator = candidates.GetEnumerator()) {
|
||||
enumerator.MoveNext(); // We can be sure this one returns true
|
||||
return enumerator.Current;
|
||||
}
|
||||
}
|
||||
default: {
|
||||
int index = this.randomNumberGenerator.Next(candidates.Count);
|
||||
using(IEnumerator<TValue> enumerator = candidates.GetEnumerator()) {
|
||||
do {
|
||||
--index;
|
||||
enumerator.MoveNext(); // We can be sure this one returns true
|
||||
} while(index >= 0);
|
||||
|
||||
return enumerator.Current;
|
||||
}
|
||||
|
||||
throw new InvalidOperationException(
|
||||
"ISet.Count was off or random number generator malfunctioned"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Adds a recently used value to the history</summary>
|
||||
/// <param name="value">Value that will be added to the history</param>
|
||||
private void addRecentlyUsedValue(TValue value) {
|
||||
if(this.historyTailIndex == this.historyLength) {
|
||||
this.historyFull = true;
|
||||
this.history[0] = value;
|
||||
this.historyTailIndex = 1;
|
||||
} else {
|
||||
this.history[this.historyTailIndex] = value;
|
||||
++this.historyTailIndex;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Removes all values that are in the recent use list from a set</summary>
|
||||
/// <param name="candidates">Set from which recently used values are removed</param>
|
||||
/// <remarks>
|
||||
/// Stops removing values when there's only 1 value left in the set
|
||||
/// </remarks>
|
||||
private void removeRecentlyUsedValues(ISet<TValue> candidates) {
|
||||
if(candidates.Count <= 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(this.historyFull) { // History buffer has wrapped around
|
||||
int index = this.historyTailIndex;
|
||||
while(index > 0) {
|
||||
--index;
|
||||
if(candidates.Remove(this.history[index])) {
|
||||
if(candidates.Count <= 1) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
index = this.historyLength;
|
||||
while(index > this.historyTailIndex) {
|
||||
--index;
|
||||
if(candidates.Remove(this.history[index])) {
|
||||
if(candidates.Count <= 1) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else { // History buffer was not full yet
|
||||
int index = this.historyTailIndex;
|
||||
while(index > 0) {
|
||||
--index;
|
||||
if(candidates.Remove(this.history[index])) {
|
||||
if(candidates.Count <= 1) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Frees all memory used by the individual history entries</summary>
|
||||
/// <remarks>
|
||||
/// The history array itself is kept alive and the tail index + full flag will
|
||||
/// not be reset.
|
||||
/// </remarks>
|
||||
private void freeHistory() {
|
||||
Array.Clear(this.history, 0, this.historyLength);
|
||||
}
|
||||
|
||||
/// <summary>Stores the entries the variegator can select from by their keys</summary>
|
||||
private IMultiDictionary<TKey, TValue> values;
|
||||
|
||||
/// <summary>Random number generator that will be used to pick random values</summary>
|
||||
private Random randomNumberGenerator;
|
||||
/// <summary>Number of entries in the recently used list</summary>
|
||||
private int historyLength;
|
||||
|
||||
/// <summary>Array containing the most recently provided values</summary>
|
||||
private TValue[] history;
|
||||
/// <summary>Index of the tail in the recently used value array</summary>
|
||||
private int historyTailIndex;
|
||||
/// <summary>Whether the recently used value history is at capacity</summary>
|
||||
private bool historyFull;
|
||||
|
||||
}
|
||||
|
||||
} // namespace Nuclex.Support.Collections
|
||||
|
||||
#endif // !NO_SETS
|
||||
|
|
|
|||
|
|
@ -1,211 +1,210 @@
|
|||
#region CPL License
|
||||
/*
|
||||
Nuclex Framework
|
||||
Copyright (C) 2002-2017 Nuclex Development Labs
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the IBM Common Public License as
|
||||
published by the IBM Corporation; either version 1.0 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
IBM Common Public License for more details.
|
||||
|
||||
You should have received a copy of the IBM Common Public
|
||||
License along with this library
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Nuclex.Support.Collections {
|
||||
|
||||
partial class WeakCollection<TItem> {
|
||||
|
||||
#region IEnumerable Members
|
||||
|
||||
/// <summary>Returns an enumerator that iterates through a collection.</summary>
|
||||
/// <returns>
|
||||
/// A System.Collections.IEnumerator object that can be used to iterate through
|
||||
/// the collection.
|
||||
/// </returns>
|
||||
IEnumerator IEnumerable.GetEnumerator() {
|
||||
return GetEnumerator();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IList Members
|
||||
|
||||
/// <summary>Adds an item to the WeakCollection.</summary>
|
||||
/// <param name="value">The System.Object to add to the WeakCollection.</param>
|
||||
/// <returns>The position into which the new element was inserted.</returns>
|
||||
/// <exception cref="System.NotSupportedException">
|
||||
/// The System.Collections.IList is read-only or the WeakCollection has a fixed size.
|
||||
/// </exception>
|
||||
int IList.Add(object value) {
|
||||
TItem valueAsItemType = downcastToItemType(value);
|
||||
return (this.items as IList).Add(new WeakReference<TItem>(valueAsItemType));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the WeakCollection contains a specific value.
|
||||
/// </summary>
|
||||
/// <param name="value">The System.Object to locate in the WeakCollection.</param>
|
||||
/// <returns>
|
||||
/// True if the System.Object is found in the WeakCollection; otherwise, false.
|
||||
/// </returns>
|
||||
bool IList.Contains(object value) {
|
||||
TItem valueAsItemType = downcastToItemType(value);
|
||||
return Contains(valueAsItemType);
|
||||
}
|
||||
|
||||
/// <summary>Determines the index of a specific item in the WeakCollection.</summary>
|
||||
/// <param name="value">The System.Object to locate in the WeakCollection.</param>
|
||||
/// <returns>
|
||||
/// The index of value if found in the list; otherwise, -1.
|
||||
/// </returns>
|
||||
int IList.IndexOf(object value) {
|
||||
TItem valueAsItemType = downcastToItemType(value);
|
||||
return IndexOf(valueAsItemType);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Inserts an item to the WeakCollection at the specified index.
|
||||
/// </summary>
|
||||
/// <param name="index">
|
||||
/// The zero-based index at which value should be inserted.
|
||||
/// </param>
|
||||
/// <param name="value">The System.Object to insert into the WeakCollection.</param>
|
||||
/// <exception cref="System.ArgumentOutOfRangeException">
|
||||
/// Index is not a valid index in the TransformingReadOnlyCollection.
|
||||
/// </exception>
|
||||
/// <exception cref="System.NotSupportedException">
|
||||
/// The System.Collections.IList is read-only or the WeakCollection has a fixed size.
|
||||
/// </exception>
|
||||
/// <exception cref="System.NullReferenceException">
|
||||
/// Value is null reference in the WeakCollection.
|
||||
/// </exception>
|
||||
void IList.Insert(int index, object value) {
|
||||
TItem valueAsItemType = downcastToItemType(value);
|
||||
Insert(index, valueAsItemType);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A value indicating whether the WeakCollection has a fixed size.
|
||||
/// </summary>
|
||||
bool IList.IsFixedSize {
|
||||
get { return (this.items as IList).IsFixedSize; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes the first occurrence of a specific object from the WeakCollection.
|
||||
/// </summary>
|
||||
/// <param name="value">The System.Object to remove from the WeakCollection.</param>
|
||||
/// <exception cref="System.NotSupportedException">
|
||||
/// The WeakCollection is read-only or the WeakCollection has a fixed size.
|
||||
/// </exception>
|
||||
void IList.Remove(object value) {
|
||||
TItem valueAsItemType = downcastToItemType(value);
|
||||
Remove(valueAsItemType);
|
||||
}
|
||||
|
||||
/// <summary>Gets or sets the element at the specified index.</summary>
|
||||
/// <param name="index">The zero-based index of the element to get or set.</param>
|
||||
/// <returns>The element at the specified index</returns>
|
||||
/// <exception cref="System.ArgumentOutOfRangeException">
|
||||
/// Index is not a valid index in the WeakCollection
|
||||
/// </exception>
|
||||
object IList.this[int index] {
|
||||
get { return this[index]; }
|
||||
set {
|
||||
TItem valueAsItemType = downcastToItemType(value);
|
||||
this[index] = valueAsItemType;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region ICollection Members
|
||||
|
||||
/// <summary>
|
||||
/// Copies the elements of the WeakCollection to an System.Array, starting at
|
||||
/// a particular System.Array index.
|
||||
/// </summary>
|
||||
/// <param name="array">
|
||||
/// The one-dimensional System.Array that is the destination of the elements
|
||||
/// copied from WeakCollection. The System.Array must have zero-based indexing.
|
||||
/// </param>
|
||||
/// <param name="index">The zero-based index in array at which copying begins.</param>
|
||||
/// <exception cref="System.ArgumentNullException">
|
||||
/// Array is null.
|
||||
/// </exception>
|
||||
/// <exception cref="System.ArgumentOutOfRangeException">
|
||||
/// Index is less than zero.
|
||||
/// </exception>
|
||||
/// <exception cref="System.ArgumentException">
|
||||
/// Array is multidimensional or index is equal to or greater than the length
|
||||
/// of array or the number of elements in the source WeakCollection is greater than
|
||||
/// the available space from index to the end of the destination array.
|
||||
/// </exception>
|
||||
/// <exception cref="System.InvalidCastException">
|
||||
/// The type of the source WeakCollection cannot be cast automatically to the type of
|
||||
/// the destination array.
|
||||
/// </exception>
|
||||
void ICollection.CopyTo(Array array, int index) {
|
||||
CopyTo((TItem[])array, index);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A value indicating whether access to the WeakCollection is
|
||||
/// synchronized (thread safe).
|
||||
/// </summary>
|
||||
bool ICollection.IsSynchronized {
|
||||
get { return false; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An object that can be used to synchronize access to the WeakCollection.
|
||||
/// </summary>
|
||||
object ICollection.SyncRoot {
|
||||
get {
|
||||
if(this.syncRoot == null) {
|
||||
ICollection is2 = this.items as ICollection;
|
||||
if(is2 != null) {
|
||||
this.syncRoot = is2.SyncRoot;
|
||||
} else {
|
||||
Interlocked.CompareExchange(ref this.syncRoot, new object(), null);
|
||||
}
|
||||
}
|
||||
|
||||
return this.syncRoot;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Downcasts an object reference to a reference to the collection's item type
|
||||
/// </summary>
|
||||
/// <param name="value">Object reference that will be downcast</param>
|
||||
/// <returns>
|
||||
/// The specified object referecne as a reference to the collection's item type
|
||||
/// </returns>
|
||||
private static TItem downcastToItemType(object value) {
|
||||
TItem valueAsItemType = value as TItem;
|
||||
if(!ReferenceEquals(value, null)) {
|
||||
if(valueAsItemType == null) {
|
||||
throw new ArgumentException("Object is not of a compatible type", "value");
|
||||
}
|
||||
}
|
||||
return valueAsItemType;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} // namespace Nuclex.Support.Collections
|
||||
#region Apache License 2.0
|
||||
/*
|
||||
Nuclex .NET Framework
|
||||
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
#endregion // Apache License 2.0
|
||||
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Nuclex.Support.Collections {
|
||||
|
||||
partial class WeakCollection<TItem> {
|
||||
|
||||
#region IEnumerable Members
|
||||
|
||||
/// <summary>Returns an enumerator that iterates through a collection.</summary>
|
||||
/// <returns>
|
||||
/// A System.Collections.IEnumerator object that can be used to iterate through
|
||||
/// the collection.
|
||||
/// </returns>
|
||||
IEnumerator IEnumerable.GetEnumerator() {
|
||||
return GetEnumerator();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IList Members
|
||||
|
||||
/// <summary>Adds an item to the WeakCollection.</summary>
|
||||
/// <param name="value">The System.Object to add to the WeakCollection.</param>
|
||||
/// <returns>The position into which the new element was inserted.</returns>
|
||||
/// <exception cref="System.NotSupportedException">
|
||||
/// The System.Collections.IList is read-only or the WeakCollection has a fixed size.
|
||||
/// </exception>
|
||||
int IList.Add(object value) {
|
||||
TItem valueAsItemType = downcastToItemType(value);
|
||||
return (this.items as IList).Add(new WeakReference<TItem>(valueAsItemType));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the WeakCollection contains a specific value.
|
||||
/// </summary>
|
||||
/// <param name="value">The System.Object to locate in the WeakCollection.</param>
|
||||
/// <returns>
|
||||
/// True if the System.Object is found in the WeakCollection; otherwise, false.
|
||||
/// </returns>
|
||||
bool IList.Contains(object value) {
|
||||
TItem valueAsItemType = downcastToItemType(value);
|
||||
return Contains(valueAsItemType);
|
||||
}
|
||||
|
||||
/// <summary>Determines the index of a specific item in the WeakCollection.</summary>
|
||||
/// <param name="value">The System.Object to locate in the WeakCollection.</param>
|
||||
/// <returns>
|
||||
/// The index of value if found in the list; otherwise, -1.
|
||||
/// </returns>
|
||||
int IList.IndexOf(object value) {
|
||||
TItem valueAsItemType = downcastToItemType(value);
|
||||
return IndexOf(valueAsItemType);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Inserts an item to the WeakCollection at the specified index.
|
||||
/// </summary>
|
||||
/// <param name="index">
|
||||
/// The zero-based index at which value should be inserted.
|
||||
/// </param>
|
||||
/// <param name="value">The System.Object to insert into the WeakCollection.</param>
|
||||
/// <exception cref="System.ArgumentOutOfRangeException">
|
||||
/// Index is not a valid index in the TransformingReadOnlyCollection.
|
||||
/// </exception>
|
||||
/// <exception cref="System.NotSupportedException">
|
||||
/// The System.Collections.IList is read-only or the WeakCollection has a fixed size.
|
||||
/// </exception>
|
||||
/// <exception cref="System.NullReferenceException">
|
||||
/// Value is null reference in the WeakCollection.
|
||||
/// </exception>
|
||||
void IList.Insert(int index, object value) {
|
||||
TItem valueAsItemType = downcastToItemType(value);
|
||||
Insert(index, valueAsItemType);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A value indicating whether the WeakCollection has a fixed size.
|
||||
/// </summary>
|
||||
bool IList.IsFixedSize {
|
||||
get { return (this.items as IList).IsFixedSize; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes the first occurrence of a specific object from the WeakCollection.
|
||||
/// </summary>
|
||||
/// <param name="value">The System.Object to remove from the WeakCollection.</param>
|
||||
/// <exception cref="System.NotSupportedException">
|
||||
/// The WeakCollection is read-only or the WeakCollection has a fixed size.
|
||||
/// </exception>
|
||||
void IList.Remove(object value) {
|
||||
TItem valueAsItemType = downcastToItemType(value);
|
||||
Remove(valueAsItemType);
|
||||
}
|
||||
|
||||
/// <summary>Gets or sets the element at the specified index.</summary>
|
||||
/// <param name="index">The zero-based index of the element to get or set.</param>
|
||||
/// <returns>The element at the specified index</returns>
|
||||
/// <exception cref="System.ArgumentOutOfRangeException">
|
||||
/// Index is not a valid index in the WeakCollection
|
||||
/// </exception>
|
||||
object IList.this[int index] {
|
||||
get { return this[index]; }
|
||||
set {
|
||||
TItem valueAsItemType = downcastToItemType(value);
|
||||
this[index] = valueAsItemType;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region ICollection Members
|
||||
|
||||
/// <summary>
|
||||
/// Copies the elements of the WeakCollection to an System.Array, starting at
|
||||
/// a particular System.Array index.
|
||||
/// </summary>
|
||||
/// <param name="array">
|
||||
/// The one-dimensional System.Array that is the destination of the elements
|
||||
/// copied from WeakCollection. The System.Array must have zero-based indexing.
|
||||
/// </param>
|
||||
/// <param name="index">The zero-based index in array at which copying begins.</param>
|
||||
/// <exception cref="System.ArgumentNullException">
|
||||
/// Array is null.
|
||||
/// </exception>
|
||||
/// <exception cref="System.ArgumentOutOfRangeException">
|
||||
/// Index is less than zero.
|
||||
/// </exception>
|
||||
/// <exception cref="System.ArgumentException">
|
||||
/// Array is multidimensional or index is equal to or greater than the length
|
||||
/// of array or the number of elements in the source WeakCollection is greater than
|
||||
/// the available space from index to the end of the destination array.
|
||||
/// </exception>
|
||||
/// <exception cref="System.InvalidCastException">
|
||||
/// The type of the source WeakCollection cannot be cast automatically to the type of
|
||||
/// the destination array.
|
||||
/// </exception>
|
||||
void ICollection.CopyTo(Array array, int index) {
|
||||
CopyTo((TItem[])array, index);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A value indicating whether access to the WeakCollection is
|
||||
/// synchronized (thread safe).
|
||||
/// </summary>
|
||||
bool ICollection.IsSynchronized {
|
||||
get { return false; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An object that can be used to synchronize access to the WeakCollection.
|
||||
/// </summary>
|
||||
object ICollection.SyncRoot {
|
||||
get {
|
||||
if(this.syncRoot == null) {
|
||||
ICollection is2 = this.items as ICollection;
|
||||
if(is2 != null) {
|
||||
this.syncRoot = is2.SyncRoot;
|
||||
} else {
|
||||
Interlocked.CompareExchange(ref this.syncRoot, new object(), null);
|
||||
}
|
||||
}
|
||||
|
||||
return this.syncRoot;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Downcasts an object reference to a reference to the collection's item type
|
||||
/// </summary>
|
||||
/// <param name="value">Object reference that will be downcast</param>
|
||||
/// <returns>
|
||||
/// The specified object referecne as a reference to the collection's item type
|
||||
/// </returns>
|
||||
private static TItem downcastToItemType(object value) {
|
||||
TItem valueAsItemType = value as TItem;
|
||||
if(!ReferenceEquals(value, null)) {
|
||||
if(valueAsItemType == null) {
|
||||
throw new ArgumentException("Object is not of a compatible type", "value");
|
||||
}
|
||||
}
|
||||
return valueAsItemType;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} // namespace Nuclex.Support.Collections
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -1,339 +1,338 @@
|
|||
#region CPL License
|
||||
/*
|
||||
Nuclex Framework
|
||||
Copyright (C) 2002-2017 Nuclex Development Labs
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the IBM Common Public License as
|
||||
published by the IBM Corporation; either version 1.0 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
IBM Common Public License for more details.
|
||||
|
||||
You should have received a copy of the IBM Common Public
|
||||
License along with this library
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Nuclex.Support.Collections {
|
||||
|
||||
/// <summary>Collection of weakly referenced objects</summary>
|
||||
/// <remarks>
|
||||
/// This collection tries to expose the interface of a normal collection, but stores
|
||||
/// objects as weak references. When an object is accessed, it can return null.
|
||||
/// when the collection detects that one of its items was garbage collected, it
|
||||
/// will silently remove that item.
|
||||
/// </remarks>
|
||||
public partial class WeakCollection<TItem> : IList<TItem>, IList
|
||||
where TItem : class {
|
||||
|
||||
#region class UnpackingEnumerator
|
||||
|
||||
/// <summary>
|
||||
/// An enumerator that unpacks the items returned by an enumerator of the
|
||||
/// weak reference collection into the actual item type on-the-fly.
|
||||
/// </summary>
|
||||
private class UnpackingEnumerator : IEnumerator<TItem> {
|
||||
|
||||
/// <summary>Initializes a new unpacking enumerator</summary>
|
||||
/// <param name="containedTypeEnumerator">
|
||||
/// Enumerator of the weak reference collection
|
||||
/// </param>
|
||||
public UnpackingEnumerator(
|
||||
IEnumerator<WeakReference<TItem>> containedTypeEnumerator
|
||||
) {
|
||||
this.containedTypeEnumerator = containedTypeEnumerator;
|
||||
}
|
||||
|
||||
/// <summary>Immediately releases all resources used by the instance</summary>
|
||||
public void Dispose() {
|
||||
this.containedTypeEnumerator.Dispose();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The element in the collection at the current position of the enumerator.
|
||||
/// </summary>
|
||||
public TItem Current {
|
||||
get { return this.containedTypeEnumerator.Current.Target; }
|
||||
}
|
||||
|
||||
/// <summary>Gets the current element in the collection.</summary>
|
||||
/// <returns>The current element in the collection.</returns>
|
||||
/// <exception cref="System.InvalidOperationException">
|
||||
/// The enumerator is positioned before the first element of the collection
|
||||
/// or after the last element.
|
||||
/// </exception>
|
||||
public bool MoveNext() {
|
||||
return this.containedTypeEnumerator.MoveNext();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the enumerator to its initial position, which is before the first element
|
||||
/// in the collection.
|
||||
/// </summary>
|
||||
/// <exception cref="System.InvalidOperationException">
|
||||
/// The collection was modified after the enumerator was created.
|
||||
/// </exception>
|
||||
public void Reset() {
|
||||
this.containedTypeEnumerator.Reset();
|
||||
}
|
||||
|
||||
/// <summary>The current element in the collection.</summary>
|
||||
/// <exception cref="System.InvalidOperationException">
|
||||
/// The enumerator is positioned before the first element of the collection
|
||||
/// or after the last element.
|
||||
/// </exception>
|
||||
object IEnumerator.Current {
|
||||
get { return Current; }
|
||||
}
|
||||
|
||||
/// <summary>An enumerator from the wrapped collection</summary>
|
||||
private IEnumerator<WeakReference<TItem>> containedTypeEnumerator;
|
||||
|
||||
}
|
||||
|
||||
#endregion // class UnpackingEnumerator
|
||||
|
||||
/// <summary>Initializes a new weak reference collection</summary>
|
||||
/// <param name="items">
|
||||
/// Internal list of weak references that are unpacking when accessed through
|
||||
/// the WeakCollection's interface.
|
||||
/// </param>
|
||||
public WeakCollection(IList<WeakReference<TItem>> items) :
|
||||
this(items, EqualityComparer<TItem>.Default) { }
|
||||
|
||||
/// <summary>Initializes a new weak reference collection</summary>
|
||||
/// <param name="items">
|
||||
/// Internal list of weak references that are unpacking when accessed through
|
||||
/// the WeakCollection's interface.
|
||||
/// </param>
|
||||
/// <param name="comparer">
|
||||
/// Comparer used to identify and compare items to each other
|
||||
/// </param>
|
||||
public WeakCollection(
|
||||
IList<WeakReference<TItem>> items, IEqualityComparer<TItem> comparer
|
||||
) {
|
||||
this.items = items;
|
||||
this.comparer = comparer;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether an element is in the WeakCollection
|
||||
/// </summary>
|
||||
/// <param name="item">
|
||||
/// The object to locate in the WeakCollection. The value can be null.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// True if value is found in the WeakCollection; otherwise, false.
|
||||
/// </returns>
|
||||
/// <remarks>
|
||||
/// The default implementation of this method is very unoptimized and will
|
||||
/// enumerate all the items in the collection, transforming one after another
|
||||
/// to check whether the transformed item matches the item the user was
|
||||
/// looking for. It is recommended to provide a custom implementation of
|
||||
/// this method, if possible.
|
||||
/// </remarks>
|
||||
public virtual bool Contains(TItem item) {
|
||||
return (IndexOf(item) != -1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies the entire WeakCollection to a compatible one-dimensional
|
||||
/// System.Array, starting at the specified index of the target array.
|
||||
/// </summary>
|
||||
/// <param name="array">
|
||||
/// The one-dimensional System.Array that is the destination of the elements copied
|
||||
/// from the WeakCollection. The System.Array must have zero-based indexing.
|
||||
/// </param>
|
||||
/// <param name="index">
|
||||
/// The zero-based index in array at which copying begins.
|
||||
/// </param>
|
||||
/// <exception cref="System.ArgumentException">
|
||||
/// Index is equal to or greater than the length of array or the number of elements
|
||||
/// in the source WeakCollection is greater than the available space from index to
|
||||
/// the end of the destination array.
|
||||
/// </exception>
|
||||
/// <exception cref="System.ArgumentOutOfRangeException">
|
||||
/// Index is less than zero.
|
||||
/// </exception>
|
||||
/// <exception cref="System.ArgumentNullException">
|
||||
/// Array is null.
|
||||
/// </exception>
|
||||
public void CopyTo(TItem[] array, int index) {
|
||||
if(this.items.Count > (array.Length - index)) {
|
||||
throw new ArgumentException(
|
||||
"Array too small to fit the collection items starting at the specified index"
|
||||
);
|
||||
}
|
||||
|
||||
for(int itemIndex = 0; itemIndex < this.items.Count; ++itemIndex) {
|
||||
array[itemIndex + index] = this.items[itemIndex].Target;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Removes all items from the WeakCollection</summary>
|
||||
public void Clear() {
|
||||
this.items.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns an enumerator that iterates through the WeakCollection.
|
||||
/// </summary>
|
||||
/// <returns>An enumerator or the WeakCollection.</returns>
|
||||
public IEnumerator<TItem> GetEnumerator() {
|
||||
return new UnpackingEnumerator(this.items.GetEnumerator());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Searches for the specified object and returns the zero-based index of the
|
||||
/// first occurrence within the entire WeakCollection.
|
||||
/// </summary>
|
||||
/// <param name="item">
|
||||
/// The object to locate in the WeakCollection. The value can
|
||||
/// be null for reference types.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// The zero-based index of the first occurrence of item within the entire
|
||||
/// WeakCollection, if found; otherwise, -1.
|
||||
/// </returns>
|
||||
/// <remarks>
|
||||
/// The default implementation of this method is very unoptimized and will
|
||||
/// enumerate all the items in the collection, transforming one after another
|
||||
/// to check whether the transformed item matches the item the user was
|
||||
/// looking for. It is recommended to provide a custom implementation of
|
||||
/// this method, if possible.
|
||||
/// </remarks>
|
||||
public int IndexOf(TItem item) {
|
||||
for(int index = 0; index < this.items.Count; ++index) {
|
||||
TItem itemAtIndex = this.items[index].Target;
|
||||
if((itemAtIndex == null) || (item == null)) {
|
||||
if(ReferenceEquals(item, itemAtIndex)) {
|
||||
return index;
|
||||
}
|
||||
} else {
|
||||
if(this.comparer.Equals(itemAtIndex, item)) {
|
||||
return index;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The number of elements contained in the WeakCollection instance
|
||||
/// </summary>
|
||||
public int Count {
|
||||
get { return this.items.Count; }
|
||||
}
|
||||
|
||||
/// <summary>Gets the element at the specified index.</summary>
|
||||
/// <param name="index">The zero-based index of the element to get.</param>
|
||||
/// <returns>The element at the specified index.</returns>
|
||||
/// <exception cref="System.ArgumentOutOfRangeException">
|
||||
/// Index is less than zero or index is equal to or greater than
|
||||
/// WeakCollection.Count.
|
||||
/// </exception>
|
||||
public TItem this[int index] {
|
||||
get { return this.items[index].Target; }
|
||||
set { this.items[index] = new WeakReference<TItem>(value); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes the first occurrence of a specific object from the WeakCollection.
|
||||
/// </summary>
|
||||
/// <param name="item">The object to remove from the WeakCollection</param>
|
||||
/// <returns>
|
||||
/// True if item was successfully removed from the WeakCollection; otherwise, false.
|
||||
/// </returns>
|
||||
public bool Remove(TItem item) {
|
||||
for(int index = 0; index < this.items.Count; ++index) {
|
||||
TItem itemAtIndex = this.items[index].Target;
|
||||
if((itemAtIndex == null) || (item == null)) {
|
||||
if(ReferenceEquals(item, itemAtIndex)) {
|
||||
this.items.RemoveAt(index);
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
if(this.comparer.Equals(item, itemAtIndex)) {
|
||||
this.items.RemoveAt(index);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>Adds an item to the WeakCollection.</summary>
|
||||
/// <param name="item">The object to add to the WeakCollection</param>
|
||||
public void Add(TItem item) {
|
||||
this.items.Add(new WeakReference<TItem>(item));
|
||||
}
|
||||
|
||||
/// <summary>Inserts an item to the WeakCollection at the specified index.</summary>
|
||||
/// <param name="index">
|
||||
/// The zero-based index at which item should be inserted.
|
||||
/// </param>
|
||||
/// <param name="item">The object to insert into the WeakCollection</param>
|
||||
/// <exception cref="System.ArgumentOutOfRangeException">
|
||||
/// index is not a valid index in the WeakCollection.
|
||||
/// </exception>
|
||||
public void Insert(int index, TItem item) {
|
||||
this.items.Insert(index, new WeakReference<TItem>(item));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes the WeakCollection item at the specified index.
|
||||
/// </summary>
|
||||
/// <param name="index">The zero-based index of the item to remove.</param>
|
||||
/// <exception cref="System.ArgumentOutOfRangeException">
|
||||
/// Index is not a valid index in the WeakCollection.
|
||||
/// </exception>
|
||||
public void RemoveAt(int index) {
|
||||
this.items.RemoveAt(index);
|
||||
}
|
||||
|
||||
/// <summary>Whether the List is write-protected</summary>
|
||||
public bool IsReadOnly {
|
||||
get { return this.items.IsReadOnly; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes the items that have been garbage collected from the collection
|
||||
/// </summary>
|
||||
public void RemoveDeadItems() {
|
||||
int newCount = 0;
|
||||
|
||||
// Eliminate all items that have been garbage collected by shifting
|
||||
for(int index = 0; index < this.items.Count; ++index) {
|
||||
if(this.items[index].IsAlive) {
|
||||
this.items[newCount] = this.items[index];
|
||||
++newCount;
|
||||
}
|
||||
}
|
||||
|
||||
// If any garbage collected items were found, resize the collection so
|
||||
// the space that became empty in the previous shifting process will be freed
|
||||
while(this.items.Count > newCount) {
|
||||
this.items.RemoveAt(this.items.Count - 1);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Weak references to the items contained in the collection</summary>
|
||||
private IList<WeakReference<TItem>> items;
|
||||
/// <summary>Used to identify and compare items in the collection</summary>
|
||||
private IEqualityComparer<TItem> comparer;
|
||||
/// <summary>Synchronization root for threaded accesses to this collection</summary>
|
||||
private object syncRoot;
|
||||
|
||||
}
|
||||
|
||||
} // namespace Nuclex.Support.Collections
|
||||
#region Apache License 2.0
|
||||
/*
|
||||
Nuclex .NET Framework
|
||||
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
#endregion // Apache License 2.0
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Nuclex.Support.Collections {
|
||||
|
||||
/// <summary>Collection of weakly referenced objects</summary>
|
||||
/// <remarks>
|
||||
/// This collection tries to expose the interface of a normal collection, but stores
|
||||
/// objects as weak references. When an object is accessed, it can return null.
|
||||
/// when the collection detects that one of its items was garbage collected, it
|
||||
/// will silently remove that item.
|
||||
/// </remarks>
|
||||
public partial class WeakCollection<TItem> : IList<TItem>, IList
|
||||
where TItem : class {
|
||||
|
||||
#region class UnpackingEnumerator
|
||||
|
||||
/// <summary>
|
||||
/// An enumerator that unpacks the items returned by an enumerator of the
|
||||
/// weak reference collection into the actual item type on-the-fly.
|
||||
/// </summary>
|
||||
private class UnpackingEnumerator : IEnumerator<TItem> {
|
||||
|
||||
/// <summary>Initializes a new unpacking enumerator</summary>
|
||||
/// <param name="containedTypeEnumerator">
|
||||
/// Enumerator of the weak reference collection
|
||||
/// </param>
|
||||
public UnpackingEnumerator(
|
||||
IEnumerator<WeakReference<TItem>> containedTypeEnumerator
|
||||
) {
|
||||
this.containedTypeEnumerator = containedTypeEnumerator;
|
||||
}
|
||||
|
||||
/// <summary>Immediately releases all resources used by the instance</summary>
|
||||
public void Dispose() {
|
||||
this.containedTypeEnumerator.Dispose();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The element in the collection at the current position of the enumerator.
|
||||
/// </summary>
|
||||
public TItem Current {
|
||||
get { return this.containedTypeEnumerator.Current.Target; }
|
||||
}
|
||||
|
||||
/// <summary>Gets the current element in the collection.</summary>
|
||||
/// <returns>The current element in the collection.</returns>
|
||||
/// <exception cref="System.InvalidOperationException">
|
||||
/// The enumerator is positioned before the first element of the collection
|
||||
/// or after the last element.
|
||||
/// </exception>
|
||||
public bool MoveNext() {
|
||||
return this.containedTypeEnumerator.MoveNext();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the enumerator to its initial position, which is before the first element
|
||||
/// in the collection.
|
||||
/// </summary>
|
||||
/// <exception cref="System.InvalidOperationException">
|
||||
/// The collection was modified after the enumerator was created.
|
||||
/// </exception>
|
||||
public void Reset() {
|
||||
this.containedTypeEnumerator.Reset();
|
||||
}
|
||||
|
||||
/// <summary>The current element in the collection.</summary>
|
||||
/// <exception cref="System.InvalidOperationException">
|
||||
/// The enumerator is positioned before the first element of the collection
|
||||
/// or after the last element.
|
||||
/// </exception>
|
||||
object IEnumerator.Current {
|
||||
get { return Current; }
|
||||
}
|
||||
|
||||
/// <summary>An enumerator from the wrapped collection</summary>
|
||||
private IEnumerator<WeakReference<TItem>> containedTypeEnumerator;
|
||||
|
||||
}
|
||||
|
||||
#endregion // class UnpackingEnumerator
|
||||
|
||||
/// <summary>Initializes a new weak reference collection</summary>
|
||||
/// <param name="items">
|
||||
/// Internal list of weak references that are unpacking when accessed through
|
||||
/// the WeakCollection's interface.
|
||||
/// </param>
|
||||
public WeakCollection(IList<WeakReference<TItem>> items) :
|
||||
this(items, EqualityComparer<TItem>.Default) { }
|
||||
|
||||
/// <summary>Initializes a new weak reference collection</summary>
|
||||
/// <param name="items">
|
||||
/// Internal list of weak references that are unpacking when accessed through
|
||||
/// the WeakCollection's interface.
|
||||
/// </param>
|
||||
/// <param name="comparer">
|
||||
/// Comparer used to identify and compare items to each other
|
||||
/// </param>
|
||||
public WeakCollection(
|
||||
IList<WeakReference<TItem>> items, IEqualityComparer<TItem> comparer
|
||||
) {
|
||||
this.items = items;
|
||||
this.comparer = comparer;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether an element is in the WeakCollection
|
||||
/// </summary>
|
||||
/// <param name="item">
|
||||
/// The object to locate in the WeakCollection. The value can be null.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// True if value is found in the WeakCollection; otherwise, false.
|
||||
/// </returns>
|
||||
/// <remarks>
|
||||
/// The default implementation of this method is very unoptimized and will
|
||||
/// enumerate all the items in the collection, transforming one after another
|
||||
/// to check whether the transformed item matches the item the user was
|
||||
/// looking for. It is recommended to provide a custom implementation of
|
||||
/// this method, if possible.
|
||||
/// </remarks>
|
||||
public virtual bool Contains(TItem item) {
|
||||
return (IndexOf(item) != -1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies the entire WeakCollection to a compatible one-dimensional
|
||||
/// System.Array, starting at the specified index of the target array.
|
||||
/// </summary>
|
||||
/// <param name="array">
|
||||
/// The one-dimensional System.Array that is the destination of the elements copied
|
||||
/// from the WeakCollection. The System.Array must have zero-based indexing.
|
||||
/// </param>
|
||||
/// <param name="index">
|
||||
/// The zero-based index in array at which copying begins.
|
||||
/// </param>
|
||||
/// <exception cref="System.ArgumentException">
|
||||
/// Index is equal to or greater than the length of array or the number of elements
|
||||
/// in the source WeakCollection is greater than the available space from index to
|
||||
/// the end of the destination array.
|
||||
/// </exception>
|
||||
/// <exception cref="System.ArgumentOutOfRangeException">
|
||||
/// Index is less than zero.
|
||||
/// </exception>
|
||||
/// <exception cref="System.ArgumentNullException">
|
||||
/// Array is null.
|
||||
/// </exception>
|
||||
public void CopyTo(TItem[] array, int index) {
|
||||
if(this.items.Count > (array.Length - index)) {
|
||||
throw new ArgumentException(
|
||||
"Array too small to fit the collection items starting at the specified index"
|
||||
);
|
||||
}
|
||||
|
||||
for(int itemIndex = 0; itemIndex < this.items.Count; ++itemIndex) {
|
||||
array[itemIndex + index] = this.items[itemIndex].Target;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Removes all items from the WeakCollection</summary>
|
||||
public void Clear() {
|
||||
this.items.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns an enumerator that iterates through the WeakCollection.
|
||||
/// </summary>
|
||||
/// <returns>An enumerator or the WeakCollection.</returns>
|
||||
public IEnumerator<TItem> GetEnumerator() {
|
||||
return new UnpackingEnumerator(this.items.GetEnumerator());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Searches for the specified object and returns the zero-based index of the
|
||||
/// first occurrence within the entire WeakCollection.
|
||||
/// </summary>
|
||||
/// <param name="item">
|
||||
/// The object to locate in the WeakCollection. The value can
|
||||
/// be null for reference types.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// The zero-based index of the first occurrence of item within the entire
|
||||
/// WeakCollection, if found; otherwise, -1.
|
||||
/// </returns>
|
||||
/// <remarks>
|
||||
/// The default implementation of this method is very unoptimized and will
|
||||
/// enumerate all the items in the collection, transforming one after another
|
||||
/// to check whether the transformed item matches the item the user was
|
||||
/// looking for. It is recommended to provide a custom implementation of
|
||||
/// this method, if possible.
|
||||
/// </remarks>
|
||||
public int IndexOf(TItem item) {
|
||||
for(int index = 0; index < this.items.Count; ++index) {
|
||||
TItem itemAtIndex = this.items[index].Target;
|
||||
if((itemAtIndex == null) || (item == null)) {
|
||||
if(ReferenceEquals(item, itemAtIndex)) {
|
||||
return index;
|
||||
}
|
||||
} else {
|
||||
if(this.comparer.Equals(itemAtIndex, item)) {
|
||||
return index;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The number of elements contained in the WeakCollection instance
|
||||
/// </summary>
|
||||
public int Count {
|
||||
get { return this.items.Count; }
|
||||
}
|
||||
|
||||
/// <summary>Gets the element at the specified index.</summary>
|
||||
/// <param name="index">The zero-based index of the element to get.</param>
|
||||
/// <returns>The element at the specified index.</returns>
|
||||
/// <exception cref="System.ArgumentOutOfRangeException">
|
||||
/// Index is less than zero or index is equal to or greater than
|
||||
/// WeakCollection.Count.
|
||||
/// </exception>
|
||||
public TItem this[int index] {
|
||||
get { return this.items[index].Target; }
|
||||
set { this.items[index] = new WeakReference<TItem>(value); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes the first occurrence of a specific object from the WeakCollection.
|
||||
/// </summary>
|
||||
/// <param name="item">The object to remove from the WeakCollection</param>
|
||||
/// <returns>
|
||||
/// True if item was successfully removed from the WeakCollection; otherwise, false.
|
||||
/// </returns>
|
||||
public bool Remove(TItem item) {
|
||||
for(int index = 0; index < this.items.Count; ++index) {
|
||||
TItem itemAtIndex = this.items[index].Target;
|
||||
if((itemAtIndex == null) || (item == null)) {
|
||||
if(ReferenceEquals(item, itemAtIndex)) {
|
||||
this.items.RemoveAt(index);
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
if(this.comparer.Equals(item, itemAtIndex)) {
|
||||
this.items.RemoveAt(index);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>Adds an item to the WeakCollection.</summary>
|
||||
/// <param name="item">The object to add to the WeakCollection</param>
|
||||
public void Add(TItem item) {
|
||||
this.items.Add(new WeakReference<TItem>(item));
|
||||
}
|
||||
|
||||
/// <summary>Inserts an item to the WeakCollection at the specified index.</summary>
|
||||
/// <param name="index">
|
||||
/// The zero-based index at which item should be inserted.
|
||||
/// </param>
|
||||
/// <param name="item">The object to insert into the WeakCollection</param>
|
||||
/// <exception cref="System.ArgumentOutOfRangeException">
|
||||
/// index is not a valid index in the WeakCollection.
|
||||
/// </exception>
|
||||
public void Insert(int index, TItem item) {
|
||||
this.items.Insert(index, new WeakReference<TItem>(item));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes the WeakCollection item at the specified index.
|
||||
/// </summary>
|
||||
/// <param name="index">The zero-based index of the item to remove.</param>
|
||||
/// <exception cref="System.ArgumentOutOfRangeException">
|
||||
/// Index is not a valid index in the WeakCollection.
|
||||
/// </exception>
|
||||
public void RemoveAt(int index) {
|
||||
this.items.RemoveAt(index);
|
||||
}
|
||||
|
||||
/// <summary>Whether the List is write-protected</summary>
|
||||
public bool IsReadOnly {
|
||||
get { return this.items.IsReadOnly; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes the items that have been garbage collected from the collection
|
||||
/// </summary>
|
||||
public void RemoveDeadItems() {
|
||||
int newCount = 0;
|
||||
|
||||
// Eliminate all items that have been garbage collected by shifting
|
||||
for(int index = 0; index < this.items.Count; ++index) {
|
||||
if(this.items[index].IsAlive) {
|
||||
this.items[newCount] = this.items[index];
|
||||
++newCount;
|
||||
}
|
||||
}
|
||||
|
||||
// If any garbage collected items were found, resize the collection so
|
||||
// the space that became empty in the previous shifting process will be freed
|
||||
while(this.items.Count > newCount) {
|
||||
this.items.RemoveAt(this.items.Count - 1);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Weak references to the items contained in the collection</summary>
|
||||
private IList<WeakReference<TItem>> items;
|
||||
/// <summary>Used to identify and compare items in the collection</summary>
|
||||
private IEqualityComparer<TItem> comparer;
|
||||
/// <summary>Synchronization root for threaded accesses to this collection</summary>
|
||||
private object syncRoot;
|
||||
|
||||
}
|
||||
|
||||
} // namespace Nuclex.Support.Collections
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue