IntHashTable.cs 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583
  1. //-----------------------------------------------------------------------------
  2. //
  3. // Copyright (c) Microsoft. All rights reserved.
  4. // This code is licensed under the Microsoft Public License.
  5. // THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF
  6. // ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY
  7. // IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR
  8. // PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.
  9. //
  10. //-----------------------------------------------------------------------------
  11. using System;
  12. using System.Collections;
  13. namespace Microsoft.Cci.Pdb {
  14. // The IntHashTable class represents a dictionary of associated keys and
  15. // values with constant lookup time.
  16. //
  17. // Objects used as keys in a hashtable must implement the GetHashCode
  18. // and Equals methods (or they can rely on the default implementations
  19. // inherited from Object if key equality is simply reference
  20. // equality). Furthermore, the GetHashCode and Equals methods of
  21. // a key object must produce the same results given the same parameters
  22. // for the entire time the key is present in the hashtable. In practical
  23. // terms, this means that key objects should be immutable, at least for
  24. // the time they are used as keys in a hashtable.
  25. //
  26. // When entries are added to a hashtable, they are placed into
  27. // buckets based on the hashcode of their keys. Subsequent lookups of
  28. // keys will use the hashcode of the keys to only search a particular
  29. // bucket, thus substantially reducing the number of key comparisons
  30. // required to find an entry. A hashtable's maximum load factor, which
  31. // can be specified when the hashtable is instantiated, determines the
  32. // maximum ratio of hashtable entries to hashtable buckets. Smaller load
  33. // factors cause faster average lookup times at the cost of increased
  34. // memory consumption. The default maximum load factor of 1.0 generally
  35. // provides the best balance between speed and size. As entries are added
  36. // to a hashtable, the hashtable's actual load factor increases, and when
  37. // the actual load factor reaches the maximum load factor value, the
  38. // number of buckets in the hashtable is automatically increased by
  39. // approximately a factor of two (to be precise, the number of hashtable
  40. // buckets is increased to the smallest prime number that is larger than
  41. // twice the current number of hashtable buckets).
  42. //
  43. // Each object provides their own hash function, accessed by calling
  44. // GetHashCode(). However, one can write their own object
  45. // implementing IHashCodeProvider and pass it to a constructor on
  46. // the IntHashTable. That hash function would be used for all objects in
  47. // the table.
  48. //
  49. // This IntHashTable is implemented to support multiple concurrent readers
  50. // and one concurrent writer without using any synchronization primitives.
  51. // All read methods essentially must protect themselves from a resize
  52. // occuring while they are running. This was done by enforcing an
  53. // ordering on inserts & removes, as well as removing some member variables
  54. // and special casing the expand code to work in a temporary array instead
  55. // of the live bucket array. All inserts must set a bucket's value and
  56. // key before setting the hash code & collision field.
  57. //
  58. // By Brian Grunkemeyer, algorithm by Patrick Dussud.
  59. // Version 1.30 2/20/2000
  60. //| <include path='docs/doc[@for="IntHashTable"]/*' />
  61. internal class IntHashTable {//: IEnumerable {
  62. /*
  63. Implementation Notes:
  64. This IntHashTable uses double hashing. There are hashsize buckets in
  65. the table, and each bucket can contain 0 or 1 element. We a bit to
  66. mark whether there's been a collision when we inserted multiple
  67. elements (ie, an inserted item was hashed at least a second time and
  68. we probed this bucket, but it was already in use). Using the
  69. collision bit, we can terminate lookups & removes for elements that
  70. aren't in the hash table more quickly. We steal the most
  71. significant bit from the hash code to store the collision bit.
  72. Our hash function is of the following form:
  73. h(key, n) = h1(key) + n*h2(key)
  74. where n is the number of times we've hit a collided bucket and
  75. rehashed (on this particular lookup). Here are our hash functions:
  76. h1(key) = GetHash(key); // default implementation calls key.GetHashCode();
  77. h2(key) = 1 + (((h1(key) >> 5) + 1) % (hashsize - 1));
  78. The h1 can return any number. h2 must return a number between 1 and
  79. hashsize - 1 that is relatively prime to hashsize (not a problem if
  80. hashsize is prime). (Knuth's Art of Computer Programming, Vol. 3,
  81. p. 528-9)
  82. If this is true, then we are guaranteed to visit every bucket in
  83. exactly hashsize probes, since the least common multiple of hashsize
  84. and h2(key) will be hashsize * h2(key). (This is the first number
  85. where adding h2 to h1 mod hashsize will be 0 and we will search the
  86. same bucket twice).
  87. We previously used a different h2(key, n) that was not constant.
  88. That is a horrifically bad idea, unless you can prove that series
  89. will never produce any identical numbers that overlap when you mod
  90. them by hashsize, for all subranges from i to i+hashsize, for all i.
  91. It's not worth investigating, since there was no clear benefit from
  92. using that hash function, and it was broken.
  93. For efficiency reasons, we've implemented this by storing h1 and h2
  94. in a temporary, and setting a variable called seed equal to h1. We
  95. do a probe, and if we collided, we simply add h2 to seed each time
  96. through the loop.
  97. A good test for h2() is to subclass IntHashTable, provide your own
  98. implementation of GetHash() that returns a constant, then add many
  99. items to the hash table. Make sure Count equals the number of items
  100. you inserted.
  101. -- Brian Grunkemeyer, 10/28/1999
  102. */
  103. // A typical resize algorithm would pick the smallest prime number in this array
  104. // that is larger than twice the previous capacity.
  105. // Suppose our Hashtable currently has capacity x and enough elements are added
  106. // such that a resize needs to occur. Resizing first computes 2x then finds the
  107. // first prime in the table greater than 2x, i.e. if primes are ordered
  108. // p_1, p_2, ? p_i,? it finds p_n such that p_n-1 < 2x < p_n.
  109. // Doubling is important for preserving the asymptotic complexity of the
  110. // hashtable operations such as add. Having a prime guarantees that double
  111. // hashing does not lead to infinite loops. IE, your hash function will be
  112. // h1(key) + i*h2(key), 0 <= i < size. h2 and the size must be relatively prime.
  113. private static readonly int[] primes = {
  114. 3, 7, 11, 17, 23, 29, 37, 47, 59, 71, 89, 107, 131, 163, 197, 239, 293, 353, 431, 521, 631, 761, 919,
  115. 1103, 1327, 1597, 1931, 2333, 2801, 3371, 4049, 4861, 5839, 7013, 8419, 10103, 12143, 14591,
  116. 17519, 21023, 25229, 30293, 36353, 43627, 52361, 62851, 75431, 90523, 108631, 130363, 156437,
  117. 187751, 225307, 270371, 324449, 389357, 467237, 560689, 672827, 807403, 968897, 1162687, 1395263,
  118. 1674319, 2009191, 2411033, 2893249, 3471899, 4166287, 4999559, 5999471, 7199369};
  119. private static int GetPrime(int minSize) {
  120. if (minSize < 0) {
  121. throw new ArgumentException("Arg_HTCapacityOverflow");
  122. }
  123. for (int i = 0; i < primes.Length; i++) {
  124. int size = primes[i];
  125. if (size >= minSize) {
  126. return size;
  127. }
  128. }
  129. throw new ArgumentException("Arg_HTCapacityOverflow");
  130. }
  131. // Deleted entries have their key set to buckets
  132. // The hash table data.
  133. // This cannot be serialised
  134. private struct bucket {
  135. internal int key;
  136. internal int hash_coll; // Store hash code; sign bit means there was a collision.
  137. internal Object val;
  138. }
  139. private bucket[] buckets;
  140. // The total number of entries in the hash table.
  141. private int count;
  142. // The total number of collision bits set in the hashtable
  143. private int occupancy;
  144. private int loadsize;
  145. private int loadFactorPerc; // 100 = 1.0
  146. private int version;
  147. // Constructs a new hashtable. The hashtable is created with an initial
  148. // capacity of zero and a load factor of 1.0.
  149. //| <include path='docs/doc[@for="IntHashTable.IntHashTable"]/*' />
  150. internal IntHashTable()
  151. : this(0, 100) {
  152. }
  153. //// Constructs a new hashtable with the given initial capacity and a load
  154. //// factor of 1.0. The capacity argument serves as an indication of
  155. //// the number of entries the hashtable will contain. When this number (or
  156. //// an approximation) is known, specifying it in the constructor can
  157. //// eliminate a number of resizing operations that would otherwise be
  158. //// performed when elements are added to the hashtable.
  159. ////
  160. ////| <include path='docs/doc[@for="IntHashTable.IntHashTable1"]/*' />
  161. //internal IntHashTable(int capacity)
  162. // : this(capacity, 100) {
  163. //}
  164. // Constructs a new hashtable with the given initial capacity and load
  165. // factor. The capacity argument serves as an indication of the
  166. // number of entries the hashtable will contain. When this number (or an
  167. // approximation) is known, specifying it in the constructor can eliminate
  168. // a number of resizing operations that would otherwise be performed when
  169. // elements are added to the hashtable. The loadFactorPerc argument
  170. // indicates the maximum ratio of hashtable entries to hashtable buckets.
  171. // Smaller load factors cause faster average lookup times at the cost of
  172. // increased memory consumption. A load factor of 1.0 generally provides
  173. // the best balance between speed and size.
  174. //
  175. //| <include path='docs/doc[@for="IntHashTable.IntHashTable3"]/*' />
  176. internal IntHashTable(int capacity, int loadFactorPerc) {
  177. if (capacity < 0)
  178. throw new ArgumentOutOfRangeException("capacity", "ArgumentOutOfRange_NeedNonNegNum");
  179. if (!(loadFactorPerc >= 10 && loadFactorPerc <= 100))
  180. throw new ArgumentOutOfRangeException("loadFactorPerc", String.Format("ArgumentOutOfRange_IntHashTableLoadFactor", 10, 100));
  181. // Based on perf work, .72 is the optimal load factor for this table.
  182. this.loadFactorPerc = (loadFactorPerc * 72) / 100;
  183. int hashsize = GetPrime((int)(capacity / this.loadFactorPerc));
  184. buckets = new bucket[hashsize];
  185. loadsize = (int)(this.loadFactorPerc * hashsize) / 100;
  186. if (loadsize >= hashsize)
  187. loadsize = hashsize-1;
  188. }
  189. // Computes the hash function: H(key, i) = h1(key) + i*h2(key, hashSize).
  190. // The out parameter seed is h1(key), while the out parameter
  191. // incr is h2(key, hashSize). Callers of this function should
  192. // add incr each time through a loop.
  193. private static uint InitHash(int key, int hashsize, out uint seed, out uint incr) {
  194. // Hashcode must be positive. Also, we must not use the sign bit, since
  195. // that is used for the collision bit.
  196. uint hashcode = (uint)key & 0x7FFFFFFF;
  197. seed = (uint)hashcode;
  198. // Restriction: incr MUST be between 1 and hashsize - 1, inclusive for
  199. // the modular arithmetic to work correctly. This guarantees you'll
  200. // visit every bucket in the table exactly once within hashsize
  201. // iterations. Violate this and it'll cause obscure bugs forever.
  202. // If you change this calculation for h2(key), update putEntry too!
  203. incr = (uint)(1 + (((seed >> 5) + 1) % ((uint)hashsize - 1)));
  204. return hashcode;
  205. }
  206. // Adds an entry with the given key and value to this hashtable. An
  207. // ArgumentException is thrown if the key is null or if the key is already
  208. // present in the hashtable.
  209. //
  210. //| <include path='docs/doc[@for="IntHashTable.Add"]/*' />
  211. internal void Add(int key, Object value) {
  212. Insert(key, value, true);
  213. }
  214. //// Removes all entries from this hashtable.
  215. ////| <include path='docs/doc[@for="IntHashTable.Clear"]/*' />
  216. //internal void Clear() {
  217. // if (count == 0)
  218. // return;
  219. // for (int i = 0; i < buckets.Length; i++) {
  220. // buckets[i].hash_coll = 0;
  221. // buckets[i].key = -1;
  222. // buckets[i].val = null;
  223. // }
  224. // count = 0;
  225. // occupancy = 0;
  226. //}
  227. // Checks if this hashtable contains an entry with the given key. This is
  228. // an O(1) operation.
  229. //
  230. //| <include path='docs/doc[@for="IntHashTable.Contains"]/*' />
  231. //internal bool Contains(int key) {
  232. // if (key < 0) {
  233. // throw new ArgumentException("Argument_KeyLessThanZero");
  234. // }
  235. // uint seed;
  236. // uint incr;
  237. // // Take a snapshot of buckets, in case another thread resizes table
  238. // bucket[] lbuckets = buckets;
  239. // uint hashcode = InitHash(key, lbuckets.Length, out seed, out incr);
  240. // int ntry = 0;
  241. // bucket b;
  242. // do {
  243. // int bucketNumber = (int)(seed % (uint)lbuckets.Length);
  244. // b = lbuckets[bucketNumber];
  245. // if (b.val == null) {
  246. // return false;
  247. // }
  248. // if (((b.hash_coll & 0x7FFFFFFF) == hashcode) && b.key == key) {
  249. // return true;
  250. // }
  251. // seed += incr;
  252. // } while (b.hash_coll < 0 && ++ntry < lbuckets.Length);
  253. // return false;
  254. //}
  255. // Returns the value associated with the given key. If an entry with the
  256. // given key is not found, the returned value is null.
  257. //
  258. //| <include path='docs/doc[@for="IntHashTable.this"]/*' />
  259. internal Object this[int key] {
  260. get {
  261. if (key < 0) {
  262. throw new ArgumentException("Argument_KeyLessThanZero");
  263. }
  264. uint seed;
  265. uint incr;
  266. // Take a snapshot of buckets, in case another thread does a resize
  267. bucket[] lbuckets = buckets;
  268. uint hashcode = InitHash(key, lbuckets.Length, out seed, out incr);
  269. int ntry = 0;
  270. bucket b;
  271. do {
  272. int bucketNumber = (int)(seed % (uint)lbuckets.Length);
  273. b = lbuckets[bucketNumber];
  274. if (b.val == null) {
  275. return null;
  276. }
  277. if (((b.hash_coll & 0x7FFFFFFF) == hashcode) && key == b.key) {
  278. return b.val;
  279. }
  280. seed += incr;
  281. } while (b.hash_coll < 0 && ++ntry < lbuckets.Length);
  282. return null;
  283. }
  284. //set {
  285. // Insert(key, value, false);
  286. //}
  287. }
  288. // Increases the bucket count of this hashtable. This method is called from
  289. // the Insert method when the actual load factor of the hashtable reaches
  290. // the upper limit specified when the hashtable was constructed. The number
  291. // of buckets in the hashtable is increased to the smallest prime number
  292. // that is larger than twice the current number of buckets, and the entries
  293. // in the hashtable are redistributed into the new buckets using the cached
  294. // hashcodes.
  295. private void expand() {
  296. rehash(GetPrime(1+buckets.Length*2));
  297. }
  298. // We occationally need to rehash the table to clean up the collision bits.
  299. private void rehash() {
  300. rehash(buckets.Length);
  301. }
  302. private void rehash(int newsize) {
  303. // reset occupancy
  304. occupancy=0;
  305. // Don't replace any internal state until we've finished adding to the
  306. // new bucket[]. This serves two purposes:
  307. // 1) Allow concurrent readers to see valid hashtable contents
  308. // at all times
  309. // 2) Protect against an OutOfMemoryException while allocating this
  310. // new bucket[].
  311. bucket[] newBuckets = new bucket[newsize];
  312. // rehash table into new buckets
  313. int nb;
  314. for (nb = 0; nb < buckets.Length; nb++) {
  315. bucket oldb = buckets[nb];
  316. if (oldb.val != null) {
  317. putEntry(newBuckets, oldb.key, oldb.val, oldb.hash_coll & 0x7FFFFFFF);
  318. }
  319. }
  320. // New bucket[] is good to go - replace buckets and other internal state.
  321. version++;
  322. buckets = newBuckets;
  323. loadsize = (int)(loadFactorPerc * newsize) / 100;
  324. if (loadsize >= newsize) {
  325. loadsize = newsize-1;
  326. }
  327. return;
  328. }
  329. // Returns an enumerator for this hashtable.
  330. // If modifications made to the hashtable while an enumeration is
  331. // in progress, the MoveNext and Current methods of the
  332. // enumerator will throw an exception.
  333. //
  334. //| <include path='docs/doc[@for="IntHashTable.IEnumerable.GetEnumerator"]/*' />
  335. //IEnumerator IEnumerable.GetEnumerator() {
  336. // return new IntHashTableEnumerator(this);
  337. //}
  338. // Internal method to compare two keys.
  339. //
  340. // Inserts an entry into this hashtable. This method is called from the Set
  341. // and Add methods. If the add parameter is true and the given key already
  342. // exists in the hashtable, an exception is thrown.
  343. private void Insert(int key, Object nvalue, bool add) {
  344. if (key < 0) {
  345. throw new ArgumentException("Argument_KeyLessThanZero");
  346. }
  347. if (nvalue == null) {
  348. throw new ArgumentNullException("nvalue", "ArgumentNull_Value");
  349. }
  350. if (count >= loadsize) {
  351. expand();
  352. } else if (occupancy > loadsize && count > 100) {
  353. rehash();
  354. }
  355. uint seed;
  356. uint incr;
  357. // Assume we only have one thread writing concurrently. Modify
  358. // buckets to contain new data, as long as we insert in the right order.
  359. uint hashcode = InitHash(key, buckets.Length, out seed, out incr);
  360. int ntry = 0;
  361. int emptySlotNumber = -1; // We use the empty slot number to cache the first empty slot. We chose to reuse slots
  362. // create by remove that have the collision bit set over using up new slots.
  363. do {
  364. int bucketNumber = (int)(seed % (uint)buckets.Length);
  365. // Set emptySlot number to current bucket if it is the first available bucket that we have seen
  366. // that once contained an entry and also has had a collision.
  367. // We need to search this entire collision chain because we have to ensure that there are no
  368. // duplicate entries in the table.
  369. // Insert the key/value pair into this bucket if this bucket is empty and has never contained an entry
  370. // OR
  371. // This bucket once contained an entry but there has never been a collision
  372. if (buckets[bucketNumber].val == null) {
  373. // If we have found an available bucket that has never had a collision, but we've seen an available
  374. // bucket in the past that has the collision bit set, use the previous bucket instead
  375. if (emptySlotNumber != -1) { // Reuse slot
  376. bucketNumber = emptySlotNumber;
  377. }
  378. // We pretty much have to insert in this order. Don't set hash
  379. // code until the value & key are set appropriately.
  380. buckets[bucketNumber].val = nvalue;
  381. buckets[bucketNumber].key = key;
  382. buckets[bucketNumber].hash_coll |= (int)hashcode;
  383. count++;
  384. version++;
  385. return;
  386. }
  387. // The current bucket is in use
  388. // OR
  389. // it is available, but has had the collision bit set and we have already found an available bucket
  390. if (((buckets[bucketNumber].hash_coll & 0x7FFFFFFF) == hashcode) &&
  391. key == buckets[bucketNumber].key) {
  392. if (add) {
  393. throw new ArgumentException("Argument_AddingDuplicate__" + buckets[bucketNumber].key);
  394. }
  395. buckets[bucketNumber].val = nvalue;
  396. version++;
  397. return;
  398. }
  399. // The current bucket is full, and we have therefore collided. We need to set the collision bit
  400. // UNLESS
  401. // we have remembered an available slot previously.
  402. if (emptySlotNumber == -1) {// We don't need to set the collision bit here since we already have an empty slot
  403. if (buckets[bucketNumber].hash_coll >= 0) {
  404. buckets[bucketNumber].hash_coll |= unchecked((int)0x80000000);
  405. occupancy++;
  406. }
  407. }
  408. seed += incr;
  409. } while (++ntry < buckets.Length);
  410. // This code is here if and only if there were no buckets without a collision bit set in the entire table
  411. if (emptySlotNumber != -1) {
  412. // We pretty much have to insert in this order. Don't set hash
  413. // code until the value & key are set appropriately.
  414. buckets[emptySlotNumber].val = nvalue;
  415. buckets[emptySlotNumber].key = key;
  416. buckets[emptySlotNumber].hash_coll |= (int)hashcode;
  417. count++;
  418. version++;
  419. return;
  420. }
  421. // If you see this assert, make sure load factor & count are reasonable.
  422. // Then verify that our double hash function (h2, described at top of file)
  423. // meets the requirements described above. You should never see this assert.
  424. throw new InvalidOperationException("InvalidOperation_HashInsertFailed");
  425. }
  426. private void putEntry(bucket[] newBuckets, int key, Object nvalue, int hashcode) {
  427. uint seed = (uint)hashcode;
  428. uint incr = (uint)(1 + (((seed >> 5) + 1) % ((uint)newBuckets.Length - 1)));
  429. do {
  430. int bucketNumber = (int)(seed % (uint)newBuckets.Length);
  431. if ((newBuckets[bucketNumber].val == null)) {
  432. newBuckets[bucketNumber].val = nvalue;
  433. newBuckets[bucketNumber].key = key;
  434. newBuckets[bucketNumber].hash_coll |= hashcode;
  435. return;
  436. }
  437. if (newBuckets[bucketNumber].hash_coll >= 0) {
  438. newBuckets[bucketNumber].hash_coll |= unchecked((int)0x80000000);
  439. occupancy++;
  440. }
  441. seed += incr;
  442. } while (true);
  443. }
  444. // Returns the number of associations in this hashtable.
  445. //
  446. //| <include path='docs/doc[@for="IntHashTable.Count"]/*' />
  447. //internal int Count {
  448. // get { return count; }
  449. //}
  450. // Implements an enumerator for a hashtable. The enumerator uses the
  451. // internal version number of the hashtabke to ensure that no modifications
  452. // are made to the hashtable while an enumeration is in progress.
  453. //private class IntHashTableEnumerator : IEnumerator {
  454. // private IntHashTable hashtable;
  455. // private int bucket;
  456. // private int version;
  457. // private bool current;
  458. // //private int currentKey;
  459. // private Object currentValue;
  460. // internal IntHashTableEnumerator(IntHashTable hashtable) {
  461. // this.hashtable = hashtable;
  462. // bucket = hashtable.buckets.Length;
  463. // version = hashtable.version;
  464. // }
  465. // public bool MoveNext() {
  466. // if (version != hashtable.version)
  467. // throw new InvalidOperationException("InvalidOperation_EnumFailedVersion");
  468. // while (bucket > 0) {
  469. // bucket--;
  470. // Object val = hashtable.buckets[bucket].val;
  471. // if (val != null) {
  472. // //currentKey = hashtable.buckets[bucket].key;
  473. // currentValue = val;
  474. // current = true;
  475. // return true;
  476. // }
  477. // }
  478. // current = false;
  479. // return false;
  480. // }
  481. // //internal int Key {
  482. // // get {
  483. // // if (current == false)
  484. // // throw new InvalidOperationException("InvalidOperation_EnumOpCantHappen");
  485. // // return currentKey;
  486. // // }
  487. // //}
  488. // public Object Current {
  489. // get {
  490. // if (current == false)
  491. // throw new InvalidOperationException("InvalidOperation_EnumOpCantHappen");
  492. // return currentValue;
  493. // }
  494. // }
  495. // //public Object Value {
  496. // // get {
  497. // // if (version != hashtable.version)
  498. // // throw new InvalidOperationException("InvalidOperation_EnumFailedVersion");
  499. // // if (current == false)
  500. // // throw new InvalidOperationException("InvalidOperation_EnumOpCantHappen");
  501. // // return currentValue;
  502. // // }
  503. // //}
  504. // public void Reset() {
  505. // if (version != hashtable.version) throw new InvalidOperationException("InvalidOperation_EnumFailedVersion");
  506. // current = false;
  507. // bucket = hashtable.buckets.Length;
  508. // //currentKey = -1;
  509. // currentValue = null;
  510. // }
  511. //}
  512. }
  513. }