Added INotifyCollectionChanged interface to ObservableCollection and ObservableDictionary; added a new dictionary type, the MultiDictionary which is equivalent to the C++ multimap<> class

git-svn-id: file:///srv/devel/repo-conversion/nusu@215 d2e56fa2-650e-0410-a79f-9358c0239efd
This commit is contained in:
Markus Ewald 2011-07-04 22:21:57 +00:00
parent aa5e4d12cc
commit a08939e914
10 changed files with 1350 additions and 70 deletions

View File

@ -76,12 +76,23 @@
<Compile Include="Source\Collections\Deque.Test.cs"> <Compile Include="Source\Collections\Deque.Test.cs">
<DependentUpon>Deque.cs</DependentUpon> <DependentUpon>Deque.cs</DependentUpon>
</Compile> </Compile>
<Compile Include="Source\Collections\IMultiDictionary.cs" />
<Compile Include="Source\Collections\IObservableCollection.cs" /> <Compile Include="Source\Collections\IObservableCollection.cs" />
<Compile Include="Source\Collections\IRecyclable.cs" /> <Compile Include="Source\Collections\IRecyclable.cs" />
<Compile Include="Source\Collections\ItemEventArgs.cs" /> <Compile Include="Source\Collections\ItemEventArgs.cs" />
<Compile Include="Source\Collections\ItemEventArgs.Test.cs"> <Compile Include="Source\Collections\ItemEventArgs.Test.cs">
<DependentUpon>ItemEventArgs.cs</DependentUpon> <DependentUpon>ItemEventArgs.cs</DependentUpon>
</Compile> </Compile>
<Compile Include="Source\Collections\MultiDictionary.cs" />
<Compile Include="Source\Collections\MultiDictionary.ValueCollection.cs">
<DependentUpon>MultiDictionary.cs</DependentUpon>
</Compile>
<Compile Include="Source\Collections\MultiDictionary.Interfaces.cs">
<DependentUpon>MultiDictionary.cs</DependentUpon>
</Compile>
<Compile Include="Source\Collections\MultiDictionary.Test.cs">
<DependentUpon>MultiDictionary.cs</DependentUpon>
</Compile>
<Compile Include="Source\Collections\ObservableCollection.cs" /> <Compile Include="Source\Collections\ObservableCollection.cs" />
<Compile Include="Source\Collections\ObservableCollection.Test.cs"> <Compile Include="Source\Collections\ObservableCollection.Test.cs">
<DependentUpon>ObservableCollection.cs</DependentUpon> <DependentUpon>ObservableCollection.cs</DependentUpon>
@ -377,6 +388,9 @@
<Link>Foundation.snk</Link> <Link>Foundation.snk</Link>
</None> </None>
</ItemGroup> </ItemGroup>
<ItemGroup>
<Folder Include="Source\Cloning\" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it. <!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets. Other similar extension points exist, see Microsoft.Common.targets.

View File

@ -22,7 +22,7 @@
<DebugType>full</DebugType> <DebugType>full</DebugType>
<Optimize>false</Optimize> <Optimize>false</Optimize>
<OutputPath>bin\xna-4.0-phone7\Debug\</OutputPath> <OutputPath>bin\xna-4.0-phone7\Debug\</OutputPath>
<DefineConstants>TRACE;DEBUG;WINDOWS_PHONE;NO_CLONING;NO_SERIALIZATION;NO_XMLSCHEMA;NO_SYSTEMEVENTS;NO_EXITCONTEXT</DefineConstants> <DefineConstants>TRACE;DEBUG;WINDOWS_PHONE;NO_CLONING;NO_SERIALIZATION;NO_XMLSCHEMA;NO_SYSTEMEVENTS;NO_EXITCONTEXT;NO_SPECIALIZED_COLLECTIONS</DefineConstants>
<ErrorReport>prompt</ErrorReport> <ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel> <WarningLevel>4</WarningLevel>
<NoStdLib>true</NoStdLib> <NoStdLib>true</NoStdLib>
@ -107,12 +107,23 @@
<Compile Include="Source\Collections\Deque.Test.cs"> <Compile Include="Source\Collections\Deque.Test.cs">
<DependentUpon>Deque.cs</DependentUpon> <DependentUpon>Deque.cs</DependentUpon>
</Compile> </Compile>
<Compile Include="Source\Collections\IMultiDictionary.cs" />
<Compile Include="Source\Collections\IObservableCollection.cs" /> <Compile Include="Source\Collections\IObservableCollection.cs" />
<Compile Include="Source\Collections\IRecyclable.cs" /> <Compile Include="Source\Collections\IRecyclable.cs" />
<Compile Include="Source\Collections\ItemEventArgs.cs" /> <Compile Include="Source\Collections\ItemEventArgs.cs" />
<Compile Include="Source\Collections\ItemEventArgs.Test.cs"> <Compile Include="Source\Collections\ItemEventArgs.Test.cs">
<DependentUpon>ItemEventArgs.cs</DependentUpon> <DependentUpon>ItemEventArgs.cs</DependentUpon>
</Compile> </Compile>
<Compile Include="Source\Collections\MultiDictionary.cs" />
<Compile Include="Source\Collections\MultiDictionary.ValueCollection.cs">
<DependentUpon>MultiDictionary.cs</DependentUpon>
</Compile>
<Compile Include="Source\Collections\MultiDictionary.Interfaces.cs">
<DependentUpon>MultiDictionary.cs</DependentUpon>
</Compile>
<Compile Include="Source\Collections\MultiDictionary.Test.cs">
<DependentUpon>MultiDictionary.cs</DependentUpon>
</Compile>
<Compile Include="Source\Collections\ObservableCollection.cs" /> <Compile Include="Source\Collections\ObservableCollection.cs" />
<Compile Include="Source\Collections\ObservableCollection.Test.cs"> <Compile Include="Source\Collections\ObservableCollection.Test.cs">
<DependentUpon>ObservableCollection.cs</DependentUpon> <DependentUpon>ObservableCollection.cs</DependentUpon>

View File

@ -22,7 +22,7 @@
<DebugType>full</DebugType> <DebugType>full</DebugType>
<Optimize>false</Optimize> <Optimize>false</Optimize>
<OutputPath>bin\xna-4.0-xbox360\Debug\</OutputPath> <OutputPath>bin\xna-4.0-xbox360\Debug\</OutputPath>
<DefineConstants>TRACE;DEBUG;XBOX;XBOX360;NO_CLONING;NO_SERIALIZATION;NO_XMLSCHEMA;NO_SYSTEMEVENTS;NO_EXITCONTEXT</DefineConstants> <DefineConstants>TRACE;DEBUG;XBOX;XBOX360;NO_CLONING;NO_SERIALIZATION;NO_XMLSCHEMA;NO_SYSTEMEVENTS;NO_EXITCONTEXT;NO_SPECIALIZED_COLLECTIONS</DefineConstants>
<ErrorReport>prompt</ErrorReport> <ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel> <WarningLevel>4</WarningLevel>
<NoStdLib>true</NoStdLib> <NoStdLib>true</NoStdLib>
@ -118,12 +118,23 @@
<Compile Include="Source\Collections\Deque.Test.cs"> <Compile Include="Source\Collections\Deque.Test.cs">
<DependentUpon>Deque.cs</DependentUpon> <DependentUpon>Deque.cs</DependentUpon>
</Compile> </Compile>
<Compile Include="Source\Collections\IMultiDictionary.cs" />
<Compile Include="Source\Collections\IObservableCollection.cs" /> <Compile Include="Source\Collections\IObservableCollection.cs" />
<Compile Include="Source\Collections\IRecyclable.cs" /> <Compile Include="Source\Collections\IRecyclable.cs" />
<Compile Include="Source\Collections\ItemEventArgs.cs" /> <Compile Include="Source\Collections\ItemEventArgs.cs" />
<Compile Include="Source\Collections\ItemEventArgs.Test.cs"> <Compile Include="Source\Collections\ItemEventArgs.Test.cs">
<DependentUpon>ItemEventArgs.cs</DependentUpon> <DependentUpon>ItemEventArgs.cs</DependentUpon>
</Compile> </Compile>
<Compile Include="Source\Collections\MultiDictionary.cs" />
<Compile Include="Source\Collections\MultiDictionary.ValueCollection.cs">
<DependentUpon>MultiDictionary.cs</DependentUpon>
</Compile>
<Compile Include="Source\Collections\MultiDictionary.Interfaces.cs">
<DependentUpon>MultiDictionary.cs</DependentUpon>
</Compile>
<Compile Include="Source\Collections\MultiDictionary.Test.cs">
<DependentUpon>MultiDictionary.cs</DependentUpon>
</Compile>
<Compile Include="Source\Collections\ObservableCollection.cs" /> <Compile Include="Source\Collections\ObservableCollection.cs" />
<Compile Include="Source\Collections\ObservableCollection.Test.cs"> <Compile Include="Source\Collections\ObservableCollection.Test.cs">
<DependentUpon>ObservableCollection.cs</DependentUpon> <DependentUpon>ObservableCollection.cs</DependentUpon>

View File

@ -0,0 +1,68 @@
#region CPL License
/*
Nuclex Framework
Copyright (C) 2002-2010 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

View File

@ -0,0 +1,248 @@
#region CPL License
/*
Nuclex Framework
Copyright (C) 2002-2010 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>Returns a new entry enumerator for the dictionary</summary>
/// <returns>The new entry enumerator</returns>
IDictionaryEnumerator IDictionary.GetEnumerator() {
return new Enumerator(this);
}
/// <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 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);
}
OnRemoved(itemToRemove);
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();
}
#endregion // ICollection<KeyValuePair<TKey, ICollection<TValue>>> implementation
}
} // namespace Nuclex.Support.Collections

View File

@ -0,0 +1,146 @@
#region CPL License
/*
Nuclex Framework
Copyright (C) 2002-2010 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;
using NMock;
namespace Nuclex.Support.Collections {
/// <summary>Unit tests for the multi dictionary</summary>
[TestFixture]
public 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 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>
/// 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
);
}
}
} // namespace Nuclex.Support.Collections
#endif // UNITTEST

View File

@ -0,0 +1,269 @@
#region CPL License
/*
Nuclex Framework
Copyright (C) 2002-2010 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.IsSynchronized; }
}
#endregion // ICollection implementation
/// <summary>Dictionary whose values the collection represents</summary>
private MultiDictionary<TKey, TValue> dictionary;
/// <summary>The dictionary under its ICollection interface</summary>
private ICollection dictionaryAsICollection;
}
}
} // namespace Nuclex.Support.Collections

View File

@ -0,0 +1,436 @@
#region CPL License
/*
Nuclex Framework
Copyright (C) 2002-2010 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 :
IEnumerator<KeyValuePair<TKey, TValue>>, IDictionaryEnumerator {
/// <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>
/// 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>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>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 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 {
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);
OnAdded(new KeyValuePair<TKey, TValue>(key, 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);
OnAdded(new KeyValuePair<TKey, TValue>(key, 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);
}
OnRemoved(new KeyValuePair<TKey, TValue>(key, value));
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);
foreach(TValue value in values) {
OnRemoved(new KeyValuePair<TKey, TValue>(key, value));
}
return values.Count;
} else {
return 0;
}
}
/// <summary>Removes all items from the Dictionary</summary>
public void Clear() {
OnClearing();
this.typedDictionary.Clear();
this.count = 0;
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) { }
/// <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) { }
/// <summary>Fires the 'Clearing' event</summary>
protected virtual void OnClearing() { }
/// <summary>Fires the 'Cleared' event</summary>
protected virtual void OnCleared() { }
/// <summary>The wrapped Dictionary under its type-safe interface</summary>
private IDictionary<TKey, ICollection<TValue>> typedDictionary;
/// <summary>The wrapped Dictionary under its object interface</summary>
private IDictionary objectDictionary;
/// <summary>The number of items currently in the multi dictionary</summary>
private int count;
/// <summary>Provides the values stores in the dictionary in sequence</summary>
private ValueCollection valueCollection;
}
} // namespace Nuclex.Support.Collections

View File

@ -21,17 +21,25 @@ License along with this library
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
#if !NO_SPECIALIZED_COLLECTIONS
using System.Collections.Specialized;
#endif
namespace Nuclex.Support.Collections { namespace Nuclex.Support.Collections {
/// <summary>Collection which fires events when items are added or removed</summary> /// <summary>Collection which fires events when items are added or removed</summary>
public class ObservableCollection<ItemType> : /// <typeparam name="TItem">Type of items the collection manages</typeparam>
Collection<ItemType>, IObservableCollection<ItemType> { public class ObservableCollection<TItem> :
Collection<TItem>,
#if !NO_SPECIALIZED_COLLECTIONS
INotifyCollectionChanged,
#endif
IObservableCollection<TItem> {
/// <summary>Raised when an item has been added to the collection</summary> /// <summary>Raised when an item has been added to the collection</summary>
public event EventHandler<ItemEventArgs<ItemType>> ItemAdded; public event EventHandler<ItemEventArgs<TItem>> ItemAdded;
/// <summary>Raised when an item is removed from the collection</summary> /// <summary>Raised when an item is removed from the collection</summary>
public event EventHandler<ItemEventArgs<ItemType>> ItemRemoved; public event EventHandler<ItemEventArgs<TItem>> ItemRemoved;
/// <summary>Raised when the collection is about to be cleared</summary> /// <summary>Raised when the collection is about to be cleared</summary>
/// <remarks> /// <remarks>
/// This could be covered by calling ItemRemoved for each item currently /// This could be covered by calling ItemRemoved for each item currently
@ -42,6 +50,11 @@ namespace Nuclex.Support.Collections {
/// <summary>Raised when the collection has been cleared</summary> /// <summary>Raised when the collection has been cleared</summary>
public event EventHandler Cleared; public event EventHandler Cleared;
#if !NO_SPECIALIZED_COLLECTIONS
/// <summary>Called when the collection has changed</summary>
public event NotifyCollectionChangedEventHandler CollectionChanged;
#endif
/// <summary> /// <summary>
/// Initializes a new instance of the ObservableCollection class that is empty. /// Initializes a new instance of the ObservableCollection class that is empty.
/// </summary> /// </summary>
@ -52,18 +65,35 @@ namespace Nuclex.Support.Collections {
/// for the specified list. /// for the specified list.
/// </summary> /// </summary>
/// <param name="list">The list that is wrapped by the new collection.</param> /// <param name="list">The list that is wrapped by the new collection.</param>
/// <exception cref="System.ArgumentNullException"> /// <exception cref="System.ArgumentNullException">List is null</exception>
/// List is null. public ObservableCollection(IList<TItem> list) : base(list) { }
/// </exception>
public ObservableCollection(IList<ItemType> list) : base(list) { }
/// <summary>Removes all elements from the Collection</summary> /// <summary>Removes all elements from the Collection</summary>
protected override void ClearItems() { protected override void ClearItems() {
OnClearing(); OnClearing();
base.ClearItems(); base.ClearItems();
OnCleared(); OnCleared();
#if !NO_SPECIALIZED_COLLECTIONS
OnCollectionChanged(NotifyCollectionChangedAction.Reset, default(TItem), -1);
#endif
} }
#if !NO_SPECIALIZED_COLLECTIONS
/// <summary>Fires the CollectionChanged event</summary>
/// <param name="action">Type of change that has occured</param>
/// <param name="item">The item that has been added, removed or replaced</param>
/// <param name="index">Index of the changed item</param>
protected virtual void OnCollectionChanged(
NotifyCollectionChangedAction action, TItem item, int index
) {
if (CollectionChanged != null) {
CollectionChanged(
this, new NotifyCollectionChangedEventArgs(action, item, index)
);
}
}
#endif
/// <summary> /// <summary>
/// Inserts an element into the ObservableCollection at the specified index /// Inserts an element into the ObservableCollection at the specified index
/// </summary> /// </summary>
@ -71,9 +101,12 @@ namespace Nuclex.Support.Collections {
/// The object to insert. The value can be null for reference types. /// The object to insert. The value can be null for reference types.
/// </param> /// </param>
/// <param name="item">The zero-based index at which item should be inserted</param> /// <param name="item">The zero-based index at which item should be inserted</param>
protected override void InsertItem(int index, ItemType item) { protected override void InsertItem(int index, TItem item) {
base.InsertItem(index, item); base.InsertItem(index, item);
OnAdded(item); OnAdded(item);
#if !NO_SPECIALIZED_COLLECTIONS
OnCollectionChanged(NotifyCollectionChangedAction.Add, item, index);
#endif
} }
/// <summary> /// <summary>
@ -81,9 +114,12 @@ namespace Nuclex.Support.Collections {
/// </summary> /// </summary>
/// <param name="index">The zero-based index of the element to remove</param> /// <param name="index">The zero-based index of the element to remove</param>
protected override void RemoveItem(int index) { protected override void RemoveItem(int index) {
ItemType item = base[index]; TItem item = base[index];
base.RemoveItem(index); base.RemoveItem(index);
OnRemoved(item); OnRemoved(item);
#if !NO_SPECIALIZED_COLLECTIONS
OnCollectionChanged(NotifyCollectionChangedAction.Remove, item, index);
#endif
} }
/// <summary>Replaces the element at the specified index</summary> /// <summary>Replaces the element at the specified index</summary>
@ -92,39 +128,43 @@ namespace Nuclex.Support.Collections {
/// for reference types /// for reference types
/// </param> /// </param>
/// <param name="item">The zero-based index of the element to replace</param> /// <param name="item">The zero-based index of the element to replace</param>
protected override void SetItem(int index, ItemType item) { protected override void SetItem(int index, TItem item) {
ItemType oldItem = base[index]; TItem oldItem = base[index];
base.SetItem(index, item); base.SetItem(index, item);
OnRemoved(oldItem); OnRemoved(oldItem);
OnAdded(item); OnAdded(item);
#if !NO_SPECIALIZED_COLLECTIONS
OnCollectionChanged(NotifyCollectionChangedAction.Replace, item, index);
#endif
} }
/// <summary>Fires the 'ItemAdded' event</summary> /// <summary>Fires the 'ItemAdded' event</summary>
/// <param name="item">Item that has been added to the collection</param> /// <param name="item">Item that has been added to the collection</param>
protected virtual void OnAdded(ItemType item) { protected virtual void OnAdded(TItem item) {
if(ItemAdded != null) if (ItemAdded != null)
ItemAdded(this, new ItemEventArgs<ItemType>(item)); ItemAdded(this, new ItemEventArgs<TItem>(item));
} }
/// <summary>Fires the 'ItemRemoved' event</summary> /// <summary>Fires the 'ItemRemoved' event</summary>
/// <param name="item">Item that has been removed from the collection</param> /// <param name="item">Item that has been removed from the collection</param>
protected virtual void OnRemoved(ItemType item) { protected virtual void OnRemoved(TItem item) {
if(ItemRemoved != null) if (ItemRemoved != null)
ItemRemoved(this, new ItemEventArgs<ItemType>(item)); ItemRemoved(this, new ItemEventArgs<TItem>(item));
} }
/// <summary>Fires the 'Clearing' event</summary> /// <summary>Fires the 'Clearing' event</summary>
protected virtual void OnClearing() { protected virtual void OnClearing() {
if(Clearing != null) if (Clearing != null)
Clearing(this, EventArgs.Empty); Clearing(this, EventArgs.Empty);
} }
/// <summary>Fires the 'Cleared' event</summary> /// <summary>Fires the 'Cleared' event</summary>
protected virtual void OnCleared() { protected virtual void OnCleared() {
if(Cleared != null) if (Cleared != null)
Cleared(this, EventArgs.Empty); Cleared(this, EventArgs.Empty);
} }
} }
} // namespace Nuclex.Support.Collections } // namespace Nuclex.Support.Collections

View File

@ -19,27 +19,32 @@ License along with this library
#endregion #endregion
using System; using System;
using System.Collections.Generic;
using System.Collections; using System.Collections;
using System.Collections.ObjectModel; using System.Collections.Generic;
#if !NO_SPECIALIZED_COLLECTIONS
using System.Collections.Specialized;
#endif
using System.Runtime.Serialization; using System.Runtime.Serialization;
namespace Nuclex.Support.Collections { namespace Nuclex.Support.Collections {
/// <summary>A dictionary that sneds out change notifications</summary> /// <summary>A dictionary that sneds out change notifications</summary>
/// <typeparam name="KeyType">Type of the keys used in the dictionary</typeparam> /// <typeparam name="TKey">Type of the keys used in the dictionary</typeparam>
/// <typeparam name="ValueType">Type of the values used in the dictionary</typeparam> /// <typeparam name="TValue">Type of the values used in the dictionary</typeparam>
#if !NO_SERIALIZATION #if !NO_SERIALIZATION
[Serializable] [Serializable]
#endif #endif
public class ObservableDictionary<KeyType, ValueType> : public class ObservableDictionary<TKey, TValue> :
#if !NO_SERIALIZATION #if !NO_SERIALIZATION
ISerializable, ISerializable,
IDeserializationCallback, IDeserializationCallback,
#endif #endif
IDictionary<KeyType, ValueType>, IDictionary<TKey, TValue>,
IDictionary, IDictionary,
IObservableCollection<KeyValuePair<KeyType, ValueType>> { #if !NO_SPECIALIZED_COLLECTIONS
INotifyCollectionChanged,
#endif
IObservableCollection<KeyValuePair<TKey, TValue>> {
#if !NO_SERIALIZATION #if !NO_SERIALIZATION
#region class SerializedDictionary #region class SerializedDictionary
@ -47,7 +52,7 @@ namespace Nuclex.Support.Collections {
/// <summary> /// <summary>
/// Dictionary wrapped used to reconstruct a serialized read only dictionary /// Dictionary wrapped used to reconstruct a serialized read only dictionary
/// </summary> /// </summary>
private class SerializedDictionary : Dictionary<KeyType, ValueType> { private class SerializedDictionary : Dictionary<TKey, TValue> {
/// <summary> /// <summary>
/// Initializes a new instance of the System.WeakReference class, using deserialized /// Initializes a new instance of the System.WeakReference class, using deserialized
@ -70,23 +75,28 @@ namespace Nuclex.Support.Collections {
} }
#endregion // class SerializedDictionary #endregion // class SerializedDictionary
#endif // !COMPACTFRAMEWORK #endif // !NO_SERIALIZATION
/// <summary>Raised when an item has been added to the dictionary</summary> /// <summary>Raised when an item has been added to the dictionary</summary>
public event EventHandler<ItemEventArgs<KeyValuePair<KeyType, ValueType>>> ItemAdded; public event EventHandler<ItemEventArgs<KeyValuePair<TKey, TValue>>> ItemAdded;
/// <summary>Raised when an item is removed from the dictionary</summary> /// <summary>Raised when an item is removed from the dictionary</summary>
public event EventHandler<ItemEventArgs<KeyValuePair<KeyType, ValueType>>> ItemRemoved; public event EventHandler<ItemEventArgs<KeyValuePair<TKey, TValue>>> ItemRemoved;
/// <summary>Raised when the dictionary is about to be cleared</summary> /// <summary>Raised when the dictionary is about to be cleared</summary>
public event EventHandler Clearing; public event EventHandler Clearing;
/// <summary>Raised when the dictionary has been cleared</summary> /// <summary>Raised when the dictionary has been cleared</summary>
public event EventHandler Cleared; public event EventHandler Cleared;
#if !NO_SPECIALIZED_COLLECTIONS
/// <summary>Called when the collection has changed</summary>
public event NotifyCollectionChangedEventHandler CollectionChanged;
#endif
/// <summary>Initializes a new observable dictionary</summary> /// <summary>Initializes a new observable dictionary</summary>
public ObservableDictionary() : this(new Dictionary<KeyType, ValueType>()) { } public ObservableDictionary() : this(new Dictionary<TKey, TValue>()) { }
/// <summary>Initializes a new observable Dictionary wrapper</summary> /// <summary>Initializes a new observable Dictionary wrapper</summary>
/// <param name="dictionary">Dictionary that will be wrapped</param> /// <param name="dictionary">Dictionary that will be wrapped</param>
public ObservableDictionary(IDictionary<KeyType, ValueType> dictionary) { public ObservableDictionary(IDictionary<TKey, TValue> dictionary) {
this.typedDictionary = dictionary; this.typedDictionary = dictionary;
this.objectDictionary = (this.typedDictionary as IDictionary); this.objectDictionary = (this.typedDictionary as IDictionary);
} }
@ -123,7 +133,7 @@ namespace Nuclex.Support.Collections {
/// </summary> /// </summary>
/// <param name="item">KeyValuePair that will be checked for</param> /// <param name="item">KeyValuePair that will be checked for</param>
/// <returns>True if the provided KeyValuePair was contained in the Dictionary</returns> /// <returns>True if the provided KeyValuePair was contained in the Dictionary</returns>
public bool Contains(KeyValuePair<KeyType, ValueType> item) { public bool Contains(KeyValuePair<TKey, TValue> item) {
return this.typedDictionary.Contains(item); return this.typedDictionary.Contains(item);
} }
@ -132,7 +142,7 @@ namespace Nuclex.Support.Collections {
/// <returns> /// <returns>
/// True if an entry with the specified key was contained in the Dictionary /// True if an entry with the specified key was contained in the Dictionary
/// </returns> /// </returns>
public bool ContainsKey(KeyType key) { public bool ContainsKey(TKey key) {
return this.typedDictionary.ContainsKey(key); return this.typedDictionary.ContainsKey(key);
} }
@ -141,7 +151,7 @@ namespace Nuclex.Support.Collections {
/// <param name="arrayIndex"> /// <param name="arrayIndex">
/// Starting index at which to begin filling the destination array /// Starting index at which to begin filling the destination array
/// </param> /// </param>
public void CopyTo(KeyValuePair<KeyType, ValueType>[] array, int arrayIndex) { public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex) {
this.typedDictionary.CopyTo(array, arrayIndex); this.typedDictionary.CopyTo(array, arrayIndex);
} }
@ -152,17 +162,17 @@ namespace Nuclex.Support.Collections {
/// <summary>Creates a new enumerator for the Dictionary</summary> /// <summary>Creates a new enumerator for the Dictionary</summary>
/// <returns>The new Dictionary enumerator</returns> /// <returns>The new Dictionary enumerator</returns>
public IEnumerator<KeyValuePair<KeyType, ValueType>> GetEnumerator() { public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator() {
return this.typedDictionary.GetEnumerator(); return this.typedDictionary.GetEnumerator();
} }
/// <summary>Collection of all keys contained in the Dictionary</summary> /// <summary>Collection of all keys contained in the Dictionary</summary>
public ICollection<KeyType> Keys { public ICollection<TKey> Keys {
get { return this.typedDictionary.Keys; } get { return this.typedDictionary.Keys; }
} }
/// <summary>Collection of all values contained in the Dictionary</summary> /// <summary>Collection of all values contained in the Dictionary</summary>
public ICollection<ValueType> Values { public ICollection<TValue> Values {
get { return this.typedDictionary.Values; } get { return this.typedDictionary.Values; }
} }
@ -176,46 +186,46 @@ namespace Nuclex.Support.Collections {
/// <returns> /// <returns>
/// True if the item was found and has been placed in the output parameter /// True if the item was found and has been placed in the output parameter
/// </returns> /// </returns>
public bool TryGetValue(KeyType key, out ValueType value) { public bool TryGetValue(TKey key, out TValue value) {
return this.typedDictionary.TryGetValue(key, out value); return this.typedDictionary.TryGetValue(key, out value);
} }
/// <summary>Accesses an item in the Dictionary by its key</summary> /// <summary>Accesses an item in the Dictionary by its key</summary>
/// <param name="key">Key of the item that will be accessed</param> /// <param name="key">Key of the item that will be accessed</param>
public ValueType this[KeyType key] { public TValue this[TKey key] {
get { return this.typedDictionary[key]; } get { return this.typedDictionary[key]; }
set { set {
bool removed; bool removed;
ValueType oldValue; TValue oldValue;
removed = this.typedDictionary.TryGetValue(key, out oldValue); removed = this.typedDictionary.TryGetValue(key, out oldValue);
this.typedDictionary[key] = value; this.typedDictionary[key] = value;
if(removed) { if(removed) {
OnRemoved(new KeyValuePair<KeyType, ValueType>(key, oldValue)); OnRemoved(new KeyValuePair<TKey, TValue>(key, oldValue));
} }
OnAdded(new KeyValuePair<KeyType, ValueType>(key, value)); OnAdded(new KeyValuePair<TKey, TValue>(key, value));
} }
} }
/// <summary>Inserts an item into the Dictionary</summary> /// <summary>Inserts an item into the Dictionary</summary>
/// <param name="key">Key under which to add the new item</param> /// <param name="key">Key under which to add the new item</param>
/// <param name="value">Item that will be added to the Dictionary</param> /// <param name="value">Item that will be added to the Dictionary</param>
public void Add(KeyType key, ValueType value) { public void Add(TKey key, TValue value) {
this.typedDictionary.Add(key, value); this.typedDictionary.Add(key, value);
OnAdded(new KeyValuePair<KeyType, ValueType>(key, value)); OnAdded(new KeyValuePair<TKey, TValue>(key, value));
} }
/// <summary>Removes the item with the specified key from the Dictionary</summary> /// <summary>Removes the item with the specified key from the Dictionary</summary>
/// <param name="key">Key of the elementes that will be removed</param> /// <param name="key">Key of the elementes that will be removed</param>
/// <returns>True if an item with the specified key was found and removed</returns> /// <returns>True if an item with the specified key was found and removed</returns>
public bool Remove(KeyType key) { public bool Remove(TKey key) {
ValueType oldValue; TValue oldValue;
this.typedDictionary.TryGetValue(key, out oldValue); this.typedDictionary.TryGetValue(key, out oldValue);
bool removed = this.typedDictionary.Remove(key); bool removed = this.typedDictionary.Remove(key);
if(removed) { if(removed) {
OnRemoved(new KeyValuePair<KeyType, ValueType>(key, oldValue)); OnRemoved(new KeyValuePair<TKey, TValue>(key, oldValue));
} }
return removed; return removed;
} }
@ -229,16 +239,32 @@ namespace Nuclex.Support.Collections {
/// <summary>Fires the 'ItemAdded' event</summary> /// <summary>Fires the 'ItemAdded' event</summary>
/// <param name="item">Item that has been added to the collection</param> /// <param name="item">Item that has been added to the collection</param>
protected virtual void OnAdded(KeyValuePair<KeyType, ValueType> item) { protected virtual void OnAdded(KeyValuePair<TKey, TValue> item) {
if(ItemAdded != null) if(ItemAdded != null)
ItemAdded(this, new ItemEventArgs<KeyValuePair<KeyType, ValueType>>(item)); 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> /// <summary>Fires the 'ItemRemoved' event</summary>
/// <param name="item">Item that has been removed from the collection</param> /// <param name="item">Item that has been removed from the collection</param>
protected virtual void OnRemoved(KeyValuePair<KeyType, ValueType> item) { protected virtual void OnRemoved(KeyValuePair<TKey, TValue> item) {
if(ItemRemoved != null) if(ItemRemoved != null)
ItemRemoved(this, new ItemEventArgs<KeyValuePair<KeyType, ValueType>>(item)); 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 'Clearing' event</summary> /// <summary>Fires the 'Clearing' event</summary>
@ -251,6 +277,10 @@ namespace Nuclex.Support.Collections {
protected virtual void OnCleared() { protected virtual void OnCleared() {
if(Cleared != null) if(Cleared != null)
Cleared(this, EventArgs.Empty); Cleared(this, EventArgs.Empty);
#if !NO_SPECIALIZED_COLLECTIONS
if(CollectionChanged != null)
CollectionChanged(this, CollectionResetEventArgs);
#endif
} }
#region IEnumerable implementation #region IEnumerable implementation
@ -270,7 +300,7 @@ namespace Nuclex.Support.Collections {
/// <param name="value">Item that will be added</param> /// <param name="value">Item that will be added</param>
void IDictionary.Add(object key, object value) { void IDictionary.Add(object key, object value) {
this.objectDictionary.Add(key, value); this.objectDictionary.Add(key, value);
OnAdded(new KeyValuePair<KeyType, ValueType>((KeyType)key, (ValueType)value)); OnAdded(new KeyValuePair<TKey, TValue>((TKey)key, (TValue)value));
} }
/// <summary>Determines whether the specified key exists in the Dictionary</summary> /// <summary>Determines whether the specified key exists in the Dictionary</summary>
@ -304,11 +334,11 @@ namespace Nuclex.Support.Collections {
/// <summary>Removes an item from the Dictionary</summary> /// <summary>Removes an item from the Dictionary</summary>
/// <param name="key">Key of the item that will be removed</param> /// <param name="key">Key of the item that will be removed</param>
void IDictionary.Remove(object key) { void IDictionary.Remove(object key) {
ValueType value; TValue value;
bool removed = this.typedDictionary.TryGetValue((KeyType)key, out value); bool removed = this.typedDictionary.TryGetValue((TKey)key, out value);
this.objectDictionary.Remove(key); this.objectDictionary.Remove(key);
if(removed) { if(removed) {
OnRemoved(new KeyValuePair<KeyType, ValueType>((KeyType)key, (ValueType)value)); OnRemoved(new KeyValuePair<TKey, TValue>((TKey)key, (TValue)value));
} }
} }
@ -319,15 +349,15 @@ namespace Nuclex.Support.Collections {
get { return this.objectDictionary[key]; } get { return this.objectDictionary[key]; }
set { set {
bool removed; bool removed;
ValueType oldValue; TValue oldValue;
removed = this.typedDictionary.TryGetValue((KeyType)key, out oldValue); removed = this.typedDictionary.TryGetValue((TKey)key, out oldValue);
this.objectDictionary[key] = value; this.objectDictionary[key] = value;
if(removed) { if(removed) {
OnRemoved(new KeyValuePair<KeyType, ValueType>((KeyType)key, oldValue)); OnRemoved(new KeyValuePair<TKey, TValue>((TKey)key, oldValue));
} }
OnAdded(new KeyValuePair<KeyType, ValueType>((KeyType)key, (ValueType)value)); OnAdded(new KeyValuePair<TKey, TValue>((TKey)key, (TValue)value));
} }
} }
@ -337,15 +367,15 @@ namespace Nuclex.Support.Collections {
/// <summary>Inserts an already prepared element into the Dictionary</summary> /// <summary>Inserts an already prepared element into the Dictionary</summary>
/// <param name="item">Prepared element that will be added to the Dictionary</param> /// <param name="item">Prepared element that will be added to the Dictionary</param>
void ICollection<KeyValuePair<KeyType, ValueType>>.Add( void ICollection<KeyValuePair<TKey, TValue>>.Add(
KeyValuePair<KeyType, ValueType> item KeyValuePair<TKey, TValue> item
) { ) {
this.typedDictionary.Add(item); this.typedDictionary.Add(item);
OnAdded(item); OnAdded(item);
} }
/// <summary>Removes all items from the Dictionary</summary> /// <summary>Removes all items from the Dictionary</summary>
void ICollection<KeyValuePair<KeyType, ValueType>>.Clear() { void ICollection<KeyValuePair<TKey, TValue>>.Clear() {
OnClearing(); OnClearing();
this.typedDictionary.Clear(); this.typedDictionary.Clear();
OnCleared(); OnCleared();
@ -353,8 +383,8 @@ namespace Nuclex.Support.Collections {
/// <summary>Removes all items from the Dictionary</summary> /// <summary>Removes all items from the Dictionary</summary>
/// <param name="itemToRemove">Item that will be removed from the Dictionary</param> /// <param name="itemToRemove">Item that will be removed from the Dictionary</param>
bool ICollection<KeyValuePair<KeyType, ValueType>>.Remove( bool ICollection<KeyValuePair<TKey, TValue>>.Remove(
KeyValuePair<KeyType, ValueType> itemToRemove KeyValuePair<TKey, TValue> itemToRemove
) { ) {
bool removed = this.typedDictionary.Remove(itemToRemove); bool removed = this.typedDictionary.Remove(itemToRemove);
if(removed) { if(removed) {
@ -412,9 +442,16 @@ namespace Nuclex.Support.Collections {
#endif //!NO_SERIALIZATION #endif //!NO_SERIALIZATION
/// <summary>The wrapped Dictionary under its type-safe interface</summary> /// <summary>The wrapped Dictionary under its type-safe interface</summary>
private IDictionary<KeyType, ValueType> typedDictionary; private IDictionary<TKey, TValue> typedDictionary;
/// <summary>The wrapped Dictionary under its object interface</summary> /// <summary>The wrapped Dictionary under its object interface</summary>
private IDictionary objectDictionary; private IDictionary objectDictionary;
#if !NO_SPECIALIZED_COLLECTIONS
/// <summary>Fixed event args used to notify that the collection has reset</summary>
private static readonly NotifyCollectionChangedEventArgs CollectionResetEventArgs =
new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset);
#endif
} }
} // namespace Nuclex.Support.Collections } // namespace Nuclex.Support.Collections