From 2cc7b27b0cc311588dd632e97a881216c2cb0d3a Mon Sep 17 00:00:00 2001 From: Markus Ewald Date: Thu, 17 Jul 2008 20:44:40 +0000 Subject: [PATCH] Added read-only collection wrappers for IDictionary<>, IList<> and ICollection<> since the classes provided by the .NET framework in System.Collections.ObjectModel are incomplete and inconsistent (eg. System.Collections.ObjectModel.ReadOnlyCollection wraps an IList<>!); minor improvements to documentation; added ReverseComparer for inverting the results of an existing IComparer<> or simply comparing values in inverted order git-svn-id: file:///srv/devel/repo-conversion/nusu@80 d2e56fa2-650e-0410-a79f-9358c0239efd --- Nuclex.Support (x86).csproj | 6 + Source/Collections/PriorityQueue.cs | 2 +- Source/Collections/ReadOnlyCollection.cs | 120 +++++++++ Source/Collections/ReadOnlyDictionary.cs | 326 +++++++++++++++++++++++ Source/Collections/ReadOnlyList.cs | 240 +++++++++++++++++ Source/Collections/ReverseComparer.cs | 36 +++ Source/Collections/RingMemoryStream.cs | 34 +-- 7 files changed, 748 insertions(+), 16 deletions(-) create mode 100644 Source/Collections/ReadOnlyCollection.cs create mode 100644 Source/Collections/ReadOnlyDictionary.cs create mode 100644 Source/Collections/ReadOnlyList.cs create mode 100644 Source/Collections/ReverseComparer.cs diff --git a/Nuclex.Support (x86).csproj b/Nuclex.Support (x86).csproj index 3e30fc9..aabecb2 100644 --- a/Nuclex.Support (x86).csproj +++ b/Nuclex.Support (x86).csproj @@ -81,6 +81,8 @@ False + + @@ -100,6 +102,10 @@ PriorityQueue.cs + + + + RingMemoryStream.cs diff --git a/Source/Collections/PriorityQueue.cs b/Source/Collections/PriorityQueue.cs index ca3706c..1cb46f0 100644 --- a/Source/Collections/PriorityQueue.cs +++ b/Source/Collections/PriorityQueue.cs @@ -127,7 +127,7 @@ namespace Nuclex.Support.Collections { ItemType result = this.heap[0]; --this.count; - trickleDown(0, heap[this.count]); + trickleDown(0, this.heap[this.count]); ++this.version; diff --git a/Source/Collections/ReadOnlyCollection.cs b/Source/Collections/ReadOnlyCollection.cs new file mode 100644 index 0000000..56caa9f --- /dev/null +++ b/Source/Collections/ReadOnlyCollection.cs @@ -0,0 +1,120 @@ +using System; +using System.Collections; +using System.Collections.Generic; + +namespace Nuclex.Support.Collections { + + /// Wraps a Collection and prevents users from modifying it + /// Type of items to manage in the Collection + public class ReadOnlyCollection : + ICollection, + ICollection { + + /// Initializes a new read-only Collection wrapper + /// Collection that will be wrapped + public ReadOnlyCollection(ICollection collection) { + this.typedCollection = collection; + this.objectCollection = (collection as ICollection); + } + + /// Determines whether the List contains the specified item + /// Item that will be checked for + /// True if the specified item is contained in the List + public bool Contains(ItemType item) { + return this.typedCollection.Contains(item); + } + + /// Copies the contents of the List into an array + /// Array the List will be copied into + /// + /// Starting index at which to begin filling the destination array + /// + public void CopyTo(ItemType[] array, int arrayIndex) { + this.typedCollection.CopyTo(array, arrayIndex); + } + + /// The number of items current contained in the List + public int Count { + get { return this.typedCollection.Count; } + } + + /// Whether the List is write-protected + public bool IsReadOnly { + get { return true; } + } + + /// Returns a new enumerator over the contents of the List + /// The new List contents enumerator + public IEnumerator GetEnumerator() { + return this.typedCollection.GetEnumerator(); + } + + #region ICollection<> implementation + + /// Adds an item to the end of the List + /// Item that will be added to the List + void ICollection.Add(ItemType item) { + throw new NotSupportedException( + "Adding items is not supported by the read-only List" + ); + } + + /// Removes all items from the List + void ICollection.Clear() { + throw new NotSupportedException( + "Clearing is not supported by the read-only List" + ); + } + + /// Removes the specified item from the List + /// Item that will be removed from the List + /// True of the specified item was found in the List and removed + bool ICollection.Remove(ItemType item) { + throw new NotSupportedException( + "Removing items is not supported by the read-only List" + ); + } + + #endregion + + #region IEnumerable implementation + + /// Returns a new enumerator over the contents of the List + /// The new List contents enumerator + IEnumerator IEnumerable.GetEnumerator() { + return this.objectCollection.GetEnumerator(); + } + + #endregion + + #region ICollection implementation + + /// Copies the contents of the List into an array + /// Array the List will be copied into + /// + /// Starting index at which to begin filling the destination array + /// + void ICollection.CopyTo(Array array, int index) { + throw new NotImplementedException(); + } + + /// Whether the List is synchronized for multi-threaded usage + bool ICollection.IsSynchronized { + get { throw new NotImplementedException(); } + } + + /// Synchronization root on which the List locks + object ICollection.SyncRoot { + get { throw new NotImplementedException(); } + } + + #endregion + + /// The wrapped Collection under its type-safe interface + private ICollection typedCollection; + /// The wrapped Collection under its object interface + private ICollection objectCollection; + + } + +} // namespace Nuclex.Support.Collections diff --git a/Source/Collections/ReadOnlyDictionary.cs b/Source/Collections/ReadOnlyDictionary.cs new file mode 100644 index 0000000..8ec555a --- /dev/null +++ b/Source/Collections/ReadOnlyDictionary.cs @@ -0,0 +1,326 @@ +using System; +using System.Collections.Generic; +using System.Collections; +using System.Collections.ObjectModel; +using System.Runtime.Serialization; + +namespace Nuclex.Support.Collections { + + /// Wraps a Dictionary and prevents users from modifying it + /// Type of the keys used in the Dictionary + /// Type of the values used in the Dictionary + public class ReadOnlyDictionary : + IDictionary, + IDictionary, + ISerializable, + IDeserializationCallback { + + /// Initializes a new read-only Dictionary wrapper + /// Dictionary that will be wrapped + public ReadOnlyDictionary(IDictionary dictionary) { + this.typedDictionary = dictionary; + this.objectDictionary = (this.typedDictionary as IDictionary); + } + + /// Whether the directory is write-protected + public bool IsReadOnly { + get { return true; } + } + + /// + /// 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) { + return this.typedDictionary.Contains(item); + } + + /// 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(KeyType 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) { + this.typedDictionary.CopyTo(array, arrayIndex); + } + + /// Number of elements contained in the Dictionary + public int Count { + get { return this.typedDictionary.Count; } + } + + /// Creates a new enumerator for the Dictionary + /// The new Dictionary enumerator + public IEnumerator> GetEnumerator() { + return this.typedDictionary.GetEnumerator(); + } + + /// Collection of all keys contained in the Dictionary + public ICollection Keys { + get { + if(this.readonlyKeyCollection == null) { + this.readonlyKeyCollection = new ReadOnlyCollection( + this.typedDictionary.Keys + ); + } + + return this.readonlyKeyCollection; + } + } + + /// Collection aller Werte im Dictionary + public ICollection Values { // TODO: RO-Wrappen! + get { + if(this.readonlyValueCollection == null) { + this.readonlyValueCollection = new ReadOnlyCollection( + this.typedDictionary.Values + ); + } + + return this.readonlyValueCollection; + } + } + + /// + /// 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 key upon successful completion + /// + /// + /// True if the item was found and has been placed in the output parameter + /// + public bool TryGetValue(KeyType key, out ValueType value) { + return this.typedDictionary.TryGetValue(key, out value); + } + + /// Accesses an item in the Dictionary by its key + /// Key of the item that will be accessed + public ValueType this[KeyType key] { + get { return this.typedDictionary[key]; } + } + + #region IDictionary<,> implementation + + /// Inserts an item into the Dictionary + /// Key under which to add the new item + /// Item that will be added to the Dictionary + void IDictionary.Add(KeyType key, ValueType value) { + throw new NotSupportedException( + "Adding items is not supported by the read-only Dictionary" + ); + } + + /// Removes the item with the specified key from the Dictionary + /// Key of the elementes that will be removed + /// True if an item with the specified key was found and removed + bool IDictionary.Remove(KeyType key) { + throw new NotSupportedException( + "Removing items is not supported by the read-only Dictionary" + ); + } + + /// Accesses an item in the Dictionary by its key + /// Key of the item that will be accessed + ValueType IDictionary.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 + + /// Returns a new object enumerator for the Dictionary + /// The new object enumerator + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { + return (this.typedDictionary as IEnumerable).GetEnumerator(); + } + + #endregion + + #region IDictionary implementation + + /// Removes all items from the Dictionary + void IDictionary.Clear() { + throw new NotSupportedException( + "Clearing is not supported in a read-only Dictionary" + ); + } + + /// Adds an item into the Dictionary + /// Key under which the item will be added + /// Item that will be added + void IDictionary.Add(object key, object value) { + throw new NotSupportedException( + "Adding items is not supported in a read-only Dictionary" + ); + } + + /// Determines whether the specified key exists in the Dictionary + /// Key that will be checked for + /// True if an item with the specified key exists in the Dictionary + bool IDictionary.Contains(object key) { + return this.objectDictionary.Contains(key); + } + + /// Returns a new entry enumerator for the dictionary + /// The new entry enumerator + IDictionaryEnumerator IDictionary.GetEnumerator() { + return this.objectDictionary.GetEnumerator(); + } + + /// Whether the size of the Dictionary is fixed + bool IDictionary.IsFixedSize { + get { return this.objectDictionary.IsFixedSize; } + } + + /// Returns a collection of all keys in the Dictionary + ICollection IDictionary.Keys { + get { + if(this.readonlyKeyCollection == null) { + this.readonlyKeyCollection = new ReadOnlyCollection( + this.typedDictionary.Keys + ); + } + + return this.readonlyKeyCollection; + } + } + + /// Returns a collection of all values stored in the Dictionary + ICollection IDictionary.Values { + get { + if(this.readonlyValueCollection == null) { + this.readonlyValueCollection = new ReadOnlyCollection( + this.typedDictionary.Values + ); + } + + return this.readonlyValueCollection; + } + } + + /// Removes an item from the Dictionary + /// Key of the item that will be removed + void IDictionary.Remove(object key) { + throw new NotSupportedException( + "Removing is not supported by the read-only Dictionary" + ); + } + + /// Accesses an item in the Dictionary by its key + /// Key of the item that will be accessed + /// The item with the specified key + 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 + + #region ICollection<> implementation + + /// Inserts an already prepared element into the Dictionary + /// Prepared element that will be added to the Dictionary + void ICollection>.Add( + KeyValuePair item + ) { + throw new NotSupportedException( + "Adding items is not supported by the read-only Dictionary" + ); + } + + /// Removes all items from the Dictionary + void ICollection>.Clear() { + throw new NotSupportedException( + "Clearing is not supported in a read-only Dictionary" + ); + } + + /// Removes all items from the Dictionary + /// Item that will be removed from the Dictionary + bool ICollection>.Remove( + KeyValuePair itemToRemove + ) { + throw new NotSupportedException( + "Removing items is not supported in a read-only Dictionary" + ); + } + + #endregion + + #region ICollection implementation + + /// Copies the contents of the Dictionary into an array + /// Array the Dictionary contents will be copied into + /// + /// Starting index at which to begin filling the destination array + /// + void ICollection.CopyTo(Array array, int index) { + this.objectDictionary.CopyTo(array, index); + } + + /// Whether the Dictionary is synchronized for multi-threaded usage + bool ICollection.IsSynchronized { + get { return this.objectDictionary.IsSynchronized; } + } + + /// Synchronization root on which the Dictionary locks + object ICollection.SyncRoot { + get { return this.objectDictionary.SyncRoot; } + } + + #endregion + + #region ISerializable implementation + + /// Serializes the Dictionary + /// + /// Provides the container into which the Dictionary will serialize itself + /// + /// + /// Contextual informations about the serialization environment + /// + void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context) { + (this.typedDictionary as ISerializable).GetObjectData(info, context); + } + + /// Called after all objects have been successfully deserialized + /// Nicht unterstützt + void IDeserializationCallback.OnDeserialization(object sender) { + (this.typedDictionary as IDeserializationCallback).OnDeserialization(sender); + } + + #endregion + + /// The wrapped Dictionary under its type-safe interface + private IDictionary typedDictionary; + /// The wrapped Dictionary under its object interface + private IDictionary objectDictionary; + /// ReadOnly wrapper for the keys collection of the Dictionary + private ReadOnlyCollection readonlyKeyCollection; + /// ReadOnly wrapper for the values collection of the Dictionary + private ReadOnlyCollection readonlyValueCollection; + } + +} // namespace Nuclex.Support.Collections diff --git a/Source/Collections/ReadOnlyList.cs b/Source/Collections/ReadOnlyList.cs new file mode 100644 index 0000000..45a623c --- /dev/null +++ b/Source/Collections/ReadOnlyList.cs @@ -0,0 +1,240 @@ +using System; +using System.Collections; +using System.Collections.Generic; + +namespace Nuclex.Support.Collections { + + /// Wraps a List and prevents users from modifying it + /// Type of items to manage in the List + public class ReadOnlyList : + IList, + IList { + + /// Initializes a new read-only List wrapper + /// List that will be wrapped + public ReadOnlyList(IList list) { + this.typedList = list; + this.objectList = (list as IList); + } + + /// Retrieves the index of an item within the List + /// Item whose index will be returned + /// The zero-based index of the specified item in the List + public int IndexOf(ItemType item) { + return this.typedList.IndexOf(item); + } + + /// Accesses the List item with the specified index + /// Zero-based index of the List item that will be accessed + public ItemType this[int index] { + get { return this.typedList[index]; } + } + + /// Determines whether the List contains the specified item + /// Item that will be checked for + /// True if the specified item is contained in the List + public bool Contains(ItemType item) { + return this.typedList.Contains(item); + } + + /// Copies the contents of the List into an array + /// Array the List will be copied into + /// + /// Starting index at which to begin filling the destination array + /// + public void CopyTo(ItemType[] array, int arrayIndex) { + this.typedList.CopyTo(array, arrayIndex); + } + + /// The number of items current contained in the List + public int Count { + get { return this.typedList.Count; } + } + + /// Whether the List is write-protected + public bool IsReadOnly { + get { return true; } + } + + /// Returns a new enumerator over the contents of the List + /// The new List contents enumerator + public IEnumerator GetEnumerator() { + return this.typedList.GetEnumerator(); + } + + #region IList<> implementation + + /// Inserts an item into the List + /// Zero-based index before which the item will be inserted + /// Item that will be inserted into the List + void IList.Insert(int index, ItemType item) { + throw new NotSupportedException( + "Inserting items is not supported by the read-only List" + ); + } + + /// Removes an item from the list + /// Zero-based index of the item that will be removed + void IList.RemoveAt(int index) { + throw new NotSupportedException( + "Removing items is not supported by the read-only List" + ); + } + + /// Accesses the List item with the specified index + /// Zero-based index of the List item that will be accessed + ItemType IList.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 + + /// Adds an item to the end of the List + /// Item that will be added to the List + void ICollection.Add(ItemType item) { + throw new NotSupportedException( + "Adding items is not supported by the read-only List" + ); + } + + /// Removes all items from the List + void ICollection.Clear() { + throw new NotSupportedException( + "Clearing is not supported by the read-only List" + ); + } + + /// Removes the specified item from the List + /// Item that will be removed from the List + /// True of the specified item was found in the List and removed + bool ICollection.Remove(ItemType item) { + throw new NotSupportedException( + "Removing items is not supported by the read-only List" + ); + } + + #endregion + + #region IEnumerable implementation + + /// Returns a new enumerator over the contents of the List + /// The new List contents enumerator + IEnumerator IEnumerable.GetEnumerator() { + return this.objectList.GetEnumerator(); + } + + #endregion + + #region IList implementation + + /// Removes all items from the List + void IList.Clear() { + throw new NotSupportedException( + "Clearing is not supported by the read-only List" + ); + } + + /// Adds an item to the end of the List + /// Item that will be added to the List + int IList.Add(object value) { + throw new NotSupportedException( + "Adding items is not supported by the read-only List" + ); + } + + /// Determines whether the List contains the specified item + /// Item that will be checked for + /// True if the specified item is contained in the List + bool IList.Contains(object value) { + return this.objectList.Contains(value); + } + + /// Retrieves the index of an item within the List + /// Item whose index will be returned + /// The zero-based index of the specified item in the List + int IList.IndexOf(object value) { + return this.objectList.IndexOf(value); + } + + /// Inserts an item into the List + /// Zero-based index before which the item will be inserted + /// Item that will be inserted into the List + void IList.Insert(int index, object value) { + throw new NotSupportedException( + "Inserting items is not supported by the read-only List" + ); + } + + /// Whether the size of the List is fixed + bool IList.IsFixedSize { + get { throw new NotImplementedException(); } + } + + /// Removes the specified item from the List + /// Item that will be removed from the List + /// True of the specified item was found in the List and removed + void IList.Remove(object value) { + throw new NotSupportedException( + "Removing items is not supported by the read-only List" + ); + } + + /// Removes an item from the list + /// Zero-based index of the item that will be removed + void IList.RemoveAt(int index) { + throw new NotSupportedException( + "Removing items is not supported by the read-only List" + ); + } + + /// Accesses the List item with the specified index + /// Zero-based index of the List item that will be accessed + 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 + + /// Copies the contents of the List into an array + /// Array the List will be copied into + /// + /// Starting index at which to begin filling the destination array + /// + void ICollection.CopyTo(Array array, int index) { + this.objectList.CopyTo(array, index); + } + + /// Whether the List is synchronized for multi-threaded usage + bool ICollection.IsSynchronized { + get { return this.objectList.IsSynchronized; } + } + + /// Synchronization root on which the List locks + object ICollection.SyncRoot { + get { return this.objectList.SyncRoot; } + } + + #endregion + + /// The wrapped List under its type-safe interface + private IList typedList; + /// The wrapped List under its object interface + private IList objectList; + + } + +} // namespace Nuclex.Support.Collections diff --git a/Source/Collections/ReverseComparer.cs b/Source/Collections/ReverseComparer.cs new file mode 100644 index 0000000..12076c2 --- /dev/null +++ b/Source/Collections/ReverseComparer.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; + +namespace Nuclex.Support.Collections { + + /// + /// Compares two values in reverse or reverses the output of another comparer + /// + /// Type of values to be compared + public class ReverseComparer : IComparer { + + /// Initializes a new reverse comparer + public ReverseComparer() : this(Comparer.Default) { } + + /// + /// Initializes the comparer to provide the inverse results of another comparer + /// + /// Comparer whose results will be inversed + public ReverseComparer(IComparer comparerToReverse) { + this.comparer = comparerToReverse; + } + + /// Compares the left value to the right value + /// Value on the left side + /// Value on the right side + /// The relationship of the two values + public int Compare(ComparedType left, ComparedType right) { + return this.comparer.Compare(right, left); // intentionally reversed + } + + /// The default comparer from the .NET framework + private IComparer comparer; + + } + +} // namespace Nuclex.Support.Collections diff --git a/Source/Collections/RingMemoryStream.cs b/Source/Collections/RingMemoryStream.cs index a443990..aff97ed 100644 --- a/Source/Collections/RingMemoryStream.cs +++ b/Source/Collections/RingMemoryStream.cs @@ -25,7 +25,7 @@ namespace Nuclex.Support.Collections { /// Specialized memory stream for ring buffers /// - /// This ring buffer class is specialized for binary data and attempts to achieve + /// This ring buffer class is specialized for binary data and tries to achieve /// optimal efficiency when storing and retrieving chunks of several bytes /// at once. Typical use cases include audio and network buffers where one party /// is responsible for refilling the buffer at regular intervals while the other @@ -50,10 +50,11 @@ namespace Nuclex.Support.Collections { get { return this.ringBuffer.Length; } set { int length = (int)Length; - if(value < length) + if(value < length) { throw new ArgumentOutOfRangeException( - "New capacity is less than the stream's length" + "New capacity is less than the stream's current length" ); + } // This could be done in a more efficient manner than just replacing // the entire buffer, but since this operation will probably be only called @@ -62,8 +63,9 @@ namespace Nuclex.Support.Collections { MemoryStream newBuffer = new MemoryStream((int)value); newBuffer.SetLength(value); - if(length > 0) + if(length > 0) { Read(newBuffer.GetBuffer(), 0, length); + } this.ringBuffer.Close(); // Equals dispose of the old buffer this.ringBuffer = newBuffer; @@ -119,12 +121,13 @@ namespace Nuclex.Support.Collections { this.ringBuffer.Read(buffer, offset, count); this.startIndex += count; - if(this.startIndex == this.endIndex) + if(this.startIndex == this.endIndex) { setEmpty(); + } } - // The end index lies before the start index, so the data in the - // ring memory stream is fragmented. Example: |#####>-------<#####| + // The end index lies before the start index, so the data in the + // ring memory stream is fragmented. Example: |#####>-------<#####| } else { int linearAvailable = (int)this.ringBuffer.Length - this.startIndex; @@ -142,8 +145,8 @@ namespace Nuclex.Support.Collections { this.startIndex = count - linearAvailable; this.ringBuffer.Read(buffer, offset + linearAvailable, this.startIndex); - // Nope, the amount of requested data can be read in one piece without - // crossing the end of the ring buffer + // Nope, the amount of requested data can be read in one piece without + // crossing the end of the ring buffer } else { this.ringBuffer.Position = this.startIndex; this.ringBuffer.Read(buffer, offset, count); @@ -151,8 +154,9 @@ namespace Nuclex.Support.Collections { } - if(this.startIndex == this.endIndex) + if(this.startIndex == this.endIndex) { setEmpty(); + } } return count; @@ -183,8 +187,8 @@ namespace Nuclex.Support.Collections { this.endIndex = count - linearAvailable; this.ringBuffer.Write(buffer, offset + linearAvailable, this.endIndex); - // All data can be appended at the current stream position without - // crossing the ring memory stream's end + // All data can be appended at the current stream position without + // crossing the ring memory stream's end } else { this.ringBuffer.Position = this.endIndex; this.ringBuffer.Write(buffer, offset, count); @@ -193,9 +197,9 @@ namespace Nuclex.Support.Collections { this.empty = false; - // The end index lies before the start index, so the data in the ring memory - // stream has been fragmented. This means the gap into which we are about - // to write is not fragmented. Example: |#####>-------<#####| + // The end index lies before the start index, so the data in the ring memory + // stream has been fragmented. This means the gap into which we are about + // to write is not fragmented. Example: |#####>-------<#####| } else { if(count > (this.startIndex - this.endIndex)) throw new OverflowException("Data does not fit in buffer");