PageRenderTime 311ms CodeModel.GetById 101ms app.highlight 90ms RepoModel.GetById 111ms app.codeStats 0ms

/Utilities/Collections/Hash.cs

#
C# | 962 lines | 551 code | 94 blank | 317 comment | 69 complexity | 5fd2ac21b50d7dfe2f0f24b965f07ee6 MD5 | raw file
  1using System;
  2using System.Collections;
  3using System.Collections.Generic;
  4using System.Diagnostics;
  5using System.Runtime.Serialization;
  6using Delta.Utilities.Helpers;
  7using NUnit.Framework;
  8
  9namespace Delta.Utilities.Collections
 10{
 11	/// <summary>
 12	/// The base implementation for various collections classes that use hash
 13	/// tables as part of their implementation. This class should not (and can
 14	/// not) be used directly by end users; it's only for internal use by the
 15	/// collections package. The Hash does not handle duplicate values.
 16	/// </summary>
 17	/// <remarks>
 18	/// The Hash manages items of type T, and uses a IComparer&lt;ItemTYpe&gt;
 19	/// that hashes compares items to hash items into the table.  
 20	/// </remarks>
 21	/// <typeparam name="T">Data type</typeparam>
 22	internal class Hash<T>
 23		: IEnumerable<T>, ISerializable, IDeserializationCallback
 24	{
 25		#region Public
 26		/// <summary>
 27		/// Get the number of items in the hash table.
 28		/// </summary>
 29		/// <typeparam name="T">T</typeparam>
 30		/// <value>The number of items stored in the hash table.</value>
 31		public int ElementCount
 32		{
 33			get
 34			{
 35				return count;
 36			}
 37		}
 38
 39		/// <summary>
 40		/// Get the number of slots in the hash table. Exposed internally
 41		/// for testing purposes.
 42		/// </summary>
 43		/// <typeparam name="T">T</typeparam>
 44		/// <value>The number of slots in the hash table.</value>
 45		internal int SlotCount
 46		{
 47			get
 48			{
 49				return totalSlots;
 50			}
 51		}
 52
 53		/// <summary>
 54		/// Get or change the load factor. Changing the load factor may cause
 55		/// the size of the table to grow or shrink accordingly.
 56		/// </summary>
 57		/// <typeparam name="T">T</typeparam>
 58		/// <value></value>
 59		public float LoadFactor
 60		{
 61			get
 62			{
 63				return loadFactor;
 64			}
 65			set
 66			{
 67				// Don't allow hopelessly inefficient load factors.
 68				if (value < 0.25 ||
 69				    value > 0.95)
 70				{
 71					throw new ArgumentOutOfRangeException(
 72						"value", "The load factor must be between 0.25 and 0.95: " + value);
 73				}
 74
 75				StopEnumerations();
 76
 77				// May need to expand or shrink the table -- which?
 78				bool maybeExpand = value < loadFactor;
 79
 80				// Update loadFactor and thresholds.
 81				loadFactor = value;
 82				thresholdGrow = (int)(totalSlots * loadFactor);
 83				thresholdShrink = thresholdGrow / 3;
 84				if (thresholdShrink <= MINSIZE)
 85				{
 86					thresholdShrink = 1;
 87				}
 88
 89				// Possibly expand or shrink the table.
 90				if (maybeExpand)
 91				{
 92					EnsureEnoughSlots(0);
 93				}
 94				else
 95				{
 96					ShrinkIfNeeded();
 97				}
 98			}
 99		}
100		#endregion
101
102		#region Private
103		// NOTE: If you add new member variables, you very well may need to change
104		// the serialization code to serialize that member.
105
106		/// <summary>
107		/// Interface for comparing elements
108		/// </summary>
109		/// <typeparam name="T">T</typeparam>
110		private IEqualityComparer<T> equalityComparer;
111
112		/// <summary>
113		/// Count
114		/// </summary>
115		/// <typeparam name="T">T</typeparam>
116		/// <summary>
117		/// The count of elements in the table.
118		/// </summary>
119		private int count;
120
121		/// <summary>
122		/// Includes real elements and deleted elements with the collision bit on.
123		/// Used to determine when we need to resize.
124		/// </summary>
125		/// <typeparam name="T">T</typeparam>
126		private int usedSlots;
127
128		/// <summary>
129		/// Size of the table. Always a power of two.
130		/// </summary>
131		/// <typeparam name="T">T</typeparam>
132		private int totalSlots;
133
134		/// <summary>
135		/// maximal load factor for the table.
136		/// </summary>
137		/// <typeparam name="T">T</typeparam>
138		private float loadFactor;
139
140		/// <summary>
141		/// floor(totalSlots * loadFactor);
142		/// </summary>
143		/// <typeparam name="T">T</typeparam>
144		private int thresholdGrow;
145
146		/// <summary>
147		/// thresholdGrow / 3.
148		/// </summary>
149		/// <typeparam name="T">T</typeparam>
150		private int thresholdShrink;
151
152		/// <summary>
153		/// Table
154		/// </summary>
155		/// <typeparam name="T">T</typeparam>
156		/// <summary>
157		/// Mask to convert hash values to the size of the table.
158		/// </summary>
159		private int hashMask;
160
161		/// <summary>
162		/// Shift to get the secondary skip value.
163		/// </summary>
164		/// <typeparam name="T">T</typeparam>
165		private int secondaryShift;
166
167		/// <summary>
168		/// The hash table.
169		/// </summary>
170		/// <typeparam name="T">T</typeparam>
171		private Slot[] table;
172
173		/// <summary>
174		/// An integer that is changed every time the table structurally changes.
175		/// Used so that enumerations throw an exception if the tree is changed
176		/// during enumeration.
177		/// </summary>
178		/// <typeparam name="T">T</typeparam>
179		private int changeStamp;
180
181		// minimum number of slots.
182		private const int MINSIZE = 16;
183
184		/// <summary>
185		/// Info used during deserialization.
186		/// </summary>
187		/// <typeparam name="T">T</typeparam>
188		private SerializationInfo serializationInfo;
189		#endregion
190
191		#region Slot helper structure
192		/// <summary>
193		/// The structure that has each slot in the hash table. Each slot has three
194		/// parts:
195		/// 1. The collision bit. Indicates whether some item visited this slot but
196		/// had to keep looking because the slot was full. 
197		/// 2. 31-bit full hash value of the item. If zero, the slot is empty.
198		/// 3. The item itself.
199		/// </summary>
200		private struct Slot
201		{
202			#region item (Public)
203			/// <summary>
204			/// 
205			/// </summary>
206			/// <summary>
207			/// The item.
208			/// </summary>
209			public T item;
210			#endregion
211
212			#region HashValue (Public)
213			/// <summary>
214			/// The full hash value associated with the value in this slot, or zero
215			/// if the slot is empty.
216			/// </summary>
217			public int HashValue
218			{
219				get
220				{
221					return (int)(hash_collision & 0x7FFFFFFF);
222				}
223				set
224				{
225					// make sure sign bit isn't set.
226					Debug.Assert((value & 0x80000000) == 0);
227					hash_collision = (uint)value | (hash_collision & 0x80000000);
228				}
229			}
230			#endregion
231
232			#region Empty (Public)
233			/// <summary>
234			/// Is this slot empty?
235			/// </summary>
236			public bool Empty
237			{
238				get
239				{
240					return HashValue == 0;
241				}
242			}
243			#endregion
244
245			#region Collision (Public)
246			/// <summary>
247			/// The "Collision" bit indicates that some value hit this slot and
248			/// collided, so had to try another slot.
249			/// </summary>
250			public bool Collision
251			{
252				get
253				{
254					return (hash_collision & 0x80000000) != 0;
255				}
256				set
257				{
258					if (value)
259					{
260						hash_collision |= 0x80000000;
261					}
262					else
263					{
264						hash_collision &= 0x7FFFFFFF;
265					}
266				}
267			}
268			#endregion
269
270			#region Private
271
272			#region hash_collision (Private)
273			/// <summary>
274			/// Lower 31 bits: the hash value. Top bit: the collision bit.
275			/// </summary>
276			private uint hash_collision;
277			#endregion
278
279			#endregion
280
281			#region Clear (Public)
282			/// <summary>
283			/// Clear this slot, leaving the collision bit alone.
284			/// </summary>
285			public void Clear()
286			{
287				HashValue = 0;
288				// Done to avoid keeping things alive that shouldn't be.
289				item = default(T);
290			}
291			#endregion
292
293			#region ToString (Public)
294			/// <summary>
295			/// To string
296			/// </summary>
297			/// <returns>String</returns>
298			public override string ToString()
299			{
300				return item != null
301				       	? item.ToString()
302				       	: "";
303			}
304			#endregion
305		}
306		#endregion
307
308		#region Constructor
309		/// <summary>
310		/// Constructor. Create a new hash table.
311		/// </summary>
312		/// <param name="equalityComparer">The comparer to use to compare items.
313		/// </param>
314		public Hash(IEqualityComparer<T> equalityComparer)
315		{
316			this.equalityComparer = equalityComparer;
317			// default load factor.
318			loadFactor = 0.70F;
319		}
320		#endregion
321
322		#region Enumeration helpers
323		/// <summary>
324		/// Gets the current enumeration stamp. Call CheckEnumerationStamp later
325		/// with this value to throw an exception if the hash table is changed.
326		/// </summary>
327		/// <returns>The current enumeration stamp.</returns>
328		internal int GetEnumerationStamp()
329		{
330			return changeStamp;
331		}
332
333		/// <summary>
334		/// Must be called whenever there is a structural change in the tree.
335		/// Causes changeStamp to be changed, which causes any in-progress
336		/// enumerations to throw exceptions.
337		/// </summary>
338		internal void StopEnumerations()
339		{
340			++changeStamp;
341		}
342
343		/// <summary>
344		/// Checks the given stamp against the current change stamp. If different,
345		/// the collection has changed during enumeration and an
346		/// InvalidOperationException must be thrown
347		/// </summary>
348		/// <param name="startStamp">changeStamp at the start of the enumeration.
349		/// </param>
350		internal void CheckEnumerationStamp(int startStamp)
351		{
352			if (startStamp != changeStamp)
353			{
354				throw new InvalidOperationException(
355					"Collection was modified during an enumeration.");
356			}
357		}
358
359		/// <summary>
360		/// Enumerate all of the items in the hash table. The items
361		/// are enumerated in a haphazard, unpredictable order.
362		/// </summary>
363		/// <returns>An IEnumerator&lt;T&gt; that enumerates the items
364		/// in the hash table.</returns>
365		public IEnumerator<T> GetEnumerator()
366		{
367			if (count > 0)
368			{
369				int startStamp = changeStamp;
370
371				foreach (Slot slot in table)
372				{
373					if (!slot.Empty)
374					{
375						yield return slot.item;
376						CheckEnumerationStamp(startStamp);
377					}
378				}
379			}
380		}
381
382		/// <summary>
383		/// Enumerate all of the items in the hash table. The items
384		/// are enumerated in a haphazard, unpredictable order.
385		/// </summary>
386		/// <returns>An IEnumerator that enumerates the items
387		/// in the hash table.</returns>
388		IEnumerator
389			IEnumerable.GetEnumerator()
390		{
391			return GetEnumerator();
392		}
393		#endregion
394
395		#region Get hash values
396		/// <summary>
397		/// Gets the full hash code for an item.
398		/// </summary>
399		/// <param name="item">Item to get hash code for.</param>
400		/// <returns>The full hash code. It is never zero.</returns>
401		private int GetFullHash(T item)
402		{
403			uint hash;
404
405			hash = (uint)(item == null
406			              	? 0x1786E23C
407			              	: equalityComparer.GetHashCode(item));
408
409			// The .NET framework tends to produce pretty bad hash codes.
410			// Scramble them up to be much more random!
411			hash += ~(hash << 15);
412			hash ^= (hash >> 10);
413			hash += (hash << 3);
414			hash ^= (hash >> 6);
415			hash += ~(hash << 11);
416			hash ^= (hash >> 16);
417			hash &= 0x7FFFFFFF;
418			if (hash == 0)
419			{
420				hash = 0x7FFFFFFF; // Make sure it isn't zero.
421			}
422			return (int)hash;
423		}
424
425		/// <summary>
426		/// Get the initial bucket number and skip amount from the full hash value.
427		/// </summary>
428		/// <param name="hash">The full hash value.</param>
429		/// <param name="initialBucket">Returns the initial bucket. Always in the
430		/// range 0..(totalSlots - 1).</param>
431		/// <param name="skip">Returns the skip values. Always odd in the range
432		/// 0..(totalSlots - 1).</param>
433		private void GetHashValuesFromFullHash(
434			int hash, out int initialBucket, out int skip)
435		{
436			initialBucket = hash & hashMask;
437
438			// The skip value must be relatively prime to the table size. Since the
439			// table size is a power of two, any odd number is relatively prime, so
440			// oring in 1 will do it.
441			skip = ((hash >> secondaryShift) & hashMask) | 1;
442		}
443
444		/// <summary>
445		/// Gets the full hash value, initial bucket number, and skip amount for an
446		/// item.
447		/// </summary>
448		/// <param name="item">Item to get hash value of.</param>
449		/// <param name="initialBucket">Returns the initial bucket. Always in the
450		/// range 0..(totalSlots - 1).</param>
451		/// <param name="skip">Returns the skip values. Always odd in the range
452		/// 0..(totalSlots - 1).</param>
453		/// <returns>The full hash value. This is never zero.</returns>
454		private int GetHashValues(T item, out int initialBucket, out int skip)
455		{
456			int hash = GetFullHash(item);
457			GetHashValuesFromFullHash(hash, out initialBucket, out skip);
458			return hash;
459		}
460		#endregion
461
462		#region Helper methods
463		/// <summary>
464		/// Make sure there are enough slots in the hash table that
465		/// <paramref name="additionalItems"/> items can be inserted into the
466		/// table.
467		/// </summary>
468		/// <param name="additionalItems">Number of additional items we are
469		/// inserting.</param>
470		private void EnsureEnoughSlots(int additionalItems)
471		{
472			StopEnumerations();
473
474			if (usedSlots + additionalItems > thresholdGrow)
475			{
476				// We need to expand the table. Figure out to what size.
477				int newSize;
478
479				newSize = Math.Max(totalSlots, MINSIZE);
480				while ((int)(newSize * loadFactor) <
481				       usedSlots + additionalItems)
482				{
483					newSize *= 2;
484					if (newSize <= 0)
485					{
486						// Must have overflowed the size of an int.
487						// Hard to believe we didn't run out of memory first.
488						throw new InvalidOperationException(
489							"The collection has become too large.");
490					}
491				}
492
493				ResizeTable(newSize);
494			}
495		}
496
497		/// <summary>
498		/// Check if the number of items in the table is small enough that
499		/// we should shrink the table again.
500		/// </summary>
501		private void ShrinkIfNeeded()
502		{
503			if (count < thresholdShrink)
504			{
505				int newSize;
506
507				if (count > 0)
508				{
509					newSize = MINSIZE;
510					while ((int)(newSize * loadFactor) < count)
511					{
512						newSize *= 2;
513					}
514				}
515				else
516				{
517					// We've removed all the elements. Shrink to zero.
518					newSize = 0;
519				}
520
521				ResizeTable(newSize);
522			}
523		}
524
525		/// <summary>
526		/// Given the size of a hash table, compute the "secondary shift" value.
527		/// The shift that is used to determine the skip amount for collision
528		/// resolution.
529		/// </summary>
530		/// <param name="newSize">The new size of the table.</param>
531		/// <returns>The secondary skip amount.</returns>
532		private int GetSecondaryShift(int newSize)
533		{
534			// x is of the form 0000111110 -- a single string of 1's followed by a
535			// single zero.
536			int x = newSize - 2;
537			int secondaryShiftValue = 0;
538
539			// Keep shifting x until it is the set of bits we want to extract:
540			// it be the highest bits possible, but can't overflow into the sign bit.
541			while ((x & 0x40000000) == 0)
542			{
543				x <<= 1;
544				++secondaryShiftValue;
545			}
546
547			return secondaryShiftValue;
548		}
549		#endregion
550
551		#region Resize table
552		/// <summary>
553		/// Resize the hash table to the given new size, moving all items into the
554		/// new hash table.
555		/// </summary>
556		/// <param name="newSize">The new size of the hash table. Must be a power
557		/// of two.</param>
558		private void ResizeTable(int newSize)
559		{
560			// Move all the items from this table to the new table.
561			Slot[] oldTable = table;
562
563			// Check newSize is a power of two.
564			Debug.Assert((newSize & (newSize - 1)) == 0);
565			totalSlots = newSize;
566			thresholdGrow = (int)(totalSlots * loadFactor);
567			thresholdShrink = thresholdGrow / 3;
568			if (thresholdShrink <= MINSIZE)
569			{
570				thresholdShrink = 1;
571			}
572			hashMask = newSize - 1;
573			secondaryShift = GetSecondaryShift(newSize);
574			if (totalSlots > 0)
575			{
576				table = new Slot[totalSlots];
577			}
578			else
579			{
580				table = null;
581			}
582
583			if (oldTable != null &&
584			    table != null)
585			{
586				foreach (Slot oldSlot in oldTable)
587				{
588					int hash, bucket, skip;
589
590					hash = oldSlot.HashValue;
591					GetHashValuesFromFullHash(hash, out bucket, out skip);
592
593					// Find an empty bucket.
594					while (!table[bucket].Empty)
595					{
596						// The slot is used, but isn't our item. Set the collision bit
597						// and keep looking.
598						table[bucket].Collision = true;
599						bucket = (bucket + skip) & hashMask;
600					}
601
602					// We found an empty bucket. 
603					table[bucket].HashValue = hash;
604					table[bucket].item = oldSlot.item;
605				}
606			}
607
608			// no deleted elements have the collision bit on now.
609			usedSlots = count;
610		}
611		#endregion
612
613		#region Insert
614		/// <summary>
615		/// Insert a new item into the hash table. If a duplicate item exists,
616		/// can replace or do nothing.
617		/// </summary>
618		/// <param name="item">The item to insert.</param>
619		/// <param name="replaceOnDuplicate">If true, duplicate items are replaced.
620		/// If false, nothing is done if a duplicate already exists.</param>
621		/// <param name="previous">If a duplicate was found, returns it (whether
622		/// replaced or not).</param>
623		/// <returns>True if no duplicate existed, false if a duplicate was found.
624		/// </returns>
625		public bool Insert(T item, bool replaceOnDuplicate, out T previous)
626		{
627			int hash, bucket, skip;
628			// If >= 0, an empty bucket we can use for a true insert
629			int emptyBucket = -1;
630			// If true, still the possibility that a duplicate exists.
631			bool duplicateMightExist = true;
632
633			// Ensure enough room to insert. Also stops enumerations.
634			EnsureEnoughSlots(1);
635
636			hash = GetHashValues(item, out bucket, out skip);
637
638			for (;;)
639			{
640				if (table[bucket].Empty)
641				{
642					// Record the location of the first empty bucket seen.
643					// This is where the item will go if no duplicate exists.
644					if (emptyBucket == -1)
645					{
646						emptyBucket = bucket;
647					}
648
649					if (!duplicateMightExist ||
650					    !table[bucket].Collision)
651					{
652						// There can't be a duplicate further on, because a bucket with
653						// the collision bit clear was found (here or earlier). We have
654						// the place to insert.
655						break;
656					}
657				}
658				else if (table[bucket].HashValue == hash &&
659				         equalityComparer.Equals(table[bucket].item, item))
660				{
661					// We found a duplicate item. Replace it if requested to.
662					previous = table[bucket].item;
663					if (replaceOnDuplicate)
664					{
665						table[bucket].item = item;
666					}
667					return false;
668				}
669				else
670				{
671					// The slot is used, but isn't our item. 
672					if (!table[bucket].Collision)
673					{
674						// Since the collision bit is off, we can't have a duplicate. 
675						if (emptyBucket >= 0)
676						{
677							// We already have an empty bucket to use.
678							break;
679						}
680						else
681						{
682							// Keep searching for an empty bucket to place the item.
683							table[bucket].Collision = true;
684							duplicateMightExist = false;
685						}
686					}
687				}
688
689				bucket = (bucket + skip) & hashMask;
690			}
691
692			// We found an empty bucket. Insert the new item.
693			table[emptyBucket].HashValue = hash;
694			table[emptyBucket].item = item;
695
696			++count;
697			if (!table[emptyBucket].Collision)
698			{
699				++usedSlots;
700			}
701			previous = default(T);
702			return true;
703		}
704		#endregion
705
706		#region Delete
707		/// <summary>
708		/// Deletes an item from the hash table. 
709		/// </summary>
710		/// <param name="item">Item to search for and delete.</param>
711		/// <param name="itemDeleted">If true returned, the actual item stored in
712		/// the hash table (must be equal to <paramref name="item"/>, but may not
713		/// be identical.</param>
714		/// <returns>True if item was found and deleted, false if item wasn't
715		/// found.</returns>
716		public bool Delete(T item, out T itemDeleted)
717		{
718			int hash, bucket, skip;
719
720			StopEnumerations();
721
722			if (count == 0)
723			{
724				itemDeleted = default(T);
725				return false;
726			}
727
728			hash = GetHashValues(item, out bucket, out skip);
729
730			for (;;)
731			{
732				if (table[bucket].HashValue == hash &&
733				    equalityComparer.Equals(table[bucket].item, item))
734				{
735					// Found the item. Remove it.
736					itemDeleted = table[bucket].item;
737					table[bucket].Clear();
738					--count;
739					if (!table[bucket].Collision)
740					{
741						--usedSlots;
742					}
743					ShrinkIfNeeded();
744					return true;
745				}
746				else if (!table[bucket].Collision)
747				{
748					// No collision bit, so we can stop searching. No such element.
749					itemDeleted = default(T);
750					return false;
751				}
752
753				bucket = (bucket + skip) & hashMask;
754			}
755		}
756		#endregion
757
758		#region Find
759		/// <summary>
760		/// Find an item in the hash table. If found, optionally replace it with
761		/// the finding item.
762		/// </summary>
763		/// <param name="find">Item to find.</param>
764		/// <param name="replace">If true, replaces the equal item in the hash
765		/// table with <paramref name="item"/>.</param>
766		/// <param name="item">Returns the equal item found in the table, if true
767		/// was returned.</param>
768		/// <returns>True if the item was found, false otherwise.</returns>
769		public bool Find(T find, bool replace, out T item)
770		{
771			int hash, bucket, skip;
772
773			if (count == 0)
774			{
775				item = default(T);
776				return false;
777			}
778
779			hash = GetHashValues(find, out bucket, out skip);
780
781			for (;;)
782			{
783				if (table[bucket].HashValue == hash &&
784				    equalityComparer.Equals(table[bucket].item, find))
785				{
786					// Found the item.  
787					item = table[bucket].item;
788					if (replace)
789					{
790						table[bucket].item = find;
791					}
792					return true;
793				}
794				else if (!table[bucket].Collision)
795				{
796					// No collision bit, so we can stop searching. No such element.
797					item = default(T);
798					return false;
799				}
800
801				bucket = (bucket + skip) & hashMask;
802			}
803		}
804		#endregion
805
806		#region Clone
807		/// <summary>
808		/// Creates a clone of this hash table.
809		/// </summary>
810		/// <param name="cloneItem">If non-null, this function is applied to each
811		/// item when cloning. It must be the case that this function does not
812		/// modify the hash code or equality function.</param>
813		/// <returns>A shallow clone that contains the same items.</returns>
814		public Hash<T> Clone(Converter<T, T> cloneItem)
815		{
816			Hash<T> clone = new Hash<T>(equalityComparer);
817			clone.count = count;
818			clone.usedSlots = usedSlots;
819			clone.totalSlots = totalSlots;
820			clone.loadFactor = loadFactor;
821			clone.thresholdGrow = thresholdGrow;
822			clone.thresholdShrink = thresholdShrink;
823			clone.hashMask = hashMask;
824			clone.secondaryShift = secondaryShift;
825			if (table != null)
826			{
827				clone.table = (Slot[])table.Clone();
828
829				if (cloneItem != null)
830				{
831					for (int i = 0; i < table.Length; ++i)
832					{
833						if (!table[i].Empty)
834						{
835							table[i].item = cloneItem(table[i].item);
836						}
837					}
838				}
839			}
840
841			return clone;
842		}
843		#endregion
844
845		#region Serialization
846		/// <summary>
847		/// Serialize the hash table. Called from the serialization infrastructure.
848		/// </summary>
849		/// <param name="context">Context</param>
850		/// <param name="info">Info</param>
851		void ISerializable.GetObjectData(
852			SerializationInfo info, StreamingContext context)
853		{
854			if (info == null)
855			{
856				throw new ArgumentNullException("info");
857			}
858
859			info.AddValue("equalityComparer", equalityComparer,
860				typeof(IEqualityComparer<T>));
861			info.AddValue("loadFactor", loadFactor, typeof(float));
862			T[] items = new T[count];
863			int i = 0;
864			foreach (Slot slot in table)
865			{
866				if (!slot.Empty)
867				{
868					items[i++] = slot.item;
869				}
870			}
871			info.AddValue("items", items, typeof(T[]));
872		}
873
874		/// <summary>
875		/// Called on deserialization. We cannot deserialize now, because hash
876		/// codes might not be correct now. We do real deserialization in the
877		/// OnDeserialization call.
878		/// </summary>
879		/// <param name="context">Context</param>
880		/// <param name="serInfo">serInfo</param>
881		protected Hash(SerializationInfo serInfo, StreamingContext context)
882		{
883			// Save away the serialization info for use later. We can't be sure of
884			// hash codes being stable until the entire object graph is deserialized,
885			// so we wait until then to deserialize.
886			serializationInfo = serInfo;
887		}
888
889		/// <summary>
890		/// Deserialize the hash table. Called from the serialization
891		/// infrastructure when the object graph has finished deserializing.
892		/// </summary>
893		/// <param name="sender">sender</param>
894		void IDeserializationCallback.OnDeserialization(object sender)
895		{
896			if (serializationInfo == null)
897			{
898				return;
899			}
900
901			loadFactor = serializationInfo.GetSingle("loadFactor");
902			equalityComparer = (IEqualityComparer<T>)serializationInfo.GetValue(
903				"equalityComparer", typeof(IEqualityComparer<T>));
904
905			T[] items = (T[])serializationInfo.GetValue("items", typeof(T[]));
906			T dummy;
907
908			EnsureEnoughSlots(items.Length);
909			foreach (T item in items)
910			{
911				Insert(item, true, out dummy);
912			}
913
914			serializationInfo = null;
915		}
916		#endregion
917
918		#region To string
919		/// <summary>
920		/// To string
921		/// </summary>
922		/// <returns>String</returns>
923		public override string ToString()
924		{
925			return "Hash: " + table.Write();
926		}
927		#endregion
928
929		#region Debug helpers
930		#endregion
931	}
932
933	/// <summary>
934	/// Hash tests, needs to be an extra class because Hash is generic.
935	/// </summary>
936	internal class HashTests
937	{
938		#region TestHash (Static)
939		/// <summary>
940		/// Test hash. Note: Too slow for a dynamic unit test.
941		/// </summary>
942		[Test]
943		public static void TestHash()
944		{
945			Hash<string> hash = new Hash<string>(StringComparer.InvariantCulture);
946			string dummyPrevious;
947			hash.Insert("test item", true, out dummyPrevious);
948			hash.Insert("yo mama", true, out dummyPrevious);
949			hash.Insert("test item", true, out dummyPrevious);
950
951			Assert.Equal(2, hash.ElementCount);
952			// Thats just the way the data is used internally ^^
953			Assert.Equal("Hash: yo mama, , , , test item, , , , , ",
954				hash.ToString());
955			string item;
956			Assert.Equal(false, hash.Find("ne ne", false, out item));
957			Assert.Equal(true, hash.Find("test item", false, out item));
958			Assert.Equal("test item", item);
959		}
960		#endregion
961	}
962}