503 lines
18 KiB
C#
503 lines
18 KiB
C#
//------------------------------------------------------------------------------
|
|
// <copyright file="OrderedDictionary.cs" company="Microsoft">
|
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
|
// </copyright>
|
|
//------------------------------------------------------------------------------
|
|
|
|
namespace System.Collections.Specialized {
|
|
|
|
using System;
|
|
using System.Collections;
|
|
using System.Diagnostics.CodeAnalysis;
|
|
using System.Runtime.Serialization;
|
|
using System.Security.Permissions;
|
|
|
|
/// <devdoc>
|
|
/// <para>
|
|
/// OrderedDictionary offers IDictionary syntax with ordering. Objects
|
|
/// added or inserted in an IOrderedDictionary must have both a key and an index, and
|
|
/// can be retrieved by either.
|
|
/// OrderedDictionary is used by the ParameterCollection because MSAccess relies on ordering of
|
|
/// parameters, while almost all other DBs do not. DataKeyArray also uses it so
|
|
/// DataKeys can be retrieved by either their name or their index.
|
|
///
|
|
/// OrderedDictionary implements IDeserializationCallback because it needs to have the
|
|
/// contained ArrayList and Hashtable deserialized before it tries to get its count and objects.
|
|
/// </para>
|
|
/// </devdoc>
|
|
[Serializable]
|
|
public class OrderedDictionary : IOrderedDictionary, ISerializable, IDeserializationCallback {
|
|
|
|
private ArrayList _objectsArray;
|
|
private Hashtable _objectsTable;
|
|
private int _initialCapacity;
|
|
private IEqualityComparer _comparer;
|
|
private bool _readOnly;
|
|
private Object _syncRoot;
|
|
private SerializationInfo _siInfo; //A temporary variable which we need during deserialization.
|
|
|
|
private const string KeyComparerName = "KeyComparer";
|
|
private const string ArrayListName = "ArrayList";
|
|
private const string ReadOnlyName = "ReadOnly";
|
|
private const string InitCapacityName = "InitialCapacity";
|
|
|
|
public OrderedDictionary() : this(0) {
|
|
}
|
|
|
|
public OrderedDictionary(int capacity) : this(capacity, null) {
|
|
}
|
|
|
|
public OrderedDictionary(IEqualityComparer comparer) : this(0, comparer) {
|
|
}
|
|
|
|
public OrderedDictionary(int capacity, IEqualityComparer comparer) {
|
|
_initialCapacity = capacity;
|
|
_comparer = comparer;
|
|
}
|
|
|
|
private OrderedDictionary(OrderedDictionary dictionary) {
|
|
if (dictionary == null) {
|
|
throw new ArgumentNullException("dictionary");
|
|
}
|
|
|
|
_readOnly = true;
|
|
_objectsArray = dictionary._objectsArray;
|
|
_objectsTable = dictionary._objectsTable;
|
|
_comparer = dictionary._comparer;
|
|
_initialCapacity = dictionary._initialCapacity;
|
|
}
|
|
|
|
protected OrderedDictionary(SerializationInfo info, StreamingContext context) {
|
|
// We can't do anything with the keys and values until the entire graph has been deserialized
|
|
// and getting Counts and objects won't fail. For the time being, we'll just cache this.
|
|
// The graph is not valid until OnDeserialization has been called.
|
|
_siInfo = info;
|
|
}
|
|
|
|
/// <devdoc>
|
|
/// Gets the size of the table.
|
|
/// </devdoc>
|
|
public int Count {
|
|
get {
|
|
return objectsArray.Count;
|
|
}
|
|
}
|
|
|
|
/// <devdoc>
|
|
/// Indicates that the collection can grow.
|
|
/// </devdoc>
|
|
bool IDictionary.IsFixedSize {
|
|
get {
|
|
return _readOnly;
|
|
}
|
|
}
|
|
|
|
/// <devdoc>
|
|
/// Indicates that the collection is not read-only
|
|
/// </devdoc>
|
|
public bool IsReadOnly {
|
|
get {
|
|
return _readOnly;
|
|
}
|
|
}
|
|
|
|
/// <devdoc>
|
|
/// Indicates that this class is not synchronized
|
|
/// </devdoc>
|
|
bool ICollection.IsSynchronized {
|
|
get {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/// <devdoc>
|
|
/// Gets the collection of keys in the table in order.
|
|
/// </devdoc>
|
|
public ICollection Keys {
|
|
get {
|
|
return new OrderedDictionaryKeyValueCollection(objectsArray, true);
|
|
}
|
|
}
|
|
|
|
private ArrayList objectsArray {
|
|
get {
|
|
if (_objectsArray == null) {
|
|
_objectsArray = new ArrayList(_initialCapacity);
|
|
}
|
|
return _objectsArray;
|
|
}
|
|
}
|
|
|
|
private Hashtable objectsTable {
|
|
get {
|
|
if (_objectsTable == null) {
|
|
_objectsTable = new Hashtable(_initialCapacity, _comparer);
|
|
}
|
|
return _objectsTable;
|
|
}
|
|
}
|
|
|
|
/// <devdoc>
|
|
/// The SyncRoot object. Not used because IsSynchronized is false
|
|
/// </devdoc>
|
|
object ICollection.SyncRoot {
|
|
get {
|
|
if (_syncRoot == null) {
|
|
System.Threading.Interlocked.CompareExchange(ref _syncRoot, new Object(), null);
|
|
}
|
|
return _syncRoot;
|
|
}
|
|
}
|
|
|
|
/// <devdoc>
|
|
/// Gets or sets the object at the specified index
|
|
/// </devdoc>
|
|
public object this[int index] {
|
|
get {
|
|
return ((DictionaryEntry)objectsArray[index]).Value;
|
|
}
|
|
set {
|
|
if (_readOnly) {
|
|
throw new NotSupportedException(SR.GetString(SR.OrderedDictionary_ReadOnly));
|
|
}
|
|
if (index < 0 || index >= objectsArray.Count) {
|
|
throw new ArgumentOutOfRangeException("index");
|
|
}
|
|
object key = ((DictionaryEntry)objectsArray[index]).Key;
|
|
objectsArray[index] = new DictionaryEntry(key, value);
|
|
objectsTable[key] = value;
|
|
}
|
|
}
|
|
|
|
/// <devdoc>
|
|
/// Gets or sets the object with the specified key
|
|
/// </devdoc>
|
|
public object this[object key] {
|
|
get {
|
|
return objectsTable[key];
|
|
}
|
|
set {
|
|
if (_readOnly) {
|
|
throw new NotSupportedException(SR.GetString(SR.OrderedDictionary_ReadOnly));
|
|
}
|
|
if (objectsTable.Contains(key)) {
|
|
objectsTable[key] = value;
|
|
objectsArray[IndexOfKey(key)] = new DictionaryEntry(key, value);
|
|
}
|
|
else {
|
|
Add(key, value);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <devdoc>
|
|
/// Returns an arrayList of the values in the table
|
|
/// </devdoc>
|
|
public ICollection Values {
|
|
get {
|
|
return new OrderedDictionaryKeyValueCollection(objectsArray, false);
|
|
}
|
|
}
|
|
|
|
/// <devdoc>
|
|
/// Adds a new entry to the table with the lowest-available index.
|
|
/// </devdoc>
|
|
public void Add(object key, object value) {
|
|
if (_readOnly) {
|
|
throw new NotSupportedException(SR.GetString(SR.OrderedDictionary_ReadOnly));
|
|
}
|
|
objectsTable.Add(key, value);
|
|
objectsArray.Add(new DictionaryEntry(key, value));
|
|
}
|
|
|
|
/// <devdoc>
|
|
/// Clears all elements in the table.
|
|
/// </devdoc>
|
|
public void Clear() {
|
|
if (_readOnly) {
|
|
throw new NotSupportedException(SR.GetString(SR.OrderedDictionary_ReadOnly));
|
|
}
|
|
objectsTable.Clear();
|
|
objectsArray.Clear();
|
|
}
|
|
|
|
/// <devdoc>
|
|
/// Returns a readonly OrderedDictionary for the given OrderedDictionary.
|
|
/// </devdoc>
|
|
public OrderedDictionary AsReadOnly() {
|
|
return new OrderedDictionary(this);
|
|
}
|
|
|
|
/// <devdoc>
|
|
/// Returns true if the key exists in the table, false otherwise.
|
|
/// </devdoc>
|
|
public bool Contains(object key) {
|
|
return objectsTable.Contains(key);
|
|
}
|
|
|
|
/// <devdoc>
|
|
/// Copies the table to an array. This will not preserve order.
|
|
/// </devdoc>
|
|
public void CopyTo(Array array, int index) {
|
|
objectsTable.CopyTo(array, index);
|
|
}
|
|
|
|
private int IndexOfKey(object key) {
|
|
for (int i = 0; i < objectsArray.Count; i++) {
|
|
object o = ((DictionaryEntry)objectsArray[i]).Key;
|
|
if (_comparer != null) {
|
|
if (_comparer.Equals(o, key)) {
|
|
return i;
|
|
}
|
|
}
|
|
else {
|
|
if (o.Equals(key)) {
|
|
return i;
|
|
}
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
/// <devdoc>
|
|
/// Inserts a new object at the given index with the given key.
|
|
/// </devdoc>
|
|
public void Insert(int index, object key, object value) {
|
|
if (_readOnly) {
|
|
throw new NotSupportedException(SR.GetString(SR.OrderedDictionary_ReadOnly));
|
|
}
|
|
if (index > Count || index < 0) {
|
|
throw new ArgumentOutOfRangeException("index");
|
|
}
|
|
objectsTable.Add(key, value);
|
|
objectsArray.Insert(index, new DictionaryEntry(key, value));
|
|
}
|
|
|
|
/// <devdoc>
|
|
/// Handles the deserization event. This method can be overridden.
|
|
/// </devdoc>
|
|
protected virtual void OnDeserialization(object sender) {
|
|
if (_siInfo == null) {
|
|
throw new SerializationException(SR.GetString(SR.Serialization_InvalidOnDeser));
|
|
}
|
|
_comparer = (IEqualityComparer)_siInfo.GetValue(KeyComparerName, typeof(IEqualityComparer));
|
|
_readOnly = _siInfo.GetBoolean(ReadOnlyName);
|
|
_initialCapacity = _siInfo.GetInt32(InitCapacityName);
|
|
|
|
object[] serArray = (object[])_siInfo.GetValue(ArrayListName, typeof(object[]));
|
|
|
|
if (serArray != null) {
|
|
foreach (object o in serArray) {
|
|
DictionaryEntry entry;
|
|
try {
|
|
// DictionaryEntry is a value type, so it can only be casted.
|
|
entry = (DictionaryEntry)o;
|
|
}
|
|
catch {
|
|
throw new SerializationException(SR.GetString(SR.OrderedDictionary_SerializationMismatch));
|
|
}
|
|
objectsArray.Add(entry);
|
|
objectsTable.Add(entry.Key, entry.Value);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <devdoc>
|
|
/// Removes the entry at the given index.
|
|
/// </devdoc>
|
|
public void RemoveAt(int index) {
|
|
if (_readOnly) {
|
|
throw new NotSupportedException(SR.GetString(SR.OrderedDictionary_ReadOnly));
|
|
}
|
|
if (index >= Count || index < 0) {
|
|
throw new ArgumentOutOfRangeException("index");
|
|
}
|
|
object key = ((DictionaryEntry)objectsArray[index]).Key;
|
|
objectsArray.RemoveAt(index);
|
|
objectsTable.Remove(key);
|
|
}
|
|
|
|
/// <devdoc>
|
|
/// Removes the entry with the given key.
|
|
/// </devdoc>
|
|
public void Remove(object key) {
|
|
if (_readOnly) {
|
|
throw new NotSupportedException(SR.GetString(SR.OrderedDictionary_ReadOnly));
|
|
}
|
|
if (key == null) {
|
|
throw new ArgumentNullException("key");
|
|
}
|
|
|
|
int index = IndexOfKey(key);
|
|
if (index < 0) {
|
|
return;
|
|
}
|
|
|
|
objectsTable.Remove(key);
|
|
objectsArray.RemoveAt(index);
|
|
}
|
|
|
|
#region IDictionary implementation
|
|
/// <internalonly/>
|
|
public virtual IDictionaryEnumerator GetEnumerator() {
|
|
return new OrderedDictionaryEnumerator(objectsArray, OrderedDictionaryEnumerator.DictionaryEntry);
|
|
}
|
|
#endregion
|
|
|
|
#region IEnumerable implementation
|
|
/// <internalonly/>
|
|
IEnumerator IEnumerable.GetEnumerator() {
|
|
return new OrderedDictionaryEnumerator(objectsArray, OrderedDictionaryEnumerator.DictionaryEntry);
|
|
}
|
|
#endregion
|
|
|
|
#region ISerializable implementation
|
|
[SuppressMessage("Microsoft.Security", "CA2123:OverrideLinkDemandsShouldBeIdenticalToBase",
|
|
Justification="mscorlib removed all link demands, so don't warn when overriding a method " +
|
|
"with no link demand. Replace the LinkDemand with a SecurityCritical" +
|
|
"annotation when the assembly is moved to the v4 security model.")]
|
|
[SecurityPermissionAttribute(SecurityAction.LinkDemand, Flags=SecurityPermissionFlag.SerializationFormatter)]
|
|
public virtual void GetObjectData(SerializationInfo info, StreamingContext context) {
|
|
if (info == null) {
|
|
throw new ArgumentNullException("info");
|
|
}
|
|
info.AddValue(KeyComparerName, _comparer, typeof(IEqualityComparer));
|
|
info.AddValue(ReadOnlyName, _readOnly);
|
|
info.AddValue(InitCapacityName, _initialCapacity);
|
|
object[] serArray = new object[Count];
|
|
_objectsArray.CopyTo(serArray);
|
|
info.AddValue(ArrayListName, serArray);
|
|
}
|
|
#endregion
|
|
|
|
#region IDeserializationCallback implementation
|
|
void IDeserializationCallback.OnDeserialization(object sender) {
|
|
OnDeserialization(sender);
|
|
}
|
|
#endregion
|
|
|
|
/// <devdoc>
|
|
/// OrderedDictionaryEnumerator works just like any other IDictionaryEnumerator, but it retrieves DictionaryEntries
|
|
/// in the order by index.
|
|
/// </devdoc>
|
|
private class OrderedDictionaryEnumerator : IDictionaryEnumerator {
|
|
|
|
private int _objectReturnType;
|
|
internal const int Keys = 1;
|
|
internal const int Values = 2;
|
|
internal const int DictionaryEntry = 3;
|
|
private IEnumerator arrayEnumerator;
|
|
|
|
internal OrderedDictionaryEnumerator (ArrayList array, int objectReturnType) {
|
|
arrayEnumerator = array.GetEnumerator();
|
|
_objectReturnType = objectReturnType;
|
|
}
|
|
|
|
/// <devdoc>
|
|
/// Retrieves the current DictionaryEntry. This is the same as Entry, but not strongly-typed.
|
|
/// </devdoc>
|
|
public object Current {
|
|
get {
|
|
if (_objectReturnType == Keys) {
|
|
return ((DictionaryEntry)arrayEnumerator.Current).Key;
|
|
}
|
|
if (_objectReturnType == Values) {
|
|
return ((DictionaryEntry)arrayEnumerator.Current).Value;
|
|
}
|
|
return Entry;
|
|
}
|
|
}
|
|
|
|
/// <devdoc>
|
|
/// Retrieves the current DictionaryEntry
|
|
/// </devdoc>
|
|
public DictionaryEntry Entry {
|
|
get {
|
|
return new DictionaryEntry(((DictionaryEntry)arrayEnumerator.Current).Key, ((DictionaryEntry)arrayEnumerator.Current).Value);
|
|
}
|
|
}
|
|
|
|
/// <devdoc>
|
|
/// Retrieves the key of the current DictionaryEntry
|
|
/// </devdoc>
|
|
public object Key {
|
|
get {
|
|
return ((DictionaryEntry)arrayEnumerator.Current).Key;
|
|
}
|
|
}
|
|
|
|
/// <devdoc>
|
|
/// Retrieves the value of the current DictionaryEntry
|
|
/// </devdoc>
|
|
public object Value {
|
|
get {
|
|
return ((DictionaryEntry)arrayEnumerator.Current).Value;
|
|
}
|
|
}
|
|
|
|
/// <devdoc>
|
|
/// Moves the enumerator pointer to the next member
|
|
/// </devdoc>
|
|
public bool MoveNext() {
|
|
return arrayEnumerator.MoveNext();
|
|
}
|
|
|
|
/// <devdoc>
|
|
/// Resets the enumerator pointer to the beginning.
|
|
/// </devdoc>
|
|
public void Reset() {
|
|
arrayEnumerator.Reset();
|
|
}
|
|
}
|
|
|
|
/// <devdoc>
|
|
/// OrderedDictionaryKeyValueCollection implements a collection for the Values and Keys properties
|
|
/// that is "live"- it will reflect changes to the OrderedDictionary on the collection made after the getter
|
|
/// was called.
|
|
/// </devdoc>
|
|
private class OrderedDictionaryKeyValueCollection : ICollection {
|
|
private ArrayList _objects;
|
|
bool isKeys;
|
|
|
|
public OrderedDictionaryKeyValueCollection(ArrayList array, bool isKeys) {
|
|
this._objects = array;
|
|
this.isKeys = isKeys;
|
|
}
|
|
|
|
void ICollection.CopyTo(Array array, int index) {
|
|
if (array==null)
|
|
throw new ArgumentNullException("array");
|
|
if (index < 0)
|
|
throw new ArgumentOutOfRangeException("index");
|
|
foreach (object o in _objects) {
|
|
array.SetValue(isKeys ? ((DictionaryEntry)o).Key : ((DictionaryEntry)o).Value, index);
|
|
index++;
|
|
}
|
|
}
|
|
|
|
int ICollection.Count {
|
|
get {
|
|
return _objects.Count;
|
|
}
|
|
}
|
|
|
|
bool ICollection.IsSynchronized {
|
|
get {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
object ICollection.SyncRoot {
|
|
get {
|
|
return _objects.SyncRoot;
|
|
}
|
|
}
|
|
|
|
IEnumerator IEnumerable.GetEnumerator() {
|
|
return new OrderedDictionaryEnumerator(_objects, isKeys == true ? OrderedDictionaryEnumerator.Keys : OrderedDictionaryEnumerator.Values);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|