学英语最苦恼的事情是每个单词都认识,连在一起却不知道是什么意思;读源代码最苦恼的事情是每一行代码都能看得懂,却不知道它们是如何一起完成工作的。这里的关键都是对整体的把握。人脑的特点是有很强的逻辑思维、抽象思维能力和丰富的想象力——以及很差的记忆力,无法同时记住并关注大量的对象,所以在阅读成千上万行代码的时候就难免看了后面忘了前面。正因为如此,我们在进行一项复杂的工作的时候,就必须扬长避短,把复杂的事物划分成不同级别的抽象层次。
对于泛型 Dictionary 的源代码,可以分成“原理和概念模型——实现模型——实现细节”这3个不同的抽象层次。
Dictionary 的原理和概念模型
Dictionary 是使用除法散列法计算存储地址,在存储地址重复时使用链接法处理碰撞。之所以使用数组+链表的搭配是为了服务于“查找、添加、删除操作都是 O(1) 复杂度”的目标。关于除法散列法和链接法在 6.1 有详细的描述,不再赘述。下图是一个实例:
Dictionary 的实现模型
上面的概念模型虽然简单明了,但是有点浪费空间,因为不管有没有碰撞,buckets 的每个槽都指向一个链表,相当多的链表里其实都只有一个项,比较浪费。另外,链表是一个一个地添加新节点而不是一次申请一大块空间,无论是申请内存还是垃圾回收效率都不高。
所以 Dictionary 的实现使用了一种更为高效的做法:将每一项添加到名为 entries 的数组里,entries 数组的每个槽含有一个名为 Next 的整形变量保存碰撞链的下一个槽的下标,此乃数组模拟单向链表大法。与概念模型一样,数组 buckets 负责保存每个 Key 的散列值所对应的存储单元的地址,只不过在概念模型里 buckets 的每个槽保存的是一个链表的地址,在实现模型里 buckets 的每个槽保存的是 entries 数组的一个下标,如下图所示。
Dictionary 的实现细节
如果这时再向字典里添加一个 Key = 50,首先要把它存放到 entries[5] 里,即 entries[5].Key = 50。然后计算 50 的散列值,50 % 11 = 6,要执行 buckets[6] = 5,但是 buckets[6] 里面已经有值了,亦即发生了碰撞,这时要先把 buckets[6] 里的下标存放到 entries[5].Next 里,即 entries[5].Next = buckets[6],然后才能把 buckets[6] 的值改成5,如下图所示。
如果想要查找 Key = 39 那一项,要先计算它的散列值为 39 % 11 = 6,然后由 buckets[6] 取得 entries 下标 5,然后判断 entries[5].Key 是不是 39,如果不是,由 entries[5].Next 取得碰撞链的下一个槽的地址 4,再判断 entries[4].Key 是不是 39,如果是的话就找到了这一项。
删除的时候有些麻烦。我们有两个选择:“物理”删除和“逻辑”删除。以删除 Key = 15 那一项为例。“物理”删除是指让 entries[1] = {17, -1}、entries[2] = {28, 1}、entries[3] = {39, 2}、entries[4] = {50, 3}、 entries[5] = {0, -1},让 buckets[4] = -1、buckets[6] = 4。“逻辑”删除是指让 entries[1].hashCode = -1 表示已删除(hashCode 字段我没有在上图画出来),让 buckets[4] = -1。显然,只有“逻辑”删除才能保证 O(1) 的复杂度。但是“逻辑”删除会使得 entries 数组不连续,所以每次添加新项的时候,都应该优先添加到因被删除而空出来的槽里。但是怎么才能知道哪些槽是因被删除而空出来的槽呢?不能通过遍历 entries 数组的方法,那是 O(n) 复杂度的操作。所以要为 Dictionary 增加一个整型变量 freeList,用来保存最后一个因删除而空出来的槽的下标,然后,让最后一个因删除而空出来的槽的 Next 属性保存倒数第二个因删除而空出来的槽的下标,由此形成一个删除链,Next 属性是两用的!此外,还需要为 Dictionary 增加一个整型变量 freeCount,用来保存因被删除而空出来的槽的数量,每次添加新项的时候,如果发现 freeCount > 0 就应该把新项添加到因删除而空出来的槽里。下图演示删除 Key = 15 和 Key = 39 这两项之后的状态,蓝色箭头表示碰撞链,红色箭头表示删除链。
一点题外话:此前,我一直都以为 Dictionary 可以保持添加时的顺序,刚刚翻了下 MSDN,原来人家压根没这么保证过!如果只添加不删除的话,确实恰巧可以保持添加时的顺序不变。但是如果删除过,这个顺序就没有保证了!这一点和 List 是不同的,使用时应注意不要让程序依赖于 Dictionary 的顺序。
这时候,如果再要添加一项,就应该添加到删除链的第一个空槽也就是 entries[4] 里面。当然,在添加之前还要判断是否发生了碰撞,如果发生了碰撞,要把新项加入到碰撞链上。
Dictionary 还有一个整型变量 count,用来记录一共已经添加了多少项到 Dictionary 之中。一方面,用户可以通过它获取实际添加到 Dictionary 的项的数量。另一方面,当 freeCount = 0 也就是删除链已被填满时,entries[count] 正好就是下一个空槽。
说到这里,虽然还没有涵盖所有的实现细节,但是已经到了“做的人很爽,听的人未必”的地方了。再唠叨更多的细节,只会让人烦闷。如果您有那个闲情逸致的话,不妨立即动手自己实现一个 MyDictionary,然后把您的实现代码与微软的代码比较比较,看看无论从思维的严密程度和逻辑性,还是从程序的可读性(当然像基础算法这种几百年也不改一次的代码可读性往往要让位于榨取那几毫秒的性能)和编码风格,以及性能上到底有哪些不同。
最后,附上 .net framework4 源代码里的 Dictionary.cs,您可以先偷偷瞄上几眼,再做打算 : )
// ==++==
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// ==--==
/*============================================================
**
** Class: Dictionary
**
** <OWNER>[....]</OWNER>
**
** Purpose: Generic hash table implementation
**
** #DictionaryVersusHashtableThreadSafety
** Hashtable has multiple reader/single writer (MR/SW) thread safety built into
** certain methods and properties, whereas Dictionary doesn't. If you're
** converting framework code that formerly used Hashtable to Dictionary, it's
** important to consider whether callers may have taken a dependence on MR/SW
** thread safety. If a reader writer lock is available, then that may be used
** with a Dictionary to get the same thread safety guarantee.
**
** Reader writer locks don't exist in silverlight, so we do the following as a
** result of removing non-generic collections from silverlight:
** 1. If the Hashtable was fully synchronized, then we replace it with a
** Dictionary with full locks around reads/writes (same thread safety
** guarantee).
** 2. Otherwise, the Hashtable has the default MR/SW thread safety behavior,
** so we do one of the following on a case-by-case basis:
** a. If the ---- can be addressed by rearranging the code and using a temp
** variable (for example, it's only populated immediately after created)
** then we address the ---- this way and use Dictionary.
** b. If there's concern about degrading performance with the increased
** locking, we ifdef with FEATURE_NONGENERIC_COLLECTIONS so we can at
** least use Hashtable in the desktop build, but Dictionary with full
** locks in silverlight builds. Note that this is heavier locking than
** MR/SW, but this is the only option without rewriting (or adding back)
** the reader writer lock.
** c. If there's no performance concern (e.g. debug-only code) we
** consistently replace Hashtable with Dictionary plus full locks to
** reduce complexity.
** d. Most of serialization is dead code in silverlight. Instead of updating
** those Hashtable occurences in serialization, we carved out references
** to serialization such that this code doesn't need to build in
** silverlight.
===========================================================*/
namespace System.Collections.Generic {
using System;
using System.Collections;
using System.Diagnostics;
using System.Diagnostics.Contracts;
using System.Runtime.Serialization;
using System.Security.Permissions;
[DebuggerTypeProxy(typeof(Mscorlib_DictionaryDebugView<,>))]
[DebuggerDisplay("Count = {Count}")]
[Serializable]
[System.Runtime.InteropServices.ComVisible(false)]
public class Dictionary<TKey,TValue>: IDictionary<TKey,TValue>, IDictionary, ISerializable, IDeserializationCallback {
private struct Entry {
public int hashCode; // Lower 31 bits of hash code, -1 if unused
public int next; // Index of next entry, -1 if last
public TKey key; // Key of entry
public TValue value; // Value of entry
}
private int[] buckets;
private Entry[] entries;
private int count;
private int version;
private int freeList;
private int freeCount;
private IEqualityComparer<TKey> comparer;
private KeyCollection keys;
private ValueCollection values;
private Object _syncRoot;
private SerializationInfo m_siInfo; //A temporary variable which we need during deserialization.
// constants for serialization
private const String VersionName = "Version";
private const String HashSizeName = "HashSize"; // Must save buckets.Length
private const String KeyValuePairsName = "KeyValuePairs";
private const String ComparerName = "Comparer";
public Dictionary(): this(0, null) {}
public Dictionary(int capacity): this(capacity, null) {}
public Dictionary(IEqualityComparer<TKey> comparer): this(0, comparer) {}
public Dictionary(int capacity, IEqualityComparer<TKey> comparer) {
if (capacity < 0) ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.capacity);
if (capacity > 0) Initialize(capacity);
this.comparer = comparer ?? EqualityComparer<TKey>.Default;
}
public Dictionary(IDictionary<TKey,TValue> dictionary): this(dictionary, null) {}
public Dictionary(IDictionary<TKey,TValue> dictionary, IEqualityComparer<TKey> comparer):
this(dictionary != null? dictionary.Count: 0, comparer) {
if( dictionary == null) {
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.dictionary);
}
foreach (KeyValuePair<TKey,TValue> pair in dictionary) {
Add(pair.Key, pair.Value);
}
}
protected Dictionary(SerializationInfo info, StreamingContext context) {
//We can't do anything with the keys and values until the entire graph has been deserialized
//and we have a resonable estimate that GetHashCode is not going to fail. For the time being,
//we'll just cache this. The graph is not valid until OnDeserialization has been called.
m_siInfo = info;
}
public IEqualityComparer<TKey> Comparer {
get {
return comparer;
}
}
public int Count {
get { return count - freeCount; }
}
public KeyCollection Keys {
get {
Contract.Ensures(Contract.Result<KeyCollection>() != null);
if (keys == null) keys = new KeyCollection(this);
return keys;
}
}
ICollection<TKey> IDictionary<TKey, TValue>.Keys {
get {
if (keys == null) keys = new KeyCollection(this);
return keys;
}
}
public ValueCollection Values {
get {
Contract.Ensures(Contract.Result<ValueCollection>() != null);
if (values == null) values = new ValueCollection(this);
return values;
}
}
ICollection<TValue> IDictionary<TKey, TValue>.Values {
get {
if (values == null) values = new ValueCollection(this);
return values;
}
}
public TValue this[TKey key] {
get {
int i = FindEntry(key);
if (i >= 0) return entries[i].value;
ThrowHelper.ThrowKeyNotFoundException();
return default(TValue);
}
set {
Insert(key, value, false);
}
}
public void Add(TKey key, TValue value) {
Insert(key, value, true);
}
void ICollection<KeyValuePair<TKey, TValue>>.Add(KeyValuePair<TKey, TValue> keyValuePair) {
Add(keyValuePair.Key, keyValuePair.Value);
}
bool ICollection<KeyValuePair<TKey, TValue>>.Contains(KeyValuePair<TKey, TValue> keyValuePair) {
int i = FindEntry(keyValuePair.Key);
if( i >= 0 && EqualityComparer<TValue>.Default.Equals(entries[i].value, keyValuePair.Value)) {
return true;
}
return false;
}
bool ICollection<KeyValuePair<TKey, TValue>>.Remove(KeyValuePair<TKey, TValue> keyValuePair) {
int i = FindEntry(keyValuePair.Key);
if( i >= 0 && EqualityComparer<TValue>.Default.Equals(entries[i].value, keyValuePair.Value)) {
Remove(keyValuePair.Key);
return true;
}
return false;
}
public void Clear() {
if (count > 0) {
for (int i = 0; i < buckets.Length; i++) buckets[i] = -1;
Array.Clear(entries, 0, count);
freeList = -1;
count = 0;
freeCount = 0;
version++;
}
}
public bool ContainsKey(TKey key) {
return FindEntry(key) >= 0;
}
public bool ContainsValue(TValue value) {
if (value == null) {
for (int i = 0; i < count; i++) {
if (entries[i].hashCode >= 0 && entries[i].value == null) return true;
}
}
else {
EqualityComparer<TValue> c = EqualityComparer<TValue>.Default;
for (int i = 0; i < count; i++) {
if (entries[i].hashCode >= 0 && c.Equals(entries[i].value, value)) return true;
}
}
return false;
}
// This security annotation is present only because of tool issues. Annotations should be kept in the
// baseline xml files. Basically, don't copy this style of security annotation!
[System.Security.SecuritySafeCritical]
private void CopyTo(KeyValuePair<TKey,TValue>[] array, int index) {
if (array == null) {
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.array);
}
if (index < 0 || index > array.Length ) {
ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.index, ExceptionResource.ArgumentOutOfRange_NeedNonNegNum);
}
if (array.Length - index < Count) {
ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_ArrayPlusOffTooSmall);
}
int count = this.count;
Entry[] entries = this.entries;
for (int i = 0; i < count; i++) {
if (entries[i].hashCode >= 0) {
array[index++] = new KeyValuePair<TKey,TValue>(entries[i].key, entries[i].value);
}
}
}
public Enumerator GetEnumerator() {
return new Enumerator(this, Enumerator.KeyValuePair);
}
IEnumerator<KeyValuePair<TKey, TValue>> IEnumerable<KeyValuePair<TKey, TValue>>.GetEnumerator() {
return new Enumerator(this, Enumerator.KeyValuePair);
}
[System.Security.SecurityCritical] // auto-generated_required
public virtual void GetObjectData(SerializationInfo info, StreamingContext context) {
if (info==null) {
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.info);
}
info.AddValue(VersionName, version);
info.AddValue(ComparerName, comparer, typeof(IEqualityComparer<TKey>));
info.AddValue(HashSizeName, buckets == null ? 0 : buckets.Length); //This is the length of the bucket array.
if( buckets != null) {
KeyValuePair<TKey, TValue>[] array = new KeyValuePair<TKey, TValue>[Count];
CopyTo(array, 0);
info.AddValue(KeyValuePairsName, array, typeof(KeyValuePair<TKey, TValue>[]));
}
}
private int FindEntry(TKey key) {
if( key == null) {
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.key);
}
if (buckets != null) {
int hashCode = comparer.GetHashCode(key) & 0x7FFFFFFF;
for (int i = buckets[hashCode % buckets.Length]; i >= 0; i = entries[i].next) {
if (entries[i].hashCode == hashCode && comparer.Equals(entries[i].key, key)) return i;
}
}
return -1;
}
private void Initialize(int capacity) {
int size = HashHelpers.GetPrime(capacity);
buckets = new int[size];
for (int i = 0; i < buckets.Length; i++) buckets[i] = -1;
entries = new Entry[size];
freeList = -1;
}
private void Insert(TKey key, TValue value, bool add) {
if( key == null ) {
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.key);
}
if (buckets == null) Initialize(0);
int hashCode = comparer.GetHashCode(key) & 0x7FFFFFFF;
int targetBucket = hashCode % buckets.Length;
for (int i = buckets[targetBucket]; i >= 0; i = entries[i].next) {
if (entries[i].hashCode == hashCode && comparer.Equals(entries[i].key, key)) {
if (add) {
ThrowHelper.ThrowArgumentException(ExceptionResource.Argument_AddingDuplicate);
}
entries[i].value = value;
version++;
return;
}
}
int index;
if (freeCount > 0) {
index = freeList;
freeList = entries[index].next;
freeCount--;
}
else {
if (count == entries.Length)
{
Resize();
targetBucket = hashCode % buckets.Length;
}
index = count;
count++;
}
entries[index].hashCode = hashCode;
entries[index].next = buckets[targetBucket];
entries[index].key = key;
entries[index].value = value;
buckets[targetBucket] = index;
version++;
}
public virtual void OnDeserialization(Object sender) {
if (m_siInfo==null) {
// It might be necessary to call OnDeserialization from a container if the container object also implements
// OnDeserialization. However, remoting will call OnDeserialization again.
// We can return immediately if this function is called twice.
// Note we set m_siInfo to null at the end of this method.
return;
}
int realVersion = m_siInfo.GetInt32(VersionName);
int hashsize = m_siInfo.GetInt32(HashSizeName);
comparer = (IEqualityComparer<TKey>)m_siInfo.GetValue(ComparerName, typeof(IEqualityComparer<TKey>));
if( hashsize != 0) {
buckets = new int[hashsize];
for (int i = 0; i < buckets.Length; i++) buckets[i] = -1;
entries = new Entry[hashsize];
freeList = -1;
KeyValuePair<TKey, TValue>[] array = (KeyValuePair<TKey, TValue>[])
m_siInfo.GetValue(KeyValuePairsName, typeof(KeyValuePair<TKey, TValue>[]));
if (array==null) {
ThrowHelper.ThrowSerializationException(ExceptionResource.Serialization_MissingKeys);
}
for (int i=0; i<array.Length; i++) {
if ( array[i].Key == null) {
ThrowHelper.ThrowSerializationException(ExceptionResource.Serialization_NullKey);
}
Insert(array[i].Key, array[i].Value, true);
}
}
else {
buckets = null;
}
version = realVersion;
m_siInfo=null;
}
private void Resize() {
int newSize = HashHelpers.GetPrime(count * 2);
int[] newBuckets = new int[newSize];
for (int i = 0; i < newBuckets.Length; i++) newBuckets[i] = -1;
Entry[] newEntries = new Entry[newSize];
Array.Copy(entries, 0, newEntries, 0, count);
for (int i = 0; i < count; i++) {
int bucket = newEntries[i].hashCode % newSize;
newEntries[i].next = newBuckets[bucket];
newBuckets[bucket] = i;
}
buckets = newBuckets;
entries = newEntries;
}
public bool Remove(TKey key) {
if(key == null) {
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.key);
}
if (buckets != null) {
int hashCode = comparer.GetHashCode(key) & 0x7FFFFFFF;
int bucket = hashCode % buckets.Length;
int last = -1;
for (int i = buckets[bucket]; i >= 0; last = i, i = entries[i].next) {
if (entries[i].hashCode == hashCode && comparer.Equals(entries[i].key, key)) {
if (last < 0) {
buckets[bucket] = entries[i].next;
}
else {
entries[last].next = entries[i].next;
}
entries[i].hashCode = -1;
entries[i].next = freeList;
entries[i].key = default(TKey);
entries[i].value = default(TValue);
freeList = i;
freeCount++;
version++;
return true;
}
}
}
return false;
}
public bool TryGetValue(TKey key, out TValue value) {
int i = FindEntry(key);
if (i >= 0) {
value = entries[i].value;
return true;
}
value = default(TValue);
return false;
}
// This is a convenience method for the internal callers that were converted from using Hashtable.
// Many were combining key doesn't exist and key exists but null value (for non-value types) checks.
// This allows them to continue getting that behavior with minimal code delta. This is basically
// TryGetValue without the out param
internal TValue GetValueOrDefault(TKey key) {
int i = FindEntry(key);
if (i >= 0) {
return entries[i].value;
}
return default(TValue);
}
bool ICollection<KeyValuePair<TKey,TValue>>.IsReadOnly {
get { return false; }
}
void ICollection<KeyValuePair<TKey,TValue>>.CopyTo(KeyValuePair<TKey,TValue>[] array, int index) {
CopyTo(array, index);
}
void ICollection.CopyTo(Array array, int index) {
if (array == null) {
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.array);
}
if (array.Rank != 1) {
ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_RankMultiDimNotSupported);
}
if( array.GetLowerBound(0) != 0 ) {
ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_NonZeroLowerBound);
}
if (index < 0 || index > array.Length) {
ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.index, ExceptionResource.ArgumentOutOfRange_NeedNonNegNum);
}
if (array.Length - index < Count) {
ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_ArrayPlusOffTooSmall);
}
KeyValuePair<TKey,TValue>[] pairs = array as KeyValuePair<TKey,TValue>[];
if (pairs != null) {
CopyTo(pairs, index);
}
else if( array is DictionaryEntry[]) {
DictionaryEntry[] dictEntryArray = array as DictionaryEntry[];
Entry[] entries = this.entries;
for (int i = 0; i < count; i++) {
if (entries[i].hashCode >= 0) {
dictEntryArray[index++] = new DictionaryEntry(entries[i].key, entries[i].value);
}
}
}
else {
object[] objects = array as object[];
if (objects == null) {
ThrowHelper.ThrowArgumentException(ExceptionResource.Argument_InvalidArrayType);
}
try {
int count = this.count;
Entry[] entries = this.entries;
for (int i = 0; i < count; i++) {
if (entries[i].hashCode >= 0) {
objects[index++] = new KeyValuePair<TKey,TValue>(entries[i].key, entries[i].value);
}
}
}
catch(ArrayTypeMismatchException) {
ThrowHelper.ThrowArgumentException(ExceptionResource.Argument_InvalidArrayType);
}
}
}
IEnumerator IEnumerable.GetEnumerator() {
return new Enumerator(this, Enumerator.KeyValuePair);
}
bool ICollection.IsSynchronized {
get { return false; }
}
object ICollection.SyncRoot {
get {
if( _syncRoot == null) {
System.Threading.Interlocked.CompareExchange<Object>(ref _syncRoot, new Object(), null);
}
return _syncRoot;
}
}
bool IDictionary.IsFixedSize {
get { return false; }
}
bool IDictionary.IsReadOnly {
get { return false; }
}
ICollection IDictionary.Keys {
get { return (ICollection)Keys; }
}
ICollection IDictionary.Values {
get { return (ICollection)Values; }
}
object IDictionary.this[object key] {
get {
if( IsCompatibleKey(key)) {
int i = FindEntry((TKey)key);
if (i >= 0) {
return entries[i].value;
}
}
return null;
}
set {
if (key == null)
{
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.key);
}
ThrowHelper.IfNullAndNullsAreIllegalThenThrow<TValue>(value, ExceptionArgument.value);
try {
TKey tempKey = (TKey)key;
try {
this[tempKey] = (TValue)value;
}
catch (InvalidCastException) {
ThrowHelper.ThrowWrongValueTypeArgumentException(value, typeof(TValue));
}
}
catch (InvalidCastException) {
ThrowHelper.ThrowWrongKeyTypeArgumentException(key, typeof(TKey));
}
}
}
private static bool IsCompatibleKey(object key) {
if( key == null) {
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.key);
}
return (key is TKey);
}
void IDictionary.Add(object key, object value) {
if (key == null)
{
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.key);
}
ThrowHelper.IfNullAndNullsAreIllegalThenThrow<TValue>(value, ExceptionArgument.value);
try {
TKey tempKey = (TKey)key;
try {
Add(tempKey, (TValue)value);
}
catch (InvalidCastException) {
ThrowHelper.ThrowWrongValueTypeArgumentException(value, typeof(TValue));
}
}
catch (InvalidCastException) {
ThrowHelper.ThrowWrongKeyTypeArgumentException(key, typeof(TKey));
}
}
bool IDictionary.Contains(object key) {
if(IsCompatibleKey(key)) {
return ContainsKey((TKey)key);
}
return false;
}
IDictionaryEnumerator IDictionary.GetEnumerator() {
return new Enumerator(this, Enumerator.DictEntry);
}
void IDictionary.Remove(object key) {
if(IsCompatibleKey(key)) {
Remove((TKey)key);
}
}
[Serializable]
public struct Enumerator: IEnumerator<KeyValuePair<TKey,TValue>>,
IDictionaryEnumerator
{
private Dictionary<TKey,TValue> dictionary;
private int version;
private int index;
private KeyValuePair<TKey,TValue> current;
private int getEnumeratorRetType; // What should Enumerator.Current return?
internal const int DictEntry = 1;
internal const int KeyValuePair = 2;
internal Enumerator(Dictionary<TKey,TValue> dictionary, int getEnumeratorRetType) {
this.dictionary = dictionary;
version = dictionary.version;
index = 0;
this.getEnumeratorRetType = getEnumeratorRetType;
current = new KeyValuePair<TKey, TValue>();
}
public bool MoveNext() {
if (version != dictionary.version) {
ThrowHelper.ThrowInvalidOperationException(ExceptionResource.InvalidOperation_EnumFailedVersion);
}
// Use unsigned comparison since we set index to dictionary.count+1 when the enumeration ends.
// dictionary.count+1 could be negative if dictionary.count is Int32.MaxValue
while ((uint)index < (uint)dictionary.count) {
if (dictionary.entries[index].hashCode >= 0) {
current = new KeyValuePair<TKey, TValue>(dictionary.entries[index].key, dictionary.entries[index].value);
index++;
return true;
}
index++;
}
index = dictionary.count + 1;
current = new KeyValuePair<TKey, TValue>();
return false;
}
public KeyValuePair<TKey,TValue> Current {
get { return current; }
}
public void Dispose() {
}
object IEnumerator.Current {
get {
if( index == 0 || (index == dictionary.count + 1)) {
ThrowHelper.ThrowInvalidOperationException(ExceptionResource.InvalidOperation_EnumOpCantHappen);
}
if (getEnumeratorRetType == DictEntry) {
return new System.Collections.DictionaryEntry(current.Key, current.Value);
} else {
return new KeyValuePair<TKey, TValue>(current.Key, current.Value);
}
}
}
void IEnumerator.Reset() {
if (version != dictionary.version) {
ThrowHelper.ThrowInvalidOperationException(ExceptionResource.InvalidOperation_EnumFailedVersion);
}
index = 0;
current = new KeyValuePair<TKey, TValue>();
}
DictionaryEntry IDictionaryEnumerator.Entry {
get {
if( index == 0 || (index == dictionary.count + 1)) {
ThrowHelper.ThrowInvalidOperationException(ExceptionResource.InvalidOperation_EnumOpCantHappen);
}
return new DictionaryEntry(current.Key, current.Value);
}
}
object IDictionaryEnumerator.Key {
get {
if( index == 0 || (index == dictionary.count + 1)) {
ThrowHelper.ThrowInvalidOperationException(ExceptionResource.InvalidOperation_EnumOpCantHappen);
}
return current.Key;
}
}
object IDictionaryEnumerator.Value {
get {
if( index == 0 || (index == dictionary.count + 1)) {
ThrowHelper.ThrowInvalidOperationException(ExceptionResource.InvalidOperation_EnumOpCantHappen);
}
return current.Value;
}
}
}
[DebuggerTypeProxy(typeof(Mscorlib_DictionaryKeyCollectionDebugView<,>))]
[DebuggerDisplay("Count = {Count}")]
[Serializable]
public sealed class KeyCollection: ICollection<TKey>, ICollection
{
private Dictionary<TKey,TValue> dictionary;
public KeyCollection(Dictionary<TKey,TValue> dictionary) {
if (dictionary == null) {
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.dictionary);
}
this.dictionary = dictionary;
}
public Enumerator GetEnumerator() {
return new Enumerator(dictionary);
}
public void CopyTo(TKey[] array, int index) {
if (array == null) {
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.array);
}
if (index < 0 || index > array.Length) {
ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.index, ExceptionResource.ArgumentOutOfRange_NeedNonNegNum);
}
if (array.Length - index < dictionary.Count) {
ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_ArrayPlusOffTooSmall);
}
int count = dictionary.count;
Entry[] entries = dictionary.entries;
for (int i = 0; i < count; i++) {
if (entries[i].hashCode >= 0) array[index++] = entries[i].key;
}
}
public int Count {
get { return dictionary.Count; }
}
bool ICollection<TKey>.IsReadOnly {
get { return true; }
}
void ICollection<TKey>.Add(TKey item){
ThrowHelper.ThrowNotSupportedException(ExceptionResource.NotSupported_KeyCollectionSet);
}
void ICollection<TKey>.Clear(){
ThrowHelper.ThrowNotSupportedException(ExceptionResource.NotSupported_KeyCollectionSet);
}
bool ICollection<TKey>.Contains(TKey item){
return dictionary.ContainsKey(item);
}
bool ICollection<TKey>.Remove(TKey item){
ThrowHelper.ThrowNotSupportedException(ExceptionResource.NotSupported_KeyCollectionSet);
return false;
}
IEnumerator<TKey> IEnumerable<TKey>.GetEnumerator() {
return new Enumerator(dictionary);
}
IEnumerator IEnumerable.GetEnumerator() {
return new Enumerator(dictionary);
}
void ICollection.CopyTo(Array array, int index) {
if (array==null) {
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.array);
}
if (array.Rank != 1) {
ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_RankMultiDimNotSupported);
}
if( array.GetLowerBound(0) != 0 ) {
ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_NonZeroLowerBound);
}
if (index < 0 || index > array.Length) {
ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.index, ExceptionResource.ArgumentOutOfRange_NeedNonNegNum);
}
if (array.Length - index < dictionary.Count) {
ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_ArrayPlusOffTooSmall);
}
TKey[] keys = array as TKey[];
if (keys != null) {
CopyTo(keys, index);
}
else {
object[] objects = array as object[];
if (objects == null) {
ThrowHelper.ThrowArgumentException(ExceptionResource.Argument_InvalidArrayType);
}
int count = dictionary.count;
Entry[] entries = dictionary.entries;
try {
for (int i = 0; i < count; i++) {
if (entries[i].hashCode >= 0) objects[index++] = entries[i].key;
}
}
catch(ArrayTypeMismatchException) {
ThrowHelper.ThrowArgumentException(ExceptionResource.Argument_InvalidArrayType);
}
}
}
bool ICollection.IsSynchronized {
get { return false; }
}
Object ICollection.SyncRoot {
get { return ((ICollection)dictionary).SyncRoot; }
}
[Serializable]
public struct Enumerator : IEnumerator<TKey>, System.Collections.IEnumerator
{
private Dictionary<TKey, TValue> dictionary;
private int index;
private int version;
private TKey currentKey;
internal Enumerator(Dictionary<TKey, TValue> dictionary) {
this.dictionary = dictionary;
version = dictionary.version;
index = 0;
currentKey = default(TKey);
}
public void Dispose() {
}
public bool MoveNext() {
if (version != dictionary.version) {
ThrowHelper.ThrowInvalidOperationException(ExceptionResource.InvalidOperation_EnumFailedVersion);
}
while ((uint)index < (uint)dictionary.count) {
if (dictionary.entries[index].hashCode >= 0) {
currentKey = dictionary.entries[index].key;
index++;
return true;
}
index++;
}
index = dictionary.count + 1;
currentKey = default(TKey);
return false;
}
public TKey Current {
get {
return currentKey;
}
}
Object System.Collections.IEnumerator.Current {
get {
if( index == 0 || (index == dictionary.count + 1)) {
ThrowHelper.ThrowInvalidOperationException(ExceptionResource.InvalidOperation_EnumOpCantHappen);
}
return currentKey;
}
}
void System.Collections.IEnumerator.Reset() {
if (version != dictionary.version) {
ThrowHelper.ThrowInvalidOperationException(ExceptionResource.InvalidOperation_EnumFailedVersion);
}
index = 0;
currentKey = default(TKey);
}
}
}
[DebuggerTypeProxy(typeof(Mscorlib_DictionaryValueCollectionDebugView<,>))]
[DebuggerDisplay("Count = {Count}")]
[Serializable]
public sealed class ValueCollection: ICollection<TValue>, ICollection
{
private Dictionary<TKey,TValue> dictionary;
public ValueCollection(Dictionary<TKey,TValue> dictionary) {
if (dictionary == null) {
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.dictionary);
}
this.dictionary = dictionary;
}
public Enumerator GetEnumerator() {
return new Enumerator(dictionary);
}
public void CopyTo(TValue[] array, int index) {
if (array == null) {
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.array);
}
if (index < 0 || index > array.Length) {
ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.index, ExceptionResource.ArgumentOutOfRange_NeedNonNegNum);
}
if (array.Length - index < dictionary.Count) {
ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_ArrayPlusOffTooSmall);
}
int count = dictionary.count;
Entry[] entries = dictionary.entries;
for (int i = 0; i < count; i++) {
if (entries[i].hashCode >= 0) array[index++] = entries[i].value;
}
}
public int Count {
get { return dictionary.Count; }
}
bool ICollection<TValue>.IsReadOnly {
get { return true; }
}
void ICollection<TValue>.Add(TValue item){
ThrowHelper.ThrowNotSupportedException(ExceptionResource.NotSupported_ValueCollectionSet);
}
bool ICollection<TValue>.Remove(TValue item){
ThrowHelper.ThrowNotSupportedException(ExceptionResource.NotSupported_ValueCollectionSet);
return false;
}
void ICollection<TValue>.Clear(){
ThrowHelper.ThrowNotSupportedException(ExceptionResource.NotSupported_ValueCollectionSet);
}
bool ICollection<TValue>.Contains(TValue item){
return dictionary.ContainsValue(item);
}
IEnumerator<TValue> IEnumerable<TValue>.GetEnumerator() {
return new Enumerator(dictionary);
}
IEnumerator IEnumerable.GetEnumerator() {
return new Enumerator(dictionary);
}
void ICollection.CopyTo(Array array, int index) {
if (array == null) {
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.array);
}
if (array.Rank != 1) {
ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_RankMultiDimNotSupported);
}
if( array.GetLowerBound(0) != 0 ) {
ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_NonZeroLowerBound);
}
if (index < 0 || index > array.Length) {
ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.index, ExceptionResource.ArgumentOutOfRange_NeedNonNegNum);
}
if (array.Length - index < dictionary.Count)
ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_ArrayPlusOffTooSmall);
TValue[] values = array as TValue[];
if (values != null) {
CopyTo(values, index);
}
else {
object[] objects = array as object[];
if (objects == null) {
ThrowHelper.ThrowArgumentException(ExceptionResource.Argument_InvalidArrayType);
}
int count = dictionary.count;
Entry[] entries = dictionary.entries;
try {
for (int i = 0; i < count; i++) {
if (entries[i].hashCode >= 0) objects[index++] = entries[i].value;
}
}
catch(ArrayTypeMismatchException) {
ThrowHelper.ThrowArgumentException(ExceptionResource.Argument_InvalidArrayType);
}
}
}
bool ICollection.IsSynchronized {
get { return false; }
}
Object ICollection.SyncRoot {
get { return ((ICollection)dictionary).SyncRoot; }
}
[Serializable]
public struct Enumerator : IEnumerator<TValue>, System.Collections.IEnumerator
{
private Dictionary<TKey, TValue> dictionary;
private int index;
private int version;
private TValue currentValue;
internal Enumerator(Dictionary<TKey, TValue> dictionary) {
this.dictionary = dictionary;
version = dictionary.version;
index = 0;
currentValue = default(TValue);
}
public void Dispose() {
}
public bool MoveNext() {
if (version != dictionary.version) {
ThrowHelper.ThrowInvalidOperationException(ExceptionResource.InvalidOperation_EnumFailedVersion);
}
while ((uint)index < (uint)dictionary.count) {
if (dictionary.entries[index].hashCode >= 0) {
currentValue = dictionary.entries[index].value;
index++;
return true;
}
index++;
}
index = dictionary.count + 1;
currentValue = default(TValue);
return false;
}
public TValue Current {
get {
return currentValue;
}
}
Object System.Collections.IEnumerator.Current {
get {
if( index == 0 || (index == dictionary.count + 1)) {
ThrowHelper.ThrowInvalidOperationException(ExceptionResource.InvalidOperation_EnumOpCantHappen);
}
return currentValue;
}
}
void System.Collections.IEnumerator.Reset() {
if (version != dictionary.version) {
ThrowHelper.ThrowInvalidOperationException(ExceptionResource.InvalidOperation_EnumFailedVersion);
}
index = 0;
currentValue = default(TValue);
}
}
}
}
}
// File provided for Reference Use Only by Microsoft Corporation (c) 2007.