1880 lines
82 KiB
C#
1880 lines
82 KiB
C#
// ==++==
|
||
//
|
||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||
//
|
||
// ==--==
|
||
/*============================================================
|
||
**
|
||
** Class: Hashtable
|
||
**
|
||
** <OWNER>[....]</OWNER>
|
||
**
|
||
**
|
||
** Purpose: Hash table implementation
|
||
**
|
||
**
|
||
===========================================================*/
|
||
|
||
namespace System.Collections {
|
||
using System;
|
||
using System.Runtime;
|
||
using System.Runtime.Serialization;
|
||
using System.Security.Permissions;
|
||
using System.Diagnostics;
|
||
using System.Threading;
|
||
using System.Runtime.CompilerServices;
|
||
using System.Runtime.ConstrainedExecution;
|
||
using System.Diagnostics.Contracts;
|
||
using System.Security.Cryptography;
|
||
|
||
// The Hashtable class represents a dictionary of associated keys and values
|
||
// with constant lookup time.
|
||
//
|
||
// Objects used as keys in a hashtable must implement the GetHashCode
|
||
// and Equals methods (or they can rely on the default implementations
|
||
// inherited from Object if key equality is simply reference
|
||
// equality). Furthermore, the GetHashCode and Equals methods of
|
||
// a key object must produce the same results given the same parameters for the
|
||
// entire time the key is present in the hashtable. In practical terms, this
|
||
// means that key objects should be immutable, at least for the time they are
|
||
// used as keys in a hashtable.
|
||
//
|
||
// When entries are added to a hashtable, they are placed into
|
||
// buckets based on the hashcode of their keys. Subsequent lookups of
|
||
// keys will use the hashcode of the keys to only search a particular bucket,
|
||
// thus substantially reducing the number of key comparisons required to find
|
||
// an entry. A hashtable's maximum load factor, which can be specified
|
||
// when the hashtable is instantiated, determines the maximum ratio of
|
||
// hashtable entries to hashtable buckets. Smaller load factors cause faster
|
||
// average lookup times at the cost of increased memory consumption. The
|
||
// default maximum load factor of 1.0 generally provides the best balance
|
||
// between speed and size. As entries are added to a hashtable, the hashtable's
|
||
// actual load factor increases, and when the actual load factor reaches the
|
||
// maximum load factor value, the number of buckets in the hashtable is
|
||
// automatically increased by approximately a factor of two (to be precise, the
|
||
// number of hashtable buckets is increased to the smallest prime number that
|
||
// is larger than twice the current number of hashtable buckets).
|
||
//
|
||
// Each object provides their own hash function, accessed by calling
|
||
// GetHashCode(). However, one can write their own object
|
||
// implementing IEqualityComparer and pass it to a constructor on
|
||
// the Hashtable. That hash function (and the equals method on the
|
||
// IEqualityComparer) would be used for all objects in the table.
|
||
//
|
||
// Changes since V1 during Whidbey:
|
||
// *) Deprecated IHashCodeProvider, use IEqualityComparer instead. This will
|
||
// allow better performance for objects where equality checking can be
|
||
// done much faster than establishing an ordering between two objects,
|
||
// such as an ordinal string equality check.
|
||
//
|
||
[DebuggerTypeProxy(typeof(System.Collections.Hashtable.HashtableDebugView))]
|
||
[DebuggerDisplay("Count = {Count}")]
|
||
[System.Runtime.InteropServices.ComVisible(true)]
|
||
[Serializable]
|
||
public class Hashtable : IDictionary, ISerializable, IDeserializationCallback, ICloneable {
|
||
/*
|
||
Implementation Notes:
|
||
The generic Dictionary was copied from Hashtable's source - any bug
|
||
fixes here probably need to be made to the generic Dictionary as well.
|
||
|
||
This Hashtable uses double hashing. There are hashsize buckets in the
|
||
table, and each bucket can contain 0 or 1 element. We a bit to mark
|
||
whether there's been a collision when we inserted multiple elements
|
||
(ie, an inserted item was hashed at least a second time and we probed
|
||
this bucket, but it was already in use). Using the collision bit, we
|
||
can terminate lookups & removes for elements that aren't in the hash
|
||
table more quickly. We steal the most significant bit from the hash code
|
||
to store the collision bit.
|
||
|
||
Our hash function is of the following form:
|
||
|
||
h(key, n) = h1(key) + n*h2(key)
|
||
|
||
where n is the number of times we've hit a collided bucket and rehashed
|
||
(on this particular lookup). Here are our hash functions:
|
||
|
||
h1(key) = GetHash(key); // default implementation calls key.GetHashCode();
|
||
h2(key) = 1 + (((h1(key) >> 5) + 1) % (hashsize - 1));
|
||
|
||
The h1 can return any number. h2 must return a number between 1 and
|
||
hashsize - 1 that is relatively prime to hashsize (not a problem if
|
||
hashsize is prime). (Knuth's Art of Computer Programming, Vol. 3, p. 528-9)
|
||
If this is true, then we are guaranteed to visit every bucket in exactly
|
||
hashsize probes, since the least common multiple of hashsize and h2(key)
|
||
will be hashsize * h2(key). (This is the first number where adding h2 to
|
||
h1 mod hashsize will be 0 and we will search the same bucket twice).
|
||
|
||
We previously used a different h2(key, n) that was not constant. That is a
|
||
horrifically bad idea, unless you can prove that series will never produce
|
||
any identical numbers that overlap when you mod them by hashsize, for all
|
||
subranges from i to i+hashsize, for all i. It's not worth investigating,
|
||
since there was no clear benefit from using that hash function, and it was
|
||
broken.
|
||
|
||
For efficiency reasons, we've implemented this by storing h1 and h2 in a
|
||
temporary, and setting a variable called seed equal to h1. We do a probe,
|
||
and if we collided, we simply add h2 to seed each time through the loop.
|
||
|
||
A good test for h2() is to subclass Hashtable, provide your own implementation
|
||
of GetHash() that returns a constant, then add many items to the hash table.
|
||
Make sure Count equals the number of items you inserted.
|
||
|
||
Note that when we remove an item from the hash table, we set the key
|
||
equal to buckets, if there was a collision in this bucket. Otherwise
|
||
we'd either wipe out the collision bit, or we'd still have an item in
|
||
the hash table.
|
||
|
||
--
|
||
*/
|
||
|
||
internal const Int32 HashPrime = 101;
|
||
private const Int32 InitialSize = 3;
|
||
private const String LoadFactorName = "LoadFactor";
|
||
private const String VersionName = "Version";
|
||
private const String ComparerName = "Comparer";
|
||
private const String HashCodeProviderName = "HashCodeProvider";
|
||
private const String HashSizeName = "HashSize"; // Must save buckets.Length
|
||
private const String KeysName = "Keys";
|
||
private const String ValuesName = "Values";
|
||
private const String KeyComparerName = "KeyComparer";
|
||
|
||
// Deleted entries have their key set to buckets
|
||
|
||
// The hash table data.
|
||
// This cannot be serialised
|
||
private struct bucket {
|
||
public Object key;
|
||
public Object val;
|
||
public int hash_coll; // Store hash code; sign bit means there was a collision.
|
||
}
|
||
|
||
private bucket[] buckets;
|
||
|
||
// The total number of entries in the hash table.
|
||
private int count;
|
||
|
||
// The total number of collision bits set in the hashtable
|
||
private int occupancy;
|
||
|
||
private int loadsize;
|
||
private float loadFactor;
|
||
|
||
private volatile int version;
|
||
private volatile bool isWriterInProgress;
|
||
|
||
private ICollection keys;
|
||
private ICollection values;
|
||
|
||
private IEqualityComparer _keycomparer;
|
||
private Object _syncRoot;
|
||
|
||
[Obsolete("Please use EqualityComparer property.")]
|
||
protected IHashCodeProvider hcp
|
||
{
|
||
get
|
||
{
|
||
if( _keycomparer is CompatibleComparer) {
|
||
return ((CompatibleComparer)_keycomparer).HashCodeProvider;
|
||
}
|
||
else if( _keycomparer == null) {
|
||
return null;
|
||
}
|
||
else {
|
||
throw new ArgumentException(Environment.GetResourceString("Arg_CannotMixComparisonInfrastructure"));
|
||
}
|
||
}
|
||
set
|
||
{
|
||
if (_keycomparer is CompatibleComparer) {
|
||
CompatibleComparer keyComparer = (CompatibleComparer)_keycomparer;
|
||
_keycomparer = new CompatibleComparer(keyComparer.Comparer, value);
|
||
}
|
||
else if( _keycomparer == null) {
|
||
_keycomparer = new CompatibleComparer((IComparer)null, value);
|
||
}
|
||
else {
|
||
throw new ArgumentException(Environment.GetResourceString("Arg_CannotMixComparisonInfrastructure"));
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
[Obsolete("Please use KeyComparer properties.")]
|
||
protected IComparer comparer
|
||
{
|
||
get
|
||
{
|
||
if( _keycomparer is CompatibleComparer) {
|
||
return ((CompatibleComparer)_keycomparer).Comparer;
|
||
}
|
||
else if( _keycomparer == null) {
|
||
return null;
|
||
}
|
||
else {
|
||
throw new ArgumentException(Environment.GetResourceString("Arg_CannotMixComparisonInfrastructure"));
|
||
}
|
||
}
|
||
set
|
||
{
|
||
if (_keycomparer is CompatibleComparer) {
|
||
CompatibleComparer keyComparer = (CompatibleComparer)_keycomparer;
|
||
_keycomparer = new CompatibleComparer(value, keyComparer.HashCodeProvider);
|
||
}
|
||
else if( _keycomparer == null) {
|
||
_keycomparer = new CompatibleComparer(value, (IHashCodeProvider)null);
|
||
}
|
||
else {
|
||
throw new ArgumentException(Environment.GetResourceString("Arg_CannotMixComparisonInfrastructure"));
|
||
}
|
||
}
|
||
}
|
||
|
||
protected IEqualityComparer EqualityComparer
|
||
{
|
||
get
|
||
{
|
||
return _keycomparer;
|
||
}
|
||
}
|
||
|
||
// Note: this constructor is a bogus constructor that does nothing
|
||
// and is for use only with SyncHashtable.
|
||
internal Hashtable( bool trash )
|
||
{
|
||
}
|
||
|
||
// Constructs a new hashtable. The hashtable is created with an initial
|
||
// capacity of zero and a load factor of 1.0.
|
||
public Hashtable() : this(0, 1.0f) {
|
||
}
|
||
|
||
// Constructs a new hashtable with the given initial capacity and a load
|
||
// factor of 1.0. The capacity argument serves as an indication of
|
||
// the number of entries the hashtable will contain. When this number (or
|
||
// an approximation) is known, specifying it in the constructor can
|
||
// eliminate a number of resizing operations that would otherwise be
|
||
// performed when elements are added to the hashtable.
|
||
//
|
||
public Hashtable(int capacity) : this(capacity, 1.0f) {
|
||
}
|
||
|
||
// Constructs a new hashtable with the given initial capacity and load
|
||
// factor. The capacity argument serves as an indication of the
|
||
// number of entries the hashtable will contain. When this number (or an
|
||
// approximation) is known, specifying it in the constructor can eliminate
|
||
// a number of resizing operations that would otherwise be performed when
|
||
// elements are added to the hashtable. The loadFactor argument
|
||
// indicates the maximum ratio of hashtable entries to hashtable buckets.
|
||
// Smaller load factors cause faster average lookup times at the cost of
|
||
// increased memory consumption. A load factor of 1.0 generally provides
|
||
// the best balance between speed and size.
|
||
//
|
||
public Hashtable(int capacity, float loadFactor) {
|
||
if (capacity < 0)
|
||
throw new ArgumentOutOfRangeException("capacity", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum"));
|
||
if (!(loadFactor >= 0.1f && loadFactor <= 1.0f))
|
||
throw new ArgumentOutOfRangeException("loadFactor", Environment.GetResourceString("ArgumentOutOfRange_HashtableLoadFactor", .1, 1.0));
|
||
Contract.EndContractBlock();
|
||
|
||
// Based on perf work, .72 is the optimal load factor for this table.
|
||
this.loadFactor = 0.72f * loadFactor;
|
||
|
||
double rawsize = capacity / this.loadFactor;
|
||
if (rawsize > Int32.MaxValue)
|
||
throw new ArgumentException(Environment.GetResourceString("Arg_HTCapacityOverflow"));
|
||
|
||
// Avoid awfully small sizes
|
||
int hashsize = (rawsize > InitialSize) ? HashHelpers.GetPrime((int)rawsize) : InitialSize;
|
||
buckets = new bucket[hashsize];
|
||
|
||
loadsize = (int)(this.loadFactor * hashsize);
|
||
isWriterInProgress = false;
|
||
// Based on the current algorithm, loadsize must be less than hashsize.
|
||
Contract.Assert( loadsize < hashsize, "Invalid hashtable loadsize!");
|
||
}
|
||
|
||
// Constructs a new hashtable with the given initial capacity and load
|
||
// factor. The capacity argument serves as an indication of the
|
||
// number of entries the hashtable will contain. When this number (or an
|
||
// approximation) is known, specifying it in the constructor can eliminate
|
||
// a number of resizing operations that would otherwise be performed when
|
||
// elements are added to the hashtable. The loadFactor argument
|
||
// indicates the maximum ratio of hashtable entries to hashtable buckets.
|
||
// Smaller load factors cause faster average lookup times at the cost of
|
||
// increased memory consumption. A load factor of 1.0 generally provides
|
||
// the best balance between speed and size. The hcp argument
|
||
// is used to specify an Object that will provide hash codes for all
|
||
// the Objects in the table. Using this, you can in effect override
|
||
// GetHashCode() on each Object using your own hash function. The
|
||
// comparer argument will let you specify a custom function for
|
||
// comparing keys. By specifying user-defined objects for hcp
|
||
// and comparer, users could make a hash table using strings
|
||
// as keys do case-insensitive lookups.
|
||
//
|
||
[Obsolete("Please use Hashtable(int, float, IEqualityComparer) instead.")]
|
||
public Hashtable(int capacity, float loadFactor, IHashCodeProvider hcp, IComparer comparer) : this(capacity, loadFactor) {
|
||
if (hcp == null && comparer == null) {
|
||
this._keycomparer = null;
|
||
}
|
||
else {
|
||
this._keycomparer = new CompatibleComparer(comparer,hcp);
|
||
}
|
||
}
|
||
|
||
public Hashtable(int capacity, float loadFactor, IEqualityComparer equalityComparer) : this(capacity, loadFactor) {
|
||
this._keycomparer = equalityComparer;
|
||
}
|
||
|
||
// Constructs a new hashtable using a custom hash function
|
||
// and a custom comparison function for keys. This will enable scenarios
|
||
// such as doing lookups with case-insensitive strings.
|
||
//
|
||
[Obsolete("Please use Hashtable(IEqualityComparer) instead.")]
|
||
public Hashtable(IHashCodeProvider hcp, IComparer comparer) : this(0, 1.0f, hcp, comparer) {
|
||
}
|
||
|
||
public Hashtable(IEqualityComparer equalityComparer) : this(0, 1.0f, equalityComparer) {
|
||
}
|
||
|
||
// Constructs a new hashtable using a custom hash function
|
||
// and a custom comparison function for keys. This will enable scenarios
|
||
// such as doing lookups with case-insensitive strings.
|
||
//
|
||
[Obsolete("Please use Hashtable(int, IEqualityComparer) instead.")]
|
||
public Hashtable(int capacity, IHashCodeProvider hcp, IComparer comparer)
|
||
: this(capacity, 1.0f, hcp, comparer) {
|
||
}
|
||
|
||
public Hashtable(int capacity, IEqualityComparer equalityComparer)
|
||
: this(capacity, 1.0f, equalityComparer) {
|
||
}
|
||
|
||
// Constructs a new hashtable containing a copy of the entries in the given
|
||
// dictionary. The hashtable is created with a load factor of 1.0.
|
||
//
|
||
public Hashtable(IDictionary d) : this(d, 1.0f) {
|
||
}
|
||
|
||
// Constructs a new hashtable containing a copy of the entries in the given
|
||
// dictionary. The hashtable is created with the given load factor.
|
||
//
|
||
public Hashtable(IDictionary d, float loadFactor)
|
||
: this(d, loadFactor, (IEqualityComparer)null) {
|
||
}
|
||
|
||
[Obsolete("Please use Hashtable(IDictionary, IEqualityComparer) instead.")]
|
||
public Hashtable(IDictionary d, IHashCodeProvider hcp, IComparer comparer)
|
||
: this(d, 1.0f, hcp, comparer) {
|
||
}
|
||
|
||
public Hashtable(IDictionary d, IEqualityComparer equalityComparer)
|
||
: this(d, 1.0f, equalityComparer) {
|
||
}
|
||
|
||
[Obsolete("Please use Hashtable(IDictionary, float, IEqualityComparer) instead.")]
|
||
public Hashtable(IDictionary d, float loadFactor, IHashCodeProvider hcp, IComparer comparer)
|
||
: this((d != null ? d.Count : 0), loadFactor, hcp, comparer) {
|
||
if (d==null)
|
||
throw new ArgumentNullException("d", Environment.GetResourceString("ArgumentNull_Dictionary"));
|
||
Contract.EndContractBlock();
|
||
|
||
IDictionaryEnumerator e = d.GetEnumerator();
|
||
while (e.MoveNext()) Add(e.Key, e.Value);
|
||
}
|
||
|
||
public Hashtable(IDictionary d, float loadFactor, IEqualityComparer equalityComparer)
|
||
: this((d != null ? d.Count : 0), loadFactor, equalityComparer) {
|
||
if (d==null)
|
||
throw new ArgumentNullException("d", Environment.GetResourceString("ArgumentNull_Dictionary"));
|
||
Contract.EndContractBlock();
|
||
|
||
IDictionaryEnumerator e = d.GetEnumerator();
|
||
while (e.MoveNext()) Add(e.Key, e.Value);
|
||
}
|
||
|
||
protected Hashtable(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 reasonable 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.
|
||
HashHelpers.SerializationInfoTable.Add(this, info);
|
||
}
|
||
|
||
// <20>InitHash<73> is basically an implementation of classic DoubleHashing (see http://en.wikipedia.org/wiki/Double_hashing)
|
||
//
|
||
// 1) The only <20>correctness<73> requirement is that the <20>increment<6E> used to probe
|
||
// a. Be non-zero
|
||
// b. Be relatively prime to the table size <20>hashSize<7A>. (This is needed to insure you probe all entries in the table before you <20>wrap<61> and visit entries already probed)
|
||
// 2) Because we choose table sizes to be primes, we just need to insure that the increment is 0 < incr < hashSize
|
||
//
|
||
// Thus this function would work: Incr = 1 + (seed % (hashSize-1))
|
||
//
|
||
// While this works well for <20>uniformly distributed<65> keys, in practice, non-uniformity is common.
|
||
// In particular in practice we can see <20>mostly sequential<61> where you get long clusters of keys that <20>pack<63>.
|
||
// To avoid bad behavior you want it to be the case that the increment is <20>large<67> even for <20>small<6C> values (because small
|
||
// values tend to happen more in practice). Thus we multiply <20>seed<65> by a number that will make these small values
|
||
// bigger (and not hurt large values). We picked HashPrime (101) because it was prime, and if <20>hashSize-1<> is not a multiple of HashPrime
|
||
// (enforced in GetPrime), then incr has the potential of being every value from 1 to hashSize-1. The choice was largely arbitrary.
|
||
//
|
||
// Computes the hash function: H(key, i) = h1(key) + i*h2(key, hashSize).
|
||
// The out parameter seed is h1(key), while the out parameter
|
||
// incr is h2(key, hashSize). Callers of this function should
|
||
// add incr each time through a loop.
|
||
private uint InitHash(Object key, int hashsize, out uint seed, out uint incr) {
|
||
// Hashcode must be positive. Also, we must not use the sign bit, since
|
||
// that is used for the collision bit.
|
||
uint hashcode = (uint) GetHash(key) & 0x7FFFFFFF;
|
||
seed = (uint) hashcode;
|
||
// Restriction: incr MUST be between 1 and hashsize - 1, inclusive for
|
||
// the modular arithmetic to work correctly. This guarantees you'll
|
||
// visit every bucket in the table exactly once within hashsize
|
||
// iterations. Violate this and it'll cause obscure bugs forever.
|
||
// If you change this calculation for h2(key), update putEntry too!
|
||
incr = (uint)(1 + ((seed * HashPrime) % ((uint)hashsize - 1)));
|
||
return hashcode;
|
||
}
|
||
|
||
// Adds an entry with the given key and value to this hashtable. An
|
||
// ArgumentException is thrown if the key is null or if the key is already
|
||
// present in the hashtable.
|
||
//
|
||
#if !FEATURE_CORECLR
|
||
[TargetedPatchingOptOut("Performance critical to inline across NGen image boundaries")]
|
||
#endif
|
||
public virtual void Add(Object key, Object value) {
|
||
Insert(key, value, true);
|
||
}
|
||
|
||
// Removes all entries from this hashtable.
|
||
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
|
||
public virtual void Clear() {
|
||
Contract.Assert(!isWriterInProgress, "Race condition detected in usages of Hashtable - multiple threads appear to be writing to a Hashtable instance simultaneously! Don't do that - use Hashtable.Synchronized.");
|
||
|
||
if (count == 0 && occupancy == 0)
|
||
return;
|
||
|
||
#if !FEATURE_CORECLR
|
||
Thread.BeginCriticalRegion();
|
||
#endif
|
||
isWriterInProgress = true;
|
||
for (int i = 0; i < buckets.Length; i++){
|
||
buckets[i].hash_coll = 0;
|
||
buckets[i].key = null;
|
||
buckets[i].val = null;
|
||
}
|
||
|
||
count = 0;
|
||
occupancy = 0;
|
||
UpdateVersion();
|
||
isWriterInProgress = false;
|
||
#if !FEATURE_CORECLR
|
||
Thread.EndCriticalRegion();
|
||
#endif
|
||
}
|
||
|
||
// Clone returns a virtually identical copy of this hash table. This does
|
||
// a shallow copy - the Objects in the table aren't cloned, only the references
|
||
// to those Objects.
|
||
public virtual Object Clone()
|
||
{
|
||
bucket[] lbuckets = buckets;
|
||
Hashtable ht = new Hashtable(count,_keycomparer);
|
||
ht.version = version;
|
||
ht.loadFactor = loadFactor;
|
||
ht.count = 0;
|
||
|
||
int bucket = lbuckets.Length;
|
||
while (bucket > 0) {
|
||
bucket--;
|
||
Object keyv = lbuckets[bucket].key;
|
||
if ((keyv!= null) && (keyv != lbuckets)) {
|
||
ht[keyv] = lbuckets[bucket].val;
|
||
}
|
||
}
|
||
|
||
return ht;
|
||
}
|
||
|
||
// Checks if this hashtable contains the given key.
|
||
#if !FEATURE_CORECLR
|
||
[TargetedPatchingOptOut("Performance critical to inline across NGen image boundaries")]
|
||
#endif
|
||
public virtual bool Contains(Object key) {
|
||
return ContainsKey(key);
|
||
}
|
||
|
||
// Checks if this hashtable contains an entry with the given key. This is
|
||
// an O(1) operation.
|
||
//
|
||
public virtual bool ContainsKey(Object key) {
|
||
if (key == null) {
|
||
throw new ArgumentNullException("key", Environment.GetResourceString("ArgumentNull_Key"));
|
||
}
|
||
Contract.EndContractBlock();
|
||
|
||
uint seed;
|
||
uint incr;
|
||
// Take a snapshot of buckets, in case another thread resizes table
|
||
bucket[] lbuckets = buckets;
|
||
uint hashcode = InitHash(key, lbuckets.Length, out seed, out incr);
|
||
int ntry = 0;
|
||
|
||
bucket b;
|
||
int bucketNumber = (int) (seed % (uint)lbuckets.Length);
|
||
do {
|
||
b = lbuckets[bucketNumber];
|
||
if (b.key == null) {
|
||
return false;
|
||
}
|
||
if (((b.hash_coll & 0x7FFFFFFF) == hashcode) &&
|
||
KeyEquals (b.key, key))
|
||
return true;
|
||
bucketNumber = (int) (((long)bucketNumber + incr)% (uint)lbuckets.Length);
|
||
} while (b.hash_coll < 0 && ++ntry < lbuckets.Length);
|
||
return false;
|
||
}
|
||
|
||
|
||
|
||
// Checks if this hashtable contains an entry with the given value. The
|
||
// values of the entries of the hashtable are compared to the given value
|
||
// using the Object.Equals method. This method performs a linear
|
||
// search and is thus be substantially slower than the ContainsKey
|
||
// method.
|
||
//
|
||
public virtual bool ContainsValue(Object value) {
|
||
|
||
if (value == null) {
|
||
for (int i = buckets.Length; --i >= 0;) {
|
||
if (buckets[i].key != null && buckets[i].key != buckets && buckets[i].val == null)
|
||
return true;
|
||
}
|
||
}
|
||
else {
|
||
for (int i = buckets.Length; --i >= 0;) {
|
||
Object val = buckets[i].val;
|
||
if (val!=null && val.Equals(value)) return true;
|
||
}
|
||
}
|
||
return false;
|
||
}
|
||
|
||
// Copies the keys of this hashtable to a given array starting at a given
|
||
// index. This method is used by the implementation of the CopyTo method in
|
||
// the KeyCollection class.
|
||
private void CopyKeys(Array array, int arrayIndex) {
|
||
Contract.Requires(array != null);
|
||
Contract.Requires(array.Rank == 1);
|
||
|
||
bucket[] lbuckets = buckets;
|
||
for (int i = lbuckets.Length; --i >= 0;) {
|
||
Object keyv = lbuckets[i].key;
|
||
if ((keyv != null) && (keyv != buckets)){
|
||
array.SetValue(keyv, arrayIndex++);
|
||
}
|
||
}
|
||
}
|
||
|
||
// Copies the keys of this hashtable to a given array starting at a given
|
||
// index. This method is used by the implementation of the CopyTo method in
|
||
// the KeyCollection class.
|
||
private void CopyEntries(Array array, int arrayIndex) {
|
||
Contract.Requires(array != null);
|
||
Contract.Requires(array.Rank == 1);
|
||
|
||
bucket[] lbuckets = buckets;
|
||
for (int i = lbuckets.Length; --i >= 0;) {
|
||
Object keyv = lbuckets[i].key;
|
||
if ((keyv != null) && (keyv != buckets)){
|
||
DictionaryEntry entry = new DictionaryEntry(keyv,lbuckets[i].val);
|
||
array.SetValue(entry, arrayIndex++);
|
||
}
|
||
}
|
||
}
|
||
|
||
// Copies the values in this hash table to an array at
|
||
// a given index. Note that this only copies values, and not keys.
|
||
public virtual void CopyTo(Array array, int arrayIndex)
|
||
{
|
||
if (array == null)
|
||
throw new ArgumentNullException("array", Environment.GetResourceString("ArgumentNull_Array"));
|
||
if (array.Rank != 1)
|
||
throw new ArgumentException(Environment.GetResourceString("Arg_RankMultiDimNotSupported"));
|
||
if (arrayIndex < 0)
|
||
throw new ArgumentOutOfRangeException("arrayIndex", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum"));
|
||
if (array.Length - arrayIndex < Count)
|
||
throw new ArgumentException(Environment.GetResourceString("Arg_ArrayPlusOffTooSmall"));
|
||
Contract.EndContractBlock();
|
||
CopyEntries(array, arrayIndex);
|
||
}
|
||
|
||
// Copies the values in this Hashtable to an KeyValuePairs array.
|
||
// KeyValuePairs is different from Dictionary Entry in that it has special
|
||
// debugger attributes on its fields.
|
||
|
||
internal virtual KeyValuePairs[] ToKeyValuePairsArray() {
|
||
|
||
KeyValuePairs[] array = new KeyValuePairs[count];
|
||
int index = 0;
|
||
bucket[] lbuckets = buckets;
|
||
for (int i = lbuckets.Length; --i >= 0;) {
|
||
Object keyv = lbuckets[i].key;
|
||
if ((keyv != null) && (keyv != buckets)){
|
||
array[index++] = new KeyValuePairs(keyv,lbuckets[i].val);
|
||
}
|
||
}
|
||
|
||
return array;
|
||
}
|
||
|
||
|
||
// Copies the values of this hashtable to a given array starting at a given
|
||
// index. This method is used by the implementation of the CopyTo method in
|
||
// the ValueCollection class.
|
||
private void CopyValues(Array array, int arrayIndex) {
|
||
Contract.Requires(array != null);
|
||
Contract.Requires(array.Rank == 1);
|
||
|
||
bucket[] lbuckets = buckets;
|
||
for (int i = lbuckets.Length; --i >= 0;) {
|
||
Object keyv = lbuckets[i].key;
|
||
if ((keyv != null) && (keyv != buckets)){
|
||
array.SetValue(lbuckets[i].val, arrayIndex++);
|
||
}
|
||
}
|
||
}
|
||
|
||
// Returns the value associated with the given key. If an entry with the
|
||
// given key is not found, the returned value is null.
|
||
//
|
||
public virtual Object this[Object key] {
|
||
get {
|
||
if (key == null) {
|
||
throw new ArgumentNullException("key", Environment.GetResourceString("ArgumentNull_Key"));
|
||
}
|
||
Contract.EndContractBlock();
|
||
|
||
uint seed;
|
||
uint incr;
|
||
|
||
|
||
// Take a snapshot of buckets, in case another thread does a resize
|
||
bucket[] lbuckets = buckets;
|
||
uint hashcode = InitHash(key, lbuckets.Length, out seed, out incr);
|
||
int ntry = 0;
|
||
|
||
bucket b;
|
||
int bucketNumber = (int) (seed % (uint)lbuckets.Length);
|
||
do
|
||
{
|
||
int currentversion;
|
||
|
||
// A read operation on hashtable has three steps:
|
||
// (1) calculate the hash and find the slot number.
|
||
// (2) compare the hashcode, if equal, go to step 3. Otherwise end.
|
||
// (3) compare the key, if equal, go to step 4. Otherwise end.
|
||
// (4) return the value contained in the bucket.
|
||
// After step 3 and before step 4. A writer can kick in a remove the old item and add a new one
|
||
// in the same bukcet. So in the reader we need to check if the hash table is modified during above steps.
|
||
//
|
||
// Writers (Insert, Remove, Clear) will set 'isWriterInProgress' flag before it starts modifying
|
||
// the hashtable and will ckear the flag when it is done. When the flag is cleared, the 'version'
|
||
// will be increased. We will repeat the reading if a writer is in progress or done with the modification
|
||
// during the read.
|
||
//
|
||
// Our memory model guarantee if we pick up the change in bucket from another processor,
|
||
// we will see the 'isWriterProgress' flag to be true or 'version' is changed in the reader.
|
||
//
|
||
int spinCount = 0;
|
||
do {
|
||
// this is violate read, following memory accesses can not be moved ahead of it.
|
||
currentversion = version;
|
||
b = lbuckets[bucketNumber];
|
||
|
||
// The contention between reader and writer shouldn't happen frequently.
|
||
// But just in case this will burn CPU, yield the control of CPU if we spinned a few times.
|
||
// 8 is just a random number I pick.
|
||
if( (++spinCount) % 8 == 0 ) {
|
||
Thread.Sleep(1); // 1 means we are yeilding control to all threads, including low-priority ones.
|
||
}
|
||
} while ( isWriterInProgress || (currentversion != version) );
|
||
|
||
if (b.key == null) {
|
||
return null;
|
||
}
|
||
if (((b.hash_coll & 0x7FFFFFFF) == hashcode) &&
|
||
KeyEquals (b.key, key))
|
||
return b.val;
|
||
bucketNumber = (int) (((long)bucketNumber + incr)% (uint)lbuckets.Length);
|
||
} while (b.hash_coll < 0 && ++ntry < lbuckets.Length);
|
||
return null;
|
||
}
|
||
|
||
#if !FEATURE_CORECLR
|
||
[TargetedPatchingOptOut("Performance critical to inline across NGen image boundaries")]
|
||
#endif
|
||
set {
|
||
Insert(key, value, false);
|
||
}
|
||
}
|
||
|
||
// Increases the bucket count of this hashtable. This method is called from
|
||
// the Insert method when the actual load factor of the hashtable reaches
|
||
// the upper limit specified when the hashtable was constructed. The number
|
||
// of buckets in the hashtable is increased to the smallest prime number
|
||
// that is larger than twice the current number of buckets, and the entries
|
||
// in the hashtable are redistributed into the new buckets using the cached
|
||
// hashcodes.
|
||
private void expand() {
|
||
int rawsize = HashHelpers.ExpandPrime(buckets.Length);
|
||
rehash(rawsize, false);
|
||
}
|
||
|
||
// We occationally need to rehash the table to clean up the collision bits.
|
||
private void rehash() {
|
||
rehash( buckets.Length, false );
|
||
}
|
||
|
||
private void UpdateVersion() {
|
||
// Version might become negative when version is Int32.MaxValue, but the oddity will be still be correct.
|
||
// So we don't need to special case this.
|
||
version++;
|
||
}
|
||
|
||
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
|
||
private void rehash( int newsize, bool forceNewHashCode ) {
|
||
|
||
// reset occupancy
|
||
occupancy=0;
|
||
|
||
// Don't replace any internal state until we've finished adding to the
|
||
// new bucket[]. This serves two purposes:
|
||
// 1) Allow concurrent readers to see valid hashtable contents
|
||
// at all times
|
||
// 2) Protect against an OutOfMemoryException while allocating this
|
||
// new bucket[].
|
||
bucket[] newBuckets = new bucket[newsize];
|
||
|
||
// rehash table into new buckets
|
||
int nb;
|
||
for (nb = 0; nb < buckets.Length; nb++){
|
||
bucket oldb = buckets[nb];
|
||
if ((oldb.key != null) && (oldb.key != buckets)) {
|
||
int hashcode = ((forceNewHashCode ? GetHash(oldb.key) : oldb.hash_coll) & 0x7FFFFFFF);
|
||
putEntry(newBuckets, oldb.key, oldb.val, hashcode);
|
||
}
|
||
}
|
||
|
||
// New bucket[] is good to go - replace buckets and other internal state.
|
||
#if !FEATURE_CORECLR
|
||
Thread.BeginCriticalRegion();
|
||
#endif
|
||
isWriterInProgress = true;
|
||
buckets = newBuckets;
|
||
loadsize = (int)(loadFactor * newsize);
|
||
UpdateVersion();
|
||
isWriterInProgress = false;
|
||
#if !FEATURE_CORECLR
|
||
Thread.EndCriticalRegion();
|
||
#endif
|
||
// minimun size of hashtable is 3 now and maximum loadFactor is 0.72 now.
|
||
Contract.Assert(loadsize < newsize, "Our current implementaion means this is not possible.");
|
||
return;
|
||
}
|
||
|
||
// Returns an enumerator for this hashtable.
|
||
// If modifications made to the hashtable while an enumeration is
|
||
// in progress, the MoveNext and Current methods of the
|
||
// enumerator will throw an exception.
|
||
//
|
||
#if !FEATURE_CORECLR
|
||
[TargetedPatchingOptOut("Performance critical to inline across NGen image boundaries")]
|
||
#endif
|
||
IEnumerator IEnumerable.GetEnumerator() {
|
||
return new HashtableEnumerator(this, HashtableEnumerator.DictEntry);
|
||
}
|
||
|
||
// Returns a dictionary enumerator for this hashtable.
|
||
// If modifications made to the hashtable while an enumeration is
|
||
// in progress, the MoveNext and Current methods of the
|
||
// enumerator will throw an exception.
|
||
//
|
||
#if !FEATURE_CORECLR
|
||
[TargetedPatchingOptOut("Performance critical to inline across NGen image boundaries")]
|
||
#endif
|
||
public virtual IDictionaryEnumerator GetEnumerator() {
|
||
return new HashtableEnumerator(this, HashtableEnumerator.DictEntry);
|
||
}
|
||
|
||
// Internal method to get the hash code for an Object. This will call
|
||
// GetHashCode() on each object if you haven't provided an IHashCodeProvider
|
||
// instance. Otherwise, it calls hcp.GetHashCode(obj).
|
||
#if !FEATURE_CORECLR
|
||
[TargetedPatchingOptOut("Performance critical to inline across NGen image boundaries")]
|
||
#endif
|
||
protected virtual int GetHash(Object key)
|
||
{
|
||
if (_keycomparer != null)
|
||
return _keycomparer.GetHashCode(key);
|
||
return key.GetHashCode();
|
||
}
|
||
|
||
// Is this Hashtable read-only?
|
||
public virtual bool IsReadOnly {
|
||
get { return false; }
|
||
}
|
||
|
||
public virtual bool IsFixedSize {
|
||
get { return false; }
|
||
}
|
||
|
||
// Is this Hashtable synchronized? See SyncRoot property
|
||
public virtual bool IsSynchronized {
|
||
get { return false; }
|
||
}
|
||
|
||
// Internal method to compare two keys. If you have provided an IComparer
|
||
// instance in the constructor, this method will call comparer.Compare(item, key).
|
||
// Otherwise, it will call item.Equals(key).
|
||
//
|
||
protected virtual bool KeyEquals(Object item, Object key)
|
||
{
|
||
Contract.Assert(key != null, "key can't be null here!");
|
||
if( Object.ReferenceEquals(buckets, item)) {
|
||
return false;
|
||
}
|
||
|
||
if (Object.ReferenceEquals(item,key))
|
||
return true;
|
||
|
||
if (_keycomparer != null)
|
||
return _keycomparer.Equals(item, key);
|
||
return item == null ? false : item.Equals(key);
|
||
}
|
||
|
||
// Returns a collection representing the keys of this hashtable. The order
|
||
// in which the returned collection represents the keys is unspecified, but
|
||
// it is guaranteed to be buckets = newBuckets; the same order in which a collection returned by
|
||
// GetValues represents the values of the hashtable.
|
||
//
|
||
// The returned collection is live in the sense that any changes
|
||
// to the hash table are reflected in this collection. It is not
|
||
// a static copy of all the keys in the hash table.
|
||
//
|
||
public virtual ICollection Keys {
|
||
#if !FEATURE_CORECLR
|
||
[TargetedPatchingOptOut("Performance critical to inline across NGen image boundaries")]
|
||
#endif
|
||
get {
|
||
if (keys == null) keys = new KeyCollection(this);
|
||
return keys;
|
||
}
|
||
}
|
||
|
||
// Returns a collection representing the values of this hashtable. The
|
||
// order in which the returned collection represents the values is
|
||
// unspecified, but it is guaranteed to be the same order in which a
|
||
// collection returned by GetKeys represents the keys of the
|
||
// hashtable.
|
||
//
|
||
// The returned collection is live in the sense that any changes
|
||
// to the hash table are reflected in this collection. It is not
|
||
// a static copy of all the keys in the hash table.
|
||
//
|
||
public virtual ICollection Values {
|
||
#if !FEATURE_CORECLR
|
||
[TargetedPatchingOptOut("Performance critical to inline across NGen image boundaries")]
|
||
#endif
|
||
get {
|
||
if (values == null) values = new ValueCollection(this);
|
||
return values;
|
||
}
|
||
}
|
||
|
||
// Inserts an entry into this hashtable. This method is called from the Set
|
||
// and Add methods. If the add parameter is true and the given key already
|
||
// exists in the hashtable, an exception is thrown.
|
||
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
|
||
private void Insert (Object key, Object nvalue, bool add) {
|
||
// @
|
||
|
||
|
||
if (key == null) {
|
||
throw new ArgumentNullException("key", Environment.GetResourceString("ArgumentNull_Key"));
|
||
}
|
||
Contract.EndContractBlock();
|
||
if (count >= loadsize) {
|
||
expand();
|
||
}
|
||
else if(occupancy > loadsize && count > 100) {
|
||
rehash();
|
||
}
|
||
|
||
uint seed;
|
||
uint incr;
|
||
// Assume we only have one thread writing concurrently. Modify
|
||
// buckets to contain new data, as long as we insert in the right order.
|
||
uint hashcode = InitHash(key, buckets.Length, out seed, out incr);
|
||
int ntry = 0;
|
||
int emptySlotNumber = -1; // We use the empty slot number to cache the first empty slot. We chose to reuse slots
|
||
// create by remove that have the collision bit set over using up new slots.
|
||
int bucketNumber = (int) (seed % (uint)buckets.Length);
|
||
do {
|
||
|
||
// Set emptySlot number to current bucket if it is the first available bucket that we have seen
|
||
// that once contained an entry and also has had a collision.
|
||
// We need to search this entire collision chain because we have to ensure that there are no
|
||
// duplicate entries in the table.
|
||
if (emptySlotNumber == -1 && (buckets[bucketNumber].key == buckets) && (buckets[bucketNumber].hash_coll < 0))//(((buckets[bucketNumber].hash_coll & unchecked(0x80000000))!=0)))
|
||
emptySlotNumber = bucketNumber;
|
||
|
||
// Insert the key/value pair into this bucket if this bucket is empty and has never contained an entry
|
||
// OR
|
||
// This bucket once contained an entry but there has never been a collision
|
||
if ((buckets[bucketNumber].key == null) ||
|
||
(buckets[bucketNumber].key == buckets && ((buckets[bucketNumber].hash_coll & unchecked(0x80000000))==0))) {
|
||
|
||
// If we have found an available bucket that has never had a collision, but we've seen an available
|
||
// bucket in the past that has the collision bit set, use the previous bucket instead
|
||
if (emptySlotNumber != -1) // Reuse slot
|
||
bucketNumber = emptySlotNumber;
|
||
|
||
// We pretty much have to insert in this order. Don't set hash
|
||
// code until the value & key are set appropriately.
|
||
#if !FEATURE_CORECLR
|
||
Thread.BeginCriticalRegion();
|
||
#endif
|
||
isWriterInProgress = true;
|
||
buckets[bucketNumber].val = nvalue;
|
||
buckets[bucketNumber].key = key;
|
||
buckets[bucketNumber].hash_coll |= (int) hashcode;
|
||
count++;
|
||
UpdateVersion();
|
||
isWriterInProgress = false;
|
||
#if !FEATURE_CORECLR
|
||
Thread.EndCriticalRegion();
|
||
#endif
|
||
|
||
#if FEATURE_RANDOMIZED_STRING_HASHING
|
||
if(ntry > HashHelpers.HashCollisionThreshold && HashHelpers.IsWellKnownEqualityComparer(_keycomparer))
|
||
{
|
||
// PERF: We don't want to rehash if _keycomparer is already a RandomizedObjectEqualityComparer since in some
|
||
// cases there may not be any strings in the hashtable and we wouldn't get any mixing.
|
||
if(_keycomparer == null || !(_keycomparer is System.Collections.Generic.RandomizedObjectEqualityComparer))
|
||
{
|
||
_keycomparer = HashHelpers.GetRandomizedEqualityComparer(_keycomparer);
|
||
rehash(buckets.Length, true);
|
||
}
|
||
}
|
||
#endif
|
||
|
||
return;
|
||
}
|
||
|
||
// The current bucket is in use
|
||
// OR
|
||
// it is available, but has had the collision bit set and we have already found an available bucket
|
||
if (((buckets[bucketNumber].hash_coll & 0x7FFFFFFF) == hashcode) &&
|
||
KeyEquals (buckets[bucketNumber].key, key)) {
|
||
if (add) {
|
||
throw new ArgumentException(Environment.GetResourceString("Argument_AddingDuplicate__", buckets[bucketNumber].key, key));
|
||
}
|
||
#if !FEATURE_CORECLR
|
||
Thread.BeginCriticalRegion();
|
||
#endif
|
||
isWriterInProgress = true;
|
||
buckets[bucketNumber].val = nvalue;
|
||
UpdateVersion();
|
||
isWriterInProgress = false;
|
||
#if !FEATURE_CORECLR
|
||
Thread.EndCriticalRegion();
|
||
#endif
|
||
|
||
#if FEATURE_RANDOMIZED_STRING_HASHING
|
||
if(ntry > HashHelpers.HashCollisionThreshold && HashHelpers.IsWellKnownEqualityComparer(_keycomparer))
|
||
{
|
||
// PERF: We don't want to rehash if _keycomparer is already a RandomizedObjectEqualityComparer since in some
|
||
// cases there may not be any strings in the hashtable and we wouldn't get any mixing.
|
||
if(_keycomparer == null || !(_keycomparer is System.Collections.Generic.RandomizedObjectEqualityComparer))
|
||
{
|
||
_keycomparer = HashHelpers.GetRandomizedEqualityComparer(_keycomparer);
|
||
rehash(buckets.Length, true);
|
||
}
|
||
}
|
||
#endif
|
||
return;
|
||
}
|
||
|
||
// The current bucket is full, and we have therefore collided. We need to set the collision bit
|
||
// UNLESS
|
||
// we have remembered an available slot previously.
|
||
if (emptySlotNumber == -1) {// We don't need to set the collision bit here since we already have an empty slot
|
||
if( buckets[bucketNumber].hash_coll >= 0 ) {
|
||
buckets[bucketNumber].hash_coll |= unchecked((int)0x80000000);
|
||
occupancy++;
|
||
}
|
||
}
|
||
|
||
bucketNumber = (int) (((long)bucketNumber + incr)% (uint)buckets.Length);
|
||
} while (++ntry < buckets.Length);
|
||
|
||
// This code is here if and only if there were no buckets without a collision bit set in the entire table
|
||
if (emptySlotNumber != -1)
|
||
{
|
||
// We pretty much have to insert in this order. Don't set hash
|
||
// code until the value & key are set appropriately.
|
||
#if !FEATURE_CORECLR
|
||
Thread.BeginCriticalRegion();
|
||
#endif
|
||
isWriterInProgress = true;
|
||
buckets[emptySlotNumber].val = nvalue;
|
||
buckets[emptySlotNumber].key = key;
|
||
buckets[emptySlotNumber].hash_coll |= (int) hashcode;
|
||
count++;
|
||
UpdateVersion();
|
||
isWriterInProgress = false;
|
||
#if !FEATURE_CORECLR
|
||
Thread.EndCriticalRegion();
|
||
#endif
|
||
|
||
#if FEATURE_RANDOMIZED_STRING_HASHING
|
||
if(buckets.Length > HashHelpers.HashCollisionThreshold && HashHelpers.IsWellKnownEqualityComparer(_keycomparer))
|
||
{
|
||
// PERF: We don't want to rehash if _keycomparer is already a RandomizedObjectEqualityComparer since in some
|
||
// cases there may not be any strings in the hashtable and we wouldn't get any mixing.
|
||
if(_keycomparer == null || !(_keycomparer is System.Collections.Generic.RandomizedObjectEqualityComparer))
|
||
{
|
||
_keycomparer = HashHelpers.GetRandomizedEqualityComparer(_keycomparer);
|
||
rehash(buckets.Length, true);
|
||
}
|
||
}
|
||
#endif
|
||
return;
|
||
}
|
||
|
||
// If you see this assert, make sure load factor & count are reasonable.
|
||
// Then verify that our double hash function (h2, described at top of file)
|
||
// meets the requirements described above. You should never see this assert.
|
||
Contract.Assert(false, "hash table insert failed! Load factor too high, or our double hashing function is incorrect.");
|
||
throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_HashInsertFailed"));
|
||
}
|
||
|
||
private void putEntry (bucket[] newBuckets, Object key, Object nvalue, int hashcode)
|
||
{
|
||
Contract.Assert(hashcode >= 0, "hashcode >= 0"); // make sure collision bit (sign bit) wasn't set.
|
||
|
||
uint seed = (uint) hashcode;
|
||
uint incr = (uint)(1 + ((seed * HashPrime) % ((uint)newBuckets.Length - 1)));
|
||
int bucketNumber = (int) (seed % (uint)newBuckets.Length);
|
||
do {
|
||
|
||
if ((newBuckets[bucketNumber].key == null) || (newBuckets[bucketNumber].key == buckets)) {
|
||
newBuckets[bucketNumber].val = nvalue;
|
||
newBuckets[bucketNumber].key = key;
|
||
newBuckets[bucketNumber].hash_coll |= hashcode;
|
||
return;
|
||
}
|
||
|
||
if( newBuckets[bucketNumber].hash_coll >= 0 ) {
|
||
newBuckets[bucketNumber].hash_coll |= unchecked((int)0x80000000);
|
||
occupancy++;
|
||
}
|
||
bucketNumber = (int) (((long)bucketNumber + incr)% (uint)newBuckets.Length);
|
||
} while (true);
|
||
}
|
||
|
||
// Removes an entry from this hashtable. If an entry with the specified
|
||
// key exists in the hashtable, it is removed. An ArgumentException is
|
||
// thrown if the key is null.
|
||
//
|
||
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
|
||
public virtual void Remove(Object key) {
|
||
if (key == null) {
|
||
throw new ArgumentNullException("key", Environment.GetResourceString("ArgumentNull_Key"));
|
||
}
|
||
Contract.EndContractBlock();
|
||
Contract.Assert(!isWriterInProgress, "Race condition detected in usages of Hashtable - multiple threads appear to be writing to a Hashtable instance simultaneously! Don't do that - use Hashtable.Synchronized.");
|
||
|
||
uint seed;
|
||
uint incr;
|
||
// Assuming only one concurrent writer, write directly into buckets.
|
||
uint hashcode = InitHash(key, buckets.Length, out seed, out incr);
|
||
int ntry = 0;
|
||
|
||
bucket b;
|
||
int bn = (int) (seed % (uint)buckets.Length); // bucketNumber
|
||
do {
|
||
b = buckets[bn];
|
||
if (((b.hash_coll & 0x7FFFFFFF) == hashcode) &&
|
||
KeyEquals (b.key, key)) {
|
||
#if !FEATURE_CORECLR
|
||
Thread.BeginCriticalRegion();
|
||
#endif
|
||
isWriterInProgress = true;
|
||
// Clear hash_coll field, then key, then value
|
||
buckets[bn].hash_coll &= unchecked((int)0x80000000);
|
||
if (buckets[bn].hash_coll != 0) {
|
||
buckets[bn].key = buckets;
|
||
}
|
||
else {
|
||
buckets[bn].key = null;
|
||
}
|
||
buckets[bn].val = null; // Free object references sooner & simplify ContainsValue.
|
||
count--;
|
||
UpdateVersion();
|
||
isWriterInProgress = false;
|
||
#if !FEATURE_CORECLR
|
||
Thread.EndCriticalRegion();
|
||
#endif
|
||
return;
|
||
}
|
||
bn = (int) (((long)bn + incr)% (uint)buckets.Length);
|
||
} while (b.hash_coll < 0 && ++ntry < buckets.Length);
|
||
|
||
//throw new ArgumentException(Environment.GetResourceString("Arg_RemoveArgNotFound"));
|
||
}
|
||
|
||
// Returns the object to synchronize on for this hash table.
|
||
public virtual Object SyncRoot {
|
||
get {
|
||
if( _syncRoot == null) {
|
||
System.Threading.Interlocked.CompareExchange<Object>(ref _syncRoot, new Object(), null);
|
||
}
|
||
return _syncRoot;
|
||
}
|
||
}
|
||
|
||
// Returns the number of associations in this hashtable.
|
||
//
|
||
public virtual int Count {
|
||
get { return count; }
|
||
}
|
||
|
||
// Returns a thread-safe wrapper for a Hashtable.
|
||
//
|
||
[HostProtection(Synchronization=true)]
|
||
public static Hashtable Synchronized(Hashtable table) {
|
||
if (table==null)
|
||
throw new ArgumentNullException("table");
|
||
Contract.EndContractBlock();
|
||
return new SyncHashtable(table);
|
||
}
|
||
|
||
//
|
||
// The ISerializable Implementation
|
||
//
|
||
|
||
[System.Security.SecurityCritical]
|
||
public virtual void GetObjectData(SerializationInfo info, StreamingContext context) {
|
||
if (info==null) {
|
||
throw new ArgumentNullException("info");
|
||
}
|
||
Contract.EndContractBlock();
|
||
// This is imperfect - it only works well if all other writes are
|
||
// also using our synchronized wrapper. But it's still a good idea.
|
||
lock (SyncRoot) {
|
||
// This method hasn't been fully tweaked to be safe for a concurrent writer.
|
||
int oldVersion = version;
|
||
info.AddValue(LoadFactorName, loadFactor);
|
||
info.AddValue(VersionName, version);
|
||
|
||
//
|
||
// We need to maintain serialization compatibility with Everett and RTM.
|
||
// If the comparer is null or a compatible comparer, serialize Hashtable
|
||
// in a format that can be deserialized on Everett and RTM.
|
||
//
|
||
// Also, if the Hashtable is using randomized hashing, serialize the old
|
||
// view of the _keycomparer so perevious frameworks don't see the new types
|
||
#pragma warning disable 618
|
||
#if FEATURE_RANDOMIZED_STRING_HASHING
|
||
IEqualityComparer keyComparerForSerilization = (IEqualityComparer) HashHelpers.GetEqualityComparerForSerialization(_keycomparer);
|
||
#else
|
||
IEqualityComparer keyComparerForSerilization = _keycomparer;
|
||
#endif
|
||
|
||
if( keyComparerForSerilization == null) {
|
||
info.AddValue(ComparerName, null,typeof(IComparer));
|
||
info.AddValue(HashCodeProviderName, null, typeof(IHashCodeProvider));
|
||
}
|
||
else if(keyComparerForSerilization is CompatibleComparer) {
|
||
CompatibleComparer c = keyComparerForSerilization as CompatibleComparer;
|
||
info.AddValue(ComparerName, c.Comparer, typeof(IComparer));
|
||
info.AddValue(HashCodeProviderName, c.HashCodeProvider, typeof(IHashCodeProvider));
|
||
}
|
||
else {
|
||
info.AddValue(KeyComparerName, keyComparerForSerilization, typeof(IEqualityComparer));
|
||
}
|
||
#pragma warning restore 618
|
||
|
||
info.AddValue(HashSizeName, buckets.Length); //This is the length of the bucket array.
|
||
Object [] serKeys = new Object[count];
|
||
Object [] serValues = new Object[count];
|
||
CopyKeys(serKeys, 0);
|
||
CopyValues(serValues,0);
|
||
info.AddValue(KeysName, serKeys, typeof(Object[]));
|
||
info.AddValue(ValuesName, serValues, typeof(Object[]));
|
||
|
||
// Explicitly check to see if anyone changed the Hashtable while we
|
||
// were serializing it. That's a ---- in their code.
|
||
if (version != oldVersion)
|
||
throw new InvalidOperationException(Environment.GetResourceString(ResId.InvalidOperation_EnumFailedVersion));
|
||
}
|
||
}
|
||
|
||
//
|
||
// DeserializationEvent Listener
|
||
//
|
||
public virtual void OnDeserialization(Object sender) {
|
||
if (buckets!=null) {
|
||
// Somebody had a dependency on this hashtable and fixed us up before the ObjectManager got to it.
|
||
return;
|
||
}
|
||
|
||
SerializationInfo siInfo;
|
||
HashHelpers.SerializationInfoTable.TryGetValue(this, out siInfo);
|
||
|
||
if (siInfo==null) {
|
||
throw new SerializationException(Environment.GetResourceString("Serialization_InvalidOnDeser"));
|
||
}
|
||
|
||
int hashsize = 0;
|
||
IComparer c = null;
|
||
|
||
#pragma warning disable 618
|
||
IHashCodeProvider hcp = null;
|
||
#pragma warning restore 618
|
||
|
||
Object [] serKeys = null;
|
||
Object [] serValues = null;
|
||
|
||
SerializationInfoEnumerator enumerator = siInfo.GetEnumerator();
|
||
|
||
while( enumerator.MoveNext())
|
||
{
|
||
switch( enumerator.Name)
|
||
{
|
||
case LoadFactorName:
|
||
loadFactor = siInfo.GetSingle(LoadFactorName);
|
||
break;
|
||
case HashSizeName:
|
||
hashsize = siInfo.GetInt32(HashSizeName);
|
||
break;
|
||
case KeyComparerName:
|
||
_keycomparer = (IEqualityComparer)siInfo.GetValue(KeyComparerName, typeof(IEqualityComparer));
|
||
break;
|
||
case ComparerName:
|
||
c = (IComparer)siInfo.GetValue(ComparerName, typeof(IComparer));
|
||
break;
|
||
case HashCodeProviderName:
|
||
#pragma warning disable 618
|
||
hcp = (IHashCodeProvider)siInfo.GetValue(HashCodeProviderName, typeof(IHashCodeProvider));
|
||
#pragma warning restore 618
|
||
break;
|
||
case KeysName:
|
||
serKeys = (Object[])siInfo.GetValue(KeysName, typeof(Object[]));
|
||
break;
|
||
case ValuesName:
|
||
serValues = (Object[])siInfo.GetValue(ValuesName, typeof(Object[]));
|
||
break;
|
||
}
|
||
}
|
||
|
||
loadsize = (int)(loadFactor*hashsize);
|
||
|
||
// V1 object doesn't has _keycomparer field.
|
||
if ( (_keycomparer == null) && ( (c != null) || (hcp != null) ) ){
|
||
_keycomparer = new CompatibleComparer(c,hcp);
|
||
}
|
||
|
||
buckets = new bucket[hashsize];
|
||
|
||
if (serKeys==null) {
|
||
throw new SerializationException(Environment.GetResourceString("Serialization_MissingKeys"));
|
||
}
|
||
if (serValues==null) {
|
||
throw new SerializationException(Environment.GetResourceString("Serialization_MissingValues"));
|
||
}
|
||
if (serKeys.Length!=serValues.Length) {
|
||
throw new SerializationException(Environment.GetResourceString("Serialization_KeyValueDifferentSizes"));
|
||
}
|
||
for (int i=0; i<serKeys.Length; i++) {
|
||
if (serKeys[i]==null) {
|
||
throw new SerializationException(Environment.GetResourceString("Serialization_NullKey"));
|
||
}
|
||
Insert(serKeys[i], serValues[i], true);
|
||
}
|
||
|
||
version = siInfo.GetInt32(VersionName);
|
||
|
||
HashHelpers.SerializationInfoTable.Remove(this);
|
||
}
|
||
|
||
|
||
// Implements a Collection for the keys of a hashtable. An instance of this
|
||
// class is created by the GetKeys method of a hashtable.
|
||
[Serializable]
|
||
private class KeyCollection : ICollection
|
||
{
|
||
private Hashtable _hashtable;
|
||
|
||
internal KeyCollection(Hashtable hashtable) {
|
||
_hashtable = hashtable;
|
||
}
|
||
|
||
public virtual void CopyTo(Array array, int arrayIndex) {
|
||
if (array==null)
|
||
throw new ArgumentNullException("array");
|
||
if (array.Rank != 1)
|
||
throw new ArgumentException(Environment.GetResourceString("Arg_RankMultiDimNotSupported"));
|
||
if (arrayIndex < 0)
|
||
throw new ArgumentOutOfRangeException("arrayIndex", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum"));
|
||
Contract.EndContractBlock();
|
||
if (array.Length - arrayIndex < _hashtable.count)
|
||
throw new ArgumentException(Environment.GetResourceString("Arg_ArrayPlusOffTooSmall"));
|
||
_hashtable.CopyKeys(array, arrayIndex);
|
||
}
|
||
|
||
#if !FEATURE_CORECLR
|
||
[TargetedPatchingOptOut("Performance critical to inline across NGen image boundaries")]
|
||
#endif
|
||
public virtual IEnumerator GetEnumerator() {
|
||
return new HashtableEnumerator(_hashtable, HashtableEnumerator.Keys);
|
||
}
|
||
|
||
public virtual bool IsSynchronized {
|
||
get { return _hashtable.IsSynchronized; }
|
||
}
|
||
|
||
public virtual Object SyncRoot {
|
||
get { return _hashtable.SyncRoot; }
|
||
}
|
||
|
||
public virtual int Count {
|
||
get { return _hashtable.count; }
|
||
}
|
||
}
|
||
|
||
// Implements a Collection for the values of a hashtable. An instance of
|
||
// this class is created by the GetValues method of a hashtable.
|
||
[Serializable]
|
||
private class ValueCollection : ICollection
|
||
{
|
||
private Hashtable _hashtable;
|
||
|
||
internal ValueCollection(Hashtable hashtable) {
|
||
_hashtable = hashtable;
|
||
}
|
||
|
||
public virtual void CopyTo(Array array, int arrayIndex) {
|
||
if (array==null)
|
||
throw new ArgumentNullException("array");
|
||
if (array.Rank != 1)
|
||
throw new ArgumentException(Environment.GetResourceString("Arg_RankMultiDimNotSupported"));
|
||
if (arrayIndex < 0)
|
||
throw new ArgumentOutOfRangeException("arrayIndex", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum"));
|
||
Contract.EndContractBlock();
|
||
if (array.Length - arrayIndex < _hashtable.count)
|
||
throw new ArgumentException(Environment.GetResourceString("Arg_ArrayPlusOffTooSmall"));
|
||
_hashtable.CopyValues(array, arrayIndex);
|
||
}
|
||
|
||
#if !FEATURE_CORECLR
|
||
[TargetedPatchingOptOut("Performance critical to inline across NGen image boundaries")]
|
||
#endif
|
||
public virtual IEnumerator GetEnumerator() {
|
||
return new HashtableEnumerator(_hashtable, HashtableEnumerator.Values);
|
||
}
|
||
|
||
public virtual bool IsSynchronized {
|
||
get { return _hashtable.IsSynchronized; }
|
||
}
|
||
|
||
public virtual Object SyncRoot {
|
||
get { return _hashtable.SyncRoot; }
|
||
}
|
||
|
||
public virtual int Count {
|
||
get { return _hashtable.count; }
|
||
}
|
||
}
|
||
|
||
// Synchronized wrapper for hashtable
|
||
[Serializable]
|
||
private class SyncHashtable : Hashtable, IEnumerable
|
||
{
|
||
protected Hashtable _table;
|
||
|
||
internal SyncHashtable(Hashtable table) : base(false) {
|
||
_table = table;
|
||
}
|
||
|
||
internal SyncHashtable(SerializationInfo info, StreamingContext context) : base (info, context) {
|
||
_table = (Hashtable)info.GetValue("ParentTable", typeof(Hashtable));
|
||
if (_table==null) {
|
||
throw new SerializationException(Environment.GetResourceString("Serialization_InsufficientState"));
|
||
}
|
||
}
|
||
|
||
|
||
/*================================GetObjectData=================================
|
||
**Action: Return a serialization info containing a reference to _table. We need
|
||
** to implement this because our parent HT does and we don't want to actually
|
||
** serialize all of it's values (just a reference to the table, which will then
|
||
** be serialized separately.)
|
||
**Returns: void
|
||
**Arguments: info -- the SerializationInfo into which to store the data.
|
||
** context -- the StreamingContext for the current serialization (ignored)
|
||
**Exceptions: ArgumentNullException if info is null.
|
||
==============================================================================*/
|
||
[System.Security.SecurityCritical] // auto-generated
|
||
public override void GetObjectData(SerializationInfo info, StreamingContext context) {
|
||
if (info==null) {
|
||
throw new ArgumentNullException("info");
|
||
}
|
||
Contract.EndContractBlock();
|
||
// Our serialization code hasn't been fully tweaked to be safe
|
||
// for a concurrent writer.
|
||
lock (_table.SyncRoot) {
|
||
info.AddValue("ParentTable", _table, typeof(Hashtable));
|
||
}
|
||
}
|
||
|
||
public override int Count {
|
||
get { return _table.Count; }
|
||
}
|
||
|
||
public override bool IsReadOnly {
|
||
get { return _table.IsReadOnly; }
|
||
}
|
||
|
||
public override bool IsFixedSize {
|
||
get { return _table.IsFixedSize; }
|
||
}
|
||
|
||
public override bool IsSynchronized {
|
||
get { return true; }
|
||
}
|
||
|
||
public override Object this[Object key] {
|
||
#if !FEATURE_CORECLR
|
||
[TargetedPatchingOptOut("Performance critical to inline across NGen image boundaries")]
|
||
#endif
|
||
get {
|
||
return _table[key];
|
||
}
|
||
set {
|
||
lock(_table.SyncRoot) {
|
||
_table[key] = value;
|
||
}
|
||
}
|
||
}
|
||
|
||
public override Object SyncRoot {
|
||
get { return _table.SyncRoot; }
|
||
}
|
||
|
||
public override void Add(Object key, Object value) {
|
||
lock(_table.SyncRoot) {
|
||
_table.Add(key, value);
|
||
}
|
||
}
|
||
|
||
public override void Clear() {
|
||
lock(_table.SyncRoot) {
|
||
_table.Clear();
|
||
}
|
||
}
|
||
|
||
#if !FEATURE_CORECLR
|
||
[TargetedPatchingOptOut("Performance critical to inline across NGen image boundaries")]
|
||
#endif
|
||
public override bool Contains(Object key) {
|
||
return _table.Contains(key);
|
||
}
|
||
|
||
#if !FEATURE_CORECLR
|
||
[TargetedPatchingOptOut("Performance critical to inline across NGen image boundaries")]
|
||
#endif
|
||
public override bool ContainsKey(Object key) {
|
||
if (key == null) {
|
||
throw new ArgumentNullException("key", Environment.GetResourceString("ArgumentNull_Key"));
|
||
}
|
||
Contract.EndContractBlock();
|
||
return _table.ContainsKey(key);
|
||
}
|
||
|
||
public override bool ContainsValue(Object key) {
|
||
lock(_table.SyncRoot) {
|
||
return _table.ContainsValue(key);
|
||
}
|
||
}
|
||
|
||
public override void CopyTo(Array array, int arrayIndex) {
|
||
lock (_table.SyncRoot) {
|
||
_table.CopyTo(array, arrayIndex);
|
||
}
|
||
}
|
||
|
||
public override Object Clone() {
|
||
lock (_table.SyncRoot) {
|
||
return Hashtable.Synchronized((Hashtable)_table.Clone());
|
||
}
|
||
}
|
||
|
||
IEnumerator IEnumerable.GetEnumerator() {
|
||
return _table.GetEnumerator();
|
||
}
|
||
|
||
public override IDictionaryEnumerator GetEnumerator() {
|
||
return _table.GetEnumerator();
|
||
}
|
||
|
||
public override ICollection Keys {
|
||
get {
|
||
lock(_table.SyncRoot) {
|
||
return _table.Keys;
|
||
}
|
||
}
|
||
}
|
||
|
||
public override ICollection Values {
|
||
get {
|
||
lock(_table.SyncRoot) {
|
||
return _table.Values;
|
||
}
|
||
}
|
||
}
|
||
|
||
public override void Remove(Object key) {
|
||
lock(_table.SyncRoot) {
|
||
_table.Remove(key);
|
||
}
|
||
}
|
||
|
||
/*==============================OnDeserialization===============================
|
||
**Action: Does nothing. We have to implement this because our parent HT implements it,
|
||
** but it doesn't do anything meaningful. The real work will be done when we
|
||
** call OnDeserialization on our parent table.
|
||
**Returns: void
|
||
**Arguments: None
|
||
**Exceptions: None
|
||
==============================================================================*/
|
||
public override void OnDeserialization(Object sender) {
|
||
return;
|
||
}
|
||
|
||
internal override KeyValuePairs[] ToKeyValuePairsArray() {
|
||
return _table.ToKeyValuePairsArray();
|
||
}
|
||
|
||
}
|
||
|
||
|
||
// Implements an enumerator for a hashtable. The enumerator uses the
|
||
// internal version number of the hashtabke to ensure that no modifications
|
||
// are made to the hashtable while an enumeration is in progress.
|
||
[Serializable]
|
||
private class HashtableEnumerator : IDictionaryEnumerator, ICloneable
|
||
{
|
||
private Hashtable hashtable;
|
||
private int bucket;
|
||
private int version;
|
||
private bool current;
|
||
private int getObjectRetType; // What should GetObject return?
|
||
private Object currentKey;
|
||
private Object currentValue;
|
||
|
||
internal const int Keys = 1;
|
||
internal const int Values = 2;
|
||
internal const int DictEntry = 3;
|
||
|
||
internal HashtableEnumerator(Hashtable hashtable, int getObjRetType) {
|
||
this.hashtable = hashtable;
|
||
bucket = hashtable.buckets.Length;
|
||
version = hashtable.version;
|
||
current = false;
|
||
getObjectRetType = getObjRetType;
|
||
}
|
||
|
||
public Object Clone() {
|
||
return MemberwiseClone();
|
||
}
|
||
|
||
public virtual Object Key {
|
||
#if !FEATURE_CORECLR
|
||
[TargetedPatchingOptOut("Performance critical to inline across NGen image boundaries")]
|
||
#endif
|
||
get {
|
||
if (current == false) throw new InvalidOperationException(Environment.GetResourceString(ResId.InvalidOperation_EnumNotStarted));
|
||
return currentKey;
|
||
}
|
||
}
|
||
|
||
public virtual bool MoveNext() {
|
||
if (version != hashtable.version) throw new InvalidOperationException(Environment.GetResourceString(ResId.InvalidOperation_EnumFailedVersion));
|
||
while (bucket > 0) {
|
||
bucket--;
|
||
Object keyv = hashtable.buckets[bucket].key;
|
||
if ((keyv!= null) && (keyv != hashtable.buckets)) {
|
||
currentKey = keyv;
|
||
currentValue = hashtable.buckets[bucket].val;
|
||
current = true;
|
||
return true;
|
||
}
|
||
}
|
||
current = false;
|
||
return false;
|
||
}
|
||
|
||
public virtual DictionaryEntry Entry {
|
||
get {
|
||
if (current == false) throw new InvalidOperationException(Environment.GetResourceString(ResId.InvalidOperation_EnumOpCantHappen));
|
||
return new DictionaryEntry(currentKey, currentValue);
|
||
}
|
||
}
|
||
|
||
|
||
public virtual Object Current {
|
||
get {
|
||
if (current == false) throw new InvalidOperationException(Environment.GetResourceString(ResId.InvalidOperation_EnumOpCantHappen));
|
||
|
||
if (getObjectRetType==Keys)
|
||
return currentKey;
|
||
else if (getObjectRetType==Values)
|
||
return currentValue;
|
||
else
|
||
return new DictionaryEntry(currentKey, currentValue);
|
||
}
|
||
}
|
||
|
||
public virtual Object Value {
|
||
#if !FEATURE_CORECLR
|
||
[TargetedPatchingOptOut("Performance critical to inline across NGen image boundaries")]
|
||
#endif
|
||
get {
|
||
if (current == false) throw new InvalidOperationException(Environment.GetResourceString(ResId.InvalidOperation_EnumOpCantHappen));
|
||
return currentValue;
|
||
}
|
||
}
|
||
|
||
public virtual void Reset() {
|
||
if (version != hashtable.version) throw new InvalidOperationException(Environment.GetResourceString(ResId.InvalidOperation_EnumFailedVersion));
|
||
current = false;
|
||
bucket = hashtable.buckets.Length;
|
||
currentKey = null;
|
||
currentValue = null;
|
||
}
|
||
}
|
||
|
||
// internal debug view class for hashtable
|
||
internal class HashtableDebugView {
|
||
private Hashtable hashtable;
|
||
|
||
public HashtableDebugView( Hashtable hashtable) {
|
||
if( hashtable == null) {
|
||
throw new ArgumentNullException( "hashtable");
|
||
}
|
||
Contract.EndContractBlock();
|
||
|
||
this.hashtable = hashtable;
|
||
}
|
||
|
||
|
||
[DebuggerBrowsable(DebuggerBrowsableState.RootHidden)]
|
||
public KeyValuePairs[] Items {
|
||
get {
|
||
return hashtable.ToKeyValuePairsArray();
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
[FriendAccessAllowed]
|
||
internal static class HashHelpers
|
||
{
|
||
|
||
#if FEATURE_RANDOMIZED_STRING_HASHING
|
||
public const int HashCollisionThreshold = 100;
|
||
public static bool s_UseRandomizedStringHashing = String.UseRandomizedHashing();
|
||
#endif
|
||
|
||
// Table of prime numbers to use as hash table sizes.
|
||
// A typical resize algorithm would pick the smallest prime number in this array
|
||
// that is larger than twice the previous capacity.
|
||
// Suppose our Hashtable currently has capacity x and enough elements are added
|
||
// such that a resize needs to occur. Resizing first computes 2x then finds the
|
||
// first prime in the table greater than 2x, i.e. if primes are ordered
|
||
// p_1, p_2, ..., p_i, ..., it finds p_n such that p_n-1 < 2x < p_n.
|
||
// Doubling is important for preserving the asymptotic complexity of the
|
||
// hashtable operations such as add. Having a prime guarantees that double
|
||
// hashing does not lead to infinite loops. IE, your hash function will be
|
||
// h1(key) + i*h2(key), 0 <= i < size. h2 and the size must be relatively prime.
|
||
public static readonly int[] primes = {
|
||
3, 7, 11, 17, 23, 29, 37, 47, 59, 71, 89, 107, 131, 163, 197, 239, 293, 353, 431, 521, 631, 761, 919,
|
||
1103, 1327, 1597, 1931, 2333, 2801, 3371, 4049, 4861, 5839, 7013, 8419, 10103, 12143, 14591,
|
||
17519, 21023, 25229, 30293, 36353, 43627, 52361, 62851, 75431, 90523, 108631, 130363, 156437,
|
||
187751, 225307, 270371, 324449, 389357, 467237, 560689, 672827, 807403, 968897, 1162687, 1395263,
|
||
1674319, 2009191, 2411033, 2893249, 3471899, 4166287, 4999559, 5999471, 7199369};
|
||
|
||
// Used by Hashtable and Dictionary's SeralizationInfo .ctor's to store the SeralizationInfo
|
||
// object until OnDeserialization is called.
|
||
private static ConditionalWeakTable<object, SerializationInfo> s_SerializationInfoTable;
|
||
|
||
internal static ConditionalWeakTable<object, SerializationInfo> SerializationInfoTable
|
||
{
|
||
get
|
||
{
|
||
if(s_SerializationInfoTable == null)
|
||
{
|
||
ConditionalWeakTable<object, SerializationInfo> newTable = new ConditionalWeakTable<object, SerializationInfo>();
|
||
Interlocked.CompareExchange(ref s_SerializationInfoTable, newTable, null);
|
||
}
|
||
|
||
return s_SerializationInfoTable;
|
||
}
|
||
|
||
}
|
||
|
||
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
|
||
public static bool IsPrime(int candidate)
|
||
{
|
||
if ((candidate & 1) != 0)
|
||
{
|
||
int limit = (int)Math.Sqrt (candidate);
|
||
for (int divisor = 3; divisor <= limit; divisor+=2)
|
||
{
|
||
if ((candidate % divisor) == 0)
|
||
return false;
|
||
}
|
||
return true;
|
||
}
|
||
return (candidate == 2);
|
||
}
|
||
|
||
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
|
||
public static int GetPrime(int min)
|
||
{
|
||
if (min < 0)
|
||
throw new ArgumentException(Environment.GetResourceString("Arg_HTCapacityOverflow"));
|
||
Contract.EndContractBlock();
|
||
|
||
for (int i = 0; i < primes.Length; i++)
|
||
{
|
||
int prime = primes[i];
|
||
if (prime >= min) return prime;
|
||
}
|
||
|
||
//outside of our predefined table.
|
||
//compute the hard way.
|
||
for (int i = (min | 1); i < Int32.MaxValue;i+=2)
|
||
{
|
||
if (IsPrime(i) && ((i - 1) % Hashtable.HashPrime != 0))
|
||
return i;
|
||
}
|
||
return min;
|
||
}
|
||
|
||
public static int GetMinPrime()
|
||
{
|
||
return primes[0];
|
||
}
|
||
|
||
// Returns size of hashtable to grow to.
|
||
public static int ExpandPrime(int oldSize)
|
||
{
|
||
int newSize = 2 * oldSize;
|
||
|
||
// Allow the hashtables to grow to maximum possible size (~2G elements) before encoutering capacity overflow.
|
||
// Note that this check works even when _items.Length overflowed thanks to the (uint) cast
|
||
if ((uint)newSize > MaxPrimeArrayLength && MaxPrimeArrayLength > oldSize)
|
||
{
|
||
Contract.Assert( MaxPrimeArrayLength == GetPrime(MaxPrimeArrayLength), "Invalid MaxPrimeArrayLength");
|
||
return MaxPrimeArrayLength;
|
||
}
|
||
|
||
return GetPrime(newSize);
|
||
}
|
||
|
||
|
||
// This is the maximum prime smaller than Array.MaxArrayLength
|
||
public const int MaxPrimeArrayLength = 0x7FEFFFFD;
|
||
|
||
#if FEATURE_RANDOMIZED_STRING_HASHING
|
||
public static bool IsWellKnownEqualityComparer(object comparer)
|
||
{
|
||
return (comparer == null || comparer == System.Collections.Generic.EqualityComparer<string>.Default || comparer is IWellKnownStringEqualityComparer);
|
||
}
|
||
|
||
public static IEqualityComparer GetRandomizedEqualityComparer(object comparer)
|
||
{
|
||
Contract.Assert(comparer == null || comparer == System.Collections.Generic.EqualityComparer<string>.Default || comparer is IWellKnownStringEqualityComparer);
|
||
|
||
if(comparer == null) {
|
||
return new System.Collections.Generic.RandomizedObjectEqualityComparer();
|
||
}
|
||
|
||
if(comparer == System.Collections.Generic.EqualityComparer<string>.Default) {
|
||
return new System.Collections.Generic.RandomizedStringEqualityComparer();
|
||
}
|
||
|
||
IWellKnownStringEqualityComparer cmp = comparer as IWellKnownStringEqualityComparer;
|
||
|
||
if(cmp != null) {
|
||
return cmp.GetRandomizedEqualityComparer();
|
||
}
|
||
|
||
Contract.Assert(false, "Missing case in GetRandomizedEqualityComparer!");
|
||
|
||
return null;
|
||
}
|
||
|
||
public static object GetEqualityComparerForSerialization(object comparer)
|
||
{
|
||
if(comparer == null)
|
||
{
|
||
return null;
|
||
}
|
||
|
||
IWellKnownStringEqualityComparer cmp = comparer as IWellKnownStringEqualityComparer;
|
||
|
||
if(cmp != null)
|
||
{
|
||
return cmp.GetEqualityComparerForSerialization();
|
||
}
|
||
|
||
return comparer;
|
||
}
|
||
|
||
private const int bufferSize = 1024;
|
||
private static RandomNumberGenerator rng;
|
||
private static byte[] data;
|
||
private static int currentIndex = bufferSize;
|
||
private static readonly object lockObj = new Object();
|
||
|
||
internal static long GetEntropy()
|
||
{
|
||
lock(lockObj) {
|
||
long ret;
|
||
|
||
if(currentIndex == bufferSize)
|
||
{
|
||
if(null == rng)
|
||
{
|
||
rng = RandomNumberGenerator.Create();
|
||
data = new byte[bufferSize];
|
||
Contract.Assert(bufferSize % 8 == 0, "We increment our current index by 8, so our buffer size must be a multiple of 8");
|
||
}
|
||
|
||
rng.GetBytes(data);
|
||
currentIndex = 0;
|
||
}
|
||
|
||
ret = BitConverter.ToInt64(data, currentIndex);
|
||
currentIndex += 8;
|
||
|
||
return ret;
|
||
}
|
||
}
|
||
#endif // FEATURE_RANDOMIZED_STRING_HASHING
|
||
}
|
||
}
|