diff --git a/Documents/CommandLine.txt b/Documents/CommandLine.txt
index ef8da36..c3c6589 100644
--- a/Documents/CommandLine.txt
+++ b/Documents/CommandLine.txt
@@ -1,13 +1,478 @@
- /*
- struct CommandLine {
- [Option]
- bool? Option;
- [Option]
- int? Width;
- [Option]
- TypeCode Code;
- [Values]
- string[] Values;
- }
-*/
+#region CPL License
+/*
+Nuclex Framework
+Copyright (C) 2002-2012 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 {
+
+ /// Dictionary that can contain multiple values under the same key
+ /// Type of keys used within the dictionary
+ /// Type of values used within the dictionary
+ public partial class MultiDictionary : IMultiDictionary {
+
+ #region class Enumerator
+
+ /// Enumerates the values stored in a multi dictionary
+ private class Enumerator :
+ IEnumerator>, IDictionaryEnumerator {
+
+ /// Initializes a new multi dictionary enumerator
+ /// Dictionary that will be enumerated
+ public Enumerator(MultiDictionary dictionary) {
+ this.dictionary = dictionary;
+
+ Reset();
+ }
+
+ /// The current entry the enumerator is pointing at
+ public KeyValuePair Current {
+ get {
+ if(this.currentValue == null) {
+ throw new InvalidOperationException("Enumerator is not on a valid position");
+ }
+
+ return new KeyValuePair(
+ this.currentCollection.Current.Key, this.currentValue.Current
+ );
+ }
+ }
+
+ /// Immediately releases all resources owned by the instance
+ public void Dispose() {
+ if(this.currentValue != null) {
+ this.currentValue.Dispose();
+ this.currentValue = null;
+ }
+ if(this.currentCollection != null) {
+ this.currentCollection.Dispose();
+ this.currentCollection = null;
+ }
+ }
+
+ /// Advances the enumerator to the entry
+ ///
+ /// True if there was a next entry, false if the end of the set has been reached
+ ///
+ 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;
+ }
+
+ }
+ }
+
+ /// Resets the enumerator to its initial position
+ 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
+
+ /// The item the enumerator is currently pointing at
+ object IEnumerator.Current {
+ get { return Current; }
+ }
+
+ #endregion // IEnumerator implementation
+
+ #region IDictionaryEnumerator implementation
+
+ /// The current entry the enumerator is pointing to
+ DictionaryEntry IDictionaryEnumerator.Entry {
+ get {
+ enforceEnumeratorOnValidPosition();
+
+ return new DictionaryEntry(
+ this.currentCollection.Current.Key, this.currentValue.Current
+ );
+ }
+ }
+
+ ///
+ /// Throws an exception if the enumerator is not on a valid position
+ ///
+ private void enforceEnumeratorOnValidPosition() {
+ if(this.currentValue == null) {
+ throw new InvalidOperationException("Enumerator is not on a valid position");
+ }
+ }
+
+ /// The current dictionary key
+ object IDictionaryEnumerator.Key {
+ get {
+ enforceEnumeratorOnValidPosition();
+ return this.currentCollection.Current.Key;
+ }
+ }
+
+ /// The current dictionary value
+ object IDictionaryEnumerator.Value {
+ get {
+ enforceEnumeratorOnValidPosition();
+ return this.currentValue.Current;
+ }
+ }
+
+ #endregion // IDictionaryEnumerator implementation
+
+ /// Dictionary over whose entries the enumerator is enumerating
+ private IDictionary> dictionary;
+ /// Current key the enumerator is at
+ private IEnumerator>> currentCollection;
+ /// Current value in the current key the enumerator is at
+ private IEnumerator currentValue;
+
+ }
+
+ #endregion // class Enumerator
+
+ #region class ValueList
+
+ /// Stores the list of values for a dictionary key
+ private class ValueList : Collection {
+
+ /// Initializes a new value list
+ /// Dictionary the value list belongs to
+ public ValueList(MultiDictionary dictionary) {
+ this.dictionary = dictionary;
+ }
+
+ /// Called when the value list is being cleared
+ protected override void ClearItems() {
+ this.dictionary.count -= Count;
+ base.ClearItems();
+ }
+
+ /// Called when an item is inserted into the value list
+ /// Index at which the item is being inserted
+ /// Item that is being inserted
+ protected override void InsertItem(int index, TValue item) {
+ base.InsertItem(index, item);
+ ++this.dictionary.count;
+ }
+
+ /// Called when an item is removed from the value list
+ /// Index at which the item is being removed
+ protected override void RemoveItem(int index) {
+ base.RemoveItem(index);
+ --this.dictionary.count;
+ }
+
+ /// The dictionary the value list belongs to
+ private MultiDictionary dictionary;
+
+ }
+
+ #endregion // class ValueList
+
+ /// Initializes a new multi dictionary
+ public MultiDictionary() : this(new Dictionary>()) { }
+
+ /// Initializes a new multi dictionary
+ /// Dictionary the multi dictionary will be based on
+ internal MultiDictionary(IDictionary> dictionary) {
+ this.typedDictionary = dictionary;
+ this.objectDictionary = (this.typedDictionary as IDictionary);
+
+ foreach(ICollection values in dictionary.Values) {
+ this.count += values.Count;
+ }
+ }
+
+ /// Whether the dictionary is write-protected
+ public bool IsReadOnly {
+ get { return this.typedDictionary.IsReadOnly; }
+ }
+
+ /// Determines the number of values stored under the specified key
+ /// Key whose values will be counted
+ /// The number of values stored under the specified key
+ public int CountValues(TKey key) {
+ ICollection values;
+ if(this.typedDictionary.TryGetValue(key, out values)) {
+ return values.Count;
+ } else {
+ return 0;
+ }
+ }
+
+ ///
+ /// Determines whether the specified KeyValuePair is contained in the dictionary
+ ///
+ /// KeyValuePair that will be checked for
+ /// True if the provided KeyValuePair was contained in the dictionary
+ public bool Contains(KeyValuePair item) {
+ ICollection values;
+ if(this.typedDictionary.TryGetValue(item.Key, out values)) {
+ return values.Contains(item.Value);
+ } else {
+ return false;
+ }
+ }
+
+ /// Determines whether the Dictionary contains the specified key
+ /// Key that will be checked for
+ ///
+ /// True if an entry with the specified key was contained in the Dictionary
+ ///
+ public bool ContainsKey(TKey key) {
+ return this.typedDictionary.ContainsKey(key);
+ }
+
+ /// Copies the contents of the Dictionary into an array
+ /// Array the Dictionary will be copied into
+ ///
+ /// Starting index at which to begin filling the destination array
+ ///
+ public void CopyTo(KeyValuePair[] array, int arrayIndex) {
+ foreach(KeyValuePair> item in this.typedDictionary) {
+ foreach(TValue value in item.Value) {
+ array[arrayIndex] = new KeyValuePair(item.Key, value);
+ ++arrayIndex;
+ }
+ }
+ }
+
+ /// Number of elements contained in the Dictionary
+ public int Count {
+ get { return this.count; }
+ }
+
+ /// Creates a new enumerator for the dictionary
+ /// The new dictionary enumerator
+ public IEnumerator> GetEnumerator() {
+ return new Enumerator(this);
+ }
+
+ /// Collection of all keys contained in the dictionary
+ public ICollection Keys {
+ get { return this.typedDictionary.Keys; }
+ }
+
+ /// Collection of all values contained in the dictionary
+ public ICollection Values {
+ get {
+ if(this.valueCollection == null) {
+ this.valueCollection = new ValueCollection(this);
+ }
+
+ return this.valueCollection;
+ }
+ }
+
+ ///
+ /// Attempts to retrieve the item with the specified key from the dictionary
+ ///
+ /// Key of the item to attempt to retrieve
+ ///
+ /// Output parameter that will receive the values upon successful completion
+ ///
+ ///
+ /// True if the item was found and has been placed in the output parameter
+ ///
+ public bool TryGetValue(TKey key, out ICollection values) {
+ return this.typedDictionary.TryGetValue(key, out values);
+ }
+
+ /// Accesses an item in the dictionary by its key
+ /// Key of the item that will be accessed
+ public ICollection this[TKey key] {
+ get { return this.typedDictionary[key]; }
+ set {
+ if(value == null) {
+ ICollection values;
+ if(this.typedDictionary.TryGetValue(key, out values)) {
+ foreach(TValue removedValue in values) {
+
+ }
+ }
+ this.typedDictionary.Remove(key);
+ }
+
+ ICollection currentValues;
+ if(this.typedDictionary.TryGetValue(key, out currentValues)) {
+ ValueList currentValueList = (ValueList)currentValues;
+
+ int index = 0;
+ foreach(TValue addedValue in value) {
+ if(index < currentValueList.Count) {
+ TValue original = currentValueList[index];
+ currentValueList[index] = addedValue;
+ OnReplaced(
+ new KeyValuePair(key, original),
+ new KeyValuePair(key, addedValue)
+ );
+ } else {
+ currentValueList.Add(addedValue);
+ OnAdded(new KeyValuePair(key, addedValue));
+ }
+ ++index;
+ }
+
+ int count = currentValueList.Count;
+ while(count > index) {
+ --count;
+ TValue removedValue = currentValueList[count];
+ currentValueList.RemoveAt(count);
+ OnRemoved(new KeyValuePair(key, removedValue));
+ }
+ } else {
+ currentValues = new ValueList(this);
+ this.typedDictionary.Add(key, currentValues);
+
+ foreach(TValue addedValue in value) {
+ currentValues.Add(addedValue);
+ OnAdded(new KeyValuePair(key, addedValue));
+ ++this.count;
+ }
+ }
+ }
+ }
+
+ /// Inserts an item into the dictionary
+ /// Key under which to add the new item
+ /// Item that will be added to the dictionary
+ public void Add(TKey key, TValue value) {
+ ICollection values;
+ if(!this.typedDictionary.TryGetValue(key, out values)) {
+ values = new ValueList(this);
+ this.typedDictionary.Add(key, values);
+ }
+
+ values.Add(value);
+ OnAdded(new KeyValuePair(key, value));
+ }
+
+ ///
+ /// Removes the item with the specified key and value from the dictionary
+ ///
+ /// Key of the item that will be removed
+ /// Value of the item that will be removed
+ ///
+ /// True if the specified item was contained in the dictionary and was removed
+ ///
+ /// If the dictionary is read-only
+ public bool Remove(TKey key, TValue value) {
+ ICollection values;
+ if(this.typedDictionary.TryGetValue(key, out values)) {
+ values.Remove(value);
+
+ if(values.Count == 0) {
+ this.typedDictionary.Remove(key);
+ }
+
+ OnRemoved(new KeyValuePair(key, value));
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /// Removes all items with the specified key from the dictionary
+ /// Key of the item that will be removed
+ /// The number of items that have been removed from the dictionary
+ /// If the dictionary is read-only
+ public int RemoveKey(TKey key) {
+ ICollection values;
+ if(this.typedDictionary.TryGetValue(key, out values)) {
+ this.count -= values.Count;
+ this.typedDictionary.Remove(key);
+
+ foreach(TValue value in values) {
+ OnRemoved(new KeyValuePair(key, value));
+ }
+ return values.Count;
+ } else {
+ return 0;
+ }
+ }
+
+ /// Removes all items from the Dictionary
+ public void Clear() {
+ OnClearing();
+ this.typedDictionary.Clear();
+ this.count = 0;
+ OnCleared();
+ }
+
+ /// Fires the 'ItemAdded' event
+ /// Item that has been added to the collection
+ protected virtual void OnAdded(KeyValuePair item) { }
+
+ /// Fires the 'ItemRemoved' event
+ /// Item that has been removed from the collection
+ protected virtual void OnRemoved(KeyValuePair item) { }
+
+ /// Fires the 'ItemReplaced' event
+ /// Item that was replaced in the collection
+ /// Item that the original was replaced by
+ protected virtual void OnReplaced(
+ KeyValuePair oldItem, KeyValuePair newItem
+ ) { }
+
+ /// Fires the 'Clearing' event
+ protected virtual void OnClearing() { }
+
+ /// Fires the 'Cleared' event
+ protected virtual void OnCleared() { }
+
+ /// The wrapped Dictionary under its type-safe interface
+ private IDictionary> typedDictionary;
+ /// The wrapped Dictionary under its object interface
+ private IDictionary objectDictionary;
+ /// The number of items currently in the multi dictionary
+ private int count;
+ /// Provides the values stores in the dictionary in sequence
+ private ValueCollection valueCollection;
+
+ }
+
+} // namespace Nuclex.Support.Collections
diff --git a/Source/Collections/MultiDictionary.Interfaces.cs b/Source/Collections/MultiDictionary.Interfaces.cs
index 86c1a1e..040ee01 100644
--- a/Source/Collections/MultiDictionary.Interfaces.cs
+++ b/Source/Collections/MultiDictionary.Interfaces.cs
@@ -119,8 +119,6 @@ namespace Nuclex.Support.Collections {
if(values.Count == 0) {
this.typedDictionary.Remove(itemToRemove.Key);
}
-
- OnRemoved(itemToRemove);
return true;
} else {
return false;
diff --git a/Source/Collections/MultiDictionary.Test.cs b/Source/Collections/MultiDictionary.Test.cs
index f950252..804057b 100644
--- a/Source/Collections/MultiDictionary.Test.cs
+++ b/Source/Collections/MultiDictionary.Test.cs
@@ -206,6 +206,184 @@ namespace Nuclex.Support.Collections {
Assert.IsFalse(dictionary.ContainsKey(20));
}
+ ///
+ /// Verifies that the key collection can be retrieved from the dictionary
+ ///
+ [Test]
+ public void KeyCollectionCanBeRetrieved() {
+ var dictionary = new MultiDictionary();
+ dictionary.Add(10, "ten");
+ dictionary.Add(10, "zehn");
+
+ ICollection keys = dictionary.Keys;
+ Assert.IsNotNull(keys);
+ Assert.AreEqual(1, keys.Count);
+ }
+
+ ///
+ /// Verifies that the key collection can be retrieved from the dictionary
+ ///
+ [Test]
+ public void ValueCollectionCanBeRetrieved() {
+ var dictionary = new MultiDictionary();
+ dictionary.Add(10, "ten");
+ dictionary.Add(10, "zehn");
+ dictionary.Add(20, "twenty");
+
+ ICollection values = dictionary.Values;
+ Assert.IsNotNull(values);
+ Assert.AreEqual(3, values.Count);
+ }
+
+ ///
+ /// Verifies that TryGetValue() returns false and doesn't throw if a key
+ /// is not found in the collection
+ ///
+ [Test]
+ public void TryGetValueReturnsFalseOnMissingKey() {
+ var dictionary = new MultiDictionary();
+ ICollection values;
+ Assert.IsFalse(dictionary.TryGetValue(123, out values));
+ }
+
+ /// Verifies that keys can be looked up via TryGetValue()
+ [Test]
+ public void TryGetValueCanLookUpValues() {
+ var dictionary = new MultiDictionary();
+ dictionary.Add(10, "ten");
+ dictionary.Add(10, "zehn");
+ ICollection values;
+ Assert.IsTrue(dictionary.TryGetValue(10, out values));
+ Assert.AreEqual(2, values.Count);
+ }
+
+ ///
+ /// Verifies that assigning null to a key deletes all the values stored
+ /// under it
+ ///
+ [Test]
+ public void AssigningNullToKeyRemovesAllValues() {
+ var dictionary = new MultiDictionary();
+ 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));
+ }
+
+ ///
+ /// Verifies that assigning null to a key deletes all the values stored
+ /// under it
+ ///
+ [Test]
+ public void ValueListCanBeAssignedToNewKey() {
+ var dictionary = new MultiDictionary();
+ dictionary[3] = new List() { "three", "drei" };
+
+ Assert.AreEqual(2, dictionary.Count);
+ Assert.IsTrue(dictionary.Contains(new KeyValuePair(3, "three")));
+ }
+
+ ///
+ /// Verifies that assigning null to a key deletes all the values stored
+ /// under it
+ ///
+ [Test]
+ public void ValueListCanOverwriteExistingKey() {
+ var dictionary = new MultiDictionary();
+ dictionary.Add(10, "dix");
+
+ Assert.AreEqual(1, dictionary.Count);
+
+ dictionary[10] = new List() { "ten", "zehn" };
+
+ Assert.AreEqual(2, dictionary.Count);
+ Assert.IsFalse(dictionary.Contains(new KeyValuePair(10, "dix")));
+ Assert.IsTrue(dictionary.Contains(new KeyValuePair(10, "ten")));
+ }
+
+ ///
+ /// Verifies that nothing bad happens when a key is removed from the dictionary
+ /// that it doesn't contain
+ ///
+ [Test]
+ public void NonExistingKeyCanBeRemoved() {
+ var dictionary = new MultiDictionary();
+ Assert.AreEqual(0, dictionary.RemoveKey(123));
+ }
+
+ ///
+ /// Verifies that the remove method returns the number of values that have
+ /// been removed from the dictionary
+ ///
+ [Test]
+ public void RemoveReturnsNumberOfValuesRemoved() {
+ var dictionary = new MultiDictionary();
+ dictionary.Add(10, "ten");
+ dictionary.Add(10, "zehn");
+ Assert.AreEqual(2, dictionary.RemoveKey(10));
+ }
+
+ ///
+ /// Verifies that the dictionary becomes empty after clearing it
+ ///
+ [Test]
+ public void DictionaryIsEmptyAfterClear() {
+ var dictionary = new MultiDictionary();
+ dictionary.Add(10, "ten");
+ dictionary.Add(10, "zehn");
+ dictionary.Add(20, "twenty");
+ Assert.AreEqual(3, dictionary.Count);
+ dictionary.Clear();
+ Assert.AreEqual(0, dictionary.Count);
+ }
+
+ ///
+ /// Verifies that non-existing values can be removed from the dictionary
+ ///
+ [Test]
+ public void NonExistingValueCanBeRemoved() {
+ var dictionary = new MultiDictionary();
+ Assert.IsFalse(dictionary.Remove(123, "test"));
+ }
+
+ ///
+ /// Verifies that nothing bad happens when the last value under a key is removed
+ ///
+ [Test]
+ public void LastValueOfKeyCanBeRemoved() {
+ var dictionary = new MultiDictionary();
+ dictionary.Add(123, "test");
+ dictionary.Remove(123, "test");
+ Assert.AreEqual(0, dictionary.CountValues(123));
+ }
+
+ ///
+ /// Verifies that the dictionary can be copied into an array
+ ///
+ [Test]
+ public void DictionaryCanBeCopiedIntoArray() {
+ var expected = new List>() {
+ new KeyValuePair(1, "one"),
+ new KeyValuePair(1, "eins"),
+ new KeyValuePair(2, "two"),
+ new KeyValuePair(2, "zwei")
+ };
+
+ var dictionary = new MultiDictionary();
+ foreach(KeyValuePair entry in expected) {
+ dictionary.Add(entry.Key, entry.Value);
+ }
+
+ var actual = new KeyValuePair[4];
+ dictionary.CopyTo(actual, 0);
+
+ CollectionAssert.AreEquivalent(expected, actual);
+ }
+
}
} // namespace Nuclex.Support.Collections
diff --git a/Source/Collections/MultiDictionary.cs b/Source/Collections/MultiDictionary.cs
index 9674990..d627192 100644
--- a/Source/Collections/MultiDictionary.cs
+++ b/Source/Collections/MultiDictionary.cs
@@ -328,43 +328,17 @@ namespace Nuclex.Support.Collections {
get { return this.typedDictionary[key]; }
set {
if(value == null) {
- this.typedDictionary.Remove(key);
- }
-
- ICollection currentValues;
- if(this.typedDictionary.TryGetValue(key, out currentValues)) {
- ValueList currentValueList = (ValueList)currentValues;
-
- int index = 0;
- foreach(TValue addedValue in value) {
- if(index < currentValueList.Count) {
- TValue original = currentValueList[index];
- currentValueList[index] = addedValue;
- OnReplaced(
- new KeyValuePair(key, original),
- new KeyValuePair(key, addedValue)
- );
- } else {
- currentValueList.Add(addedValue);
- OnAdded(new KeyValuePair(key, addedValue));
- }
- ++index;
- }
-
- int count = currentValueList.Count;
- while(count > index) {
- --count;
- TValue removedValue = currentValueList[count];
- currentValueList.RemoveAt(count);
- OnRemoved(new KeyValuePair(key, removedValue));
- }
+ RemoveKey(key);
} else {
- currentValues = new ValueList(this);
- this.typedDictionary.Add(key, currentValues);
-
+ ICollection 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(key, addedValue));
}
}
}
@@ -381,7 +355,6 @@ namespace Nuclex.Support.Collections {
}
values.Add(value);
- OnAdded(new KeyValuePair(key, value));
}
///
@@ -397,12 +370,9 @@ namespace Nuclex.Support.Collections {
ICollection values;
if(this.typedDictionary.TryGetValue(key, out values)) {
values.Remove(value);
-
if(values.Count == 0) {
this.typedDictionary.Remove(key);
}
-
- OnRemoved(new KeyValuePair(key, value));
return true;
} else {
return false;
@@ -418,10 +388,6 @@ namespace Nuclex.Support.Collections {
if(this.typedDictionary.TryGetValue(key, out values)) {
this.count -= values.Count;
this.typedDictionary.Remove(key);
-
- foreach(TValue value in values) {
- OnRemoved(new KeyValuePair(key, value));
- }
return values.Count;
} else {
return 0;
@@ -430,33 +396,10 @@ namespace Nuclex.Support.Collections {
/// Removes all items from the Dictionary
public void Clear() {
- OnClearing();
this.typedDictionary.Clear();
this.count = 0;
- OnCleared();
}
- /// Fires the 'ItemAdded' event
- /// Item that has been added to the collection
- protected virtual void OnAdded(KeyValuePair item) { }
-
- /// Fires the 'ItemRemoved' event
- /// Item that has been removed from the collection
- protected virtual void OnRemoved(KeyValuePair item) { }
-
- /// Fires the 'ItemReplaced' event
- /// Item that was replaced in the collection
- /// Item that the original was replaced by
- protected virtual void OnReplaced(
- KeyValuePair oldItem, KeyValuePair newItem
- ) { }
-
- /// Fires the 'Clearing' event
- protected virtual void OnClearing() { }
-
- /// Fires the 'Cleared' event
- protected virtual void OnCleared() { }
-
/// The wrapped Dictionary under its type-safe interface
private IDictionary> typedDictionary;
/// The wrapped Dictionary under its object interface