Was ist die generische Version einer Hashtable?

73

Ich habe die Grundlagen von Generika in .NET gelernt. Ich sehe jedoch nicht das generische Äquivalent von Hashtable. Bitte teilen Sie einen Beispiel-C # -Code zum Erstellen generischer Hashtabellenklassen mit.

Charlie
quelle

Antworten:

100

Dictionary<TKey, TValue>

Beachten Sie, dass Dictionary kein 100% iger Ersatz für HashTable ist.

Es gibt einen kleinen Unterschied in der Art und Weise, wie sie mit NULL umgehen. Das Wörterbuch löst eine Ausnahme aus, wenn Sie versuchen, auf einen nicht vorhandenen Schlüssel zu verweisen. Die HashTable gibt nur null zurück. Der Grund ist, dass der Wert ein Werttyp sein kann, der nicht null sein kann. In einer Hashtabelle war der Wert immer Objekt, sodass die Rückgabe von null zumindest möglich war.

Joel Coehoorn
quelle
3
Ein weiterer Unterschied besteht darin, dass, wenn Elemente niemals entfernt werden, eine HashTablebeliebige Anzahl von Leserthreads während des Schreibens sicher auf a zugreifen kann (dies ist für mehrere Schreiber nicht threadsicher). Im Gegensatz dazu Dictionary<TKey,TValue>ist es für kein anderes Szenario als mehrere Leser, keine Schreiber, selbst in einem Nur-Add-Szenario threadsicher.
Supercat
7
Das ist ein guter Punkt. Beachten Sie jedoch, dass .NET 4 eingeführt wurde ConcurrentDictionary<TKey, TValue>, das wie andere Sammlungen in threadsicher ist System.Collections.Concurrent.
J0e3gan
42

Die generische Version der Hashtable-Klasse ist die System.Collections.Generic.Dictionary- Klasse.

Beispielcode :

Dictionary<int, string> numbers = new Dictionary<int, string>( );
   numbers.Add(1, "one");
   numbers.Add(2, "two");
   // Display all key/value pairs in the Dictionary.
   foreach (KeyValuePair<int, string> kvp in numbers)
   {
      Console.WriteLine("Key: " + kvp.Key + "\tValue: " + kvp.Value);
   }
Gulzar Nazim
quelle
2
Neugierig. Warum nennt Microsoft es ein Wörterbuch und andere eine Hashtabelle? Hashtable schien mir immer mysteriös, bis ich über Wörterbücher in Generika las.
Johnny
22

Die generische Version einer Hashtable ist die Dictionary<TKey,TValue>Klasse ( Link ). Hier ist ein Beispielcode, der aus der Verwendung einer Hashtabelle in das direkteste Äquivalent von Dictionary übersetzt wurde (Argumentprüfung aus Gründen der Kürze entfernt).

public HashTable Create(int[] keys, string[] values) { 
  HashTable table = new HashTable();
  for ( int i = 0; i < keys.Length; i++ ) {
    table[keys[i]] = values[i];
  }
  return table;
}

public Dictionary<object,object> Create(int[] keys, string[] values) {
  Dictionary<object,object> map = Dictionary<object,object>();
  for ( int i = 0; i < keys.Length; i++) {
    map[keys[i]] = values[i];
  }
  return map;
}

Das ist eine ziemlich direkte Übersetzung. Das Problem ist jedoch, dass dies die typsicheren Funktionen von Generika nicht wirklich ausnutzt. Die zweite Funktion könnte wie folgt geschrieben werden und viel typsicherer sein und keinen Boxaufwand verursachen

public Dictionary<int,string> Create(int[] keys, string[] values) {
  Dictionary<int,string> map = Dictionary<int,string>();
  for ( int i = 0; i < keys.Length; i++) {
    map[keys[i]] = values[i];
  }
  return map;
}

Noch besser. Hier ist eine komplett generische Version

public Dictionary<TKey,TValue> Create<TKey,TValue>(TKey[] keys, TValue[] values) {
  Dictionary<TKey,TValue> map = Dictionary<TKey,TValue>();
  for ( int i = 0; i < keys.Length; i++) {
    map[keys[i]] = values[i];
  }
  return map;
}

Und eine, die noch flexibler ist (danke Joel für den Hinweis, dass ich das verpasst habe)

public Dictionary<TKey,TValue> Create<TKey,TValue>(
    IEnumerable<TKey> keys, 
    IEnumerable<TValue> values) {

  Dictionary<TKey,TValue> map = Dictionary<TKey,TValue>();
  using ( IEnumerater<TKey> keyEnum = keys.GetEnumerator() ) 
  using ( IEnumerator<TValue> valueEnum = values.GetEnumerator()) {
    while (keyEnum.MoveNext() && valueEnum.MoveNext() ) { 
      map[keyEnum.Current] = valueEnum.Current;
    }
  }
  return map;
}
JaredPar
quelle
Der nächste Schritt besteht darin, IEnumerable <T> anstelle von T [] für die Funktionsparameter zu verwenden.
Joel Coehoorn
Ja. Hinzufügen in nur einer Sekunde
JaredPar
1
Man kann "using ()" - Anweisungen ohne Einrückung und geschweifte Klammern stapeln. Macht mehrere Verwendungszwecke besser lesbar
Daten
5

Für diejenigen, die interessiert sind, habe ich eine generische Hashtable-Wrapper-Klasse erstellt, die zur Durchsetzung der Typensicherheit nützlich ist und als generisches IDictionary, ICollection und IEnumerable übergeben werden kann, während die nicht generische Hashtable dies nicht kann. Unten ist die Implementierung.

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Common.Collections.Generic
{
    public class Hashtable<TKey, TValue> : IDictionary<TKey, TValue>
        , ICollection<KeyValuePair<TKey, TValue>>
        , IEnumerable<KeyValuePair<TKey, TValue>>
        , IDictionary
        , ICollection
        , IEnumerable
    {
        protected Hashtable _items;
        /// <summary>
        /// Initializes a new, empty instance of the Hashtable class using the default initial capacity, load factor, hash code provider, and comparer.
        /// </summary>
        public Hashtable()
        {
            _items = new Hashtable();
        }
        /// <summary>
        /// Initializes a new, empty instance of the Hashtable class using the specified initial capacity, and the default load factor, hash code provider, and comparer.
        /// </summary>
        /// <param name="capacity">The approximate number of elements that the Hashtable object can initially contain. </param>
        public Hashtable(int capacity)
        {
            _items = new Hashtable(capacity);
        }
        /// <summary>
        /// Actual underlying hashtable object that contains the elements.
        /// </summary>
        public Hashtable Items { get { return _items; } }

        /// <summary>
        /// Adds an element with the specified key and value into the Hashtable.
        /// </summary>
        /// <param name="key">Key of the new element to add.</param>
        /// <param name="value">Value of the new elment to add.</param>
        public void Add(TKey key, TValue value)
        {
            _items.Add(key, value);
        }
        /// <summary>
        /// Adds an element with the specified key and value into the Hashtable.
        /// </summary>
        /// <param name="item">Item containing the key and value to add.</param>
        public void Add(KeyValuePair<TKey, TValue> item)
        {
            _items.Add(item.Key, item.Value);
        }

        void IDictionary.Add(object key, object value)
        {
            this.Add((TKey)key, (TValue)value);
        }
        /// <summary>
        /// Add a list of key/value pairs to the hashtable.
        /// </summary>
        /// <param name="collection">List of key/value pairs to add to hashtable.</param>
        public void AddRange(IEnumerable<KeyValuePair<TKey, TValue>> collection)
        {
            foreach (var item in collection)
                _items.Add(item.Key, item.Value);
        }
        /// <summary>
        /// Determines whether the Hashtable contains a specific key.
        /// </summary>
        /// <param name="key">Key to locate.</param>
        /// <returns>True if key is found, otherwise false.</returns>
        public bool ContainsKey(TKey key)
        {
            return _items.ContainsKey(key);
        }
        /// <summary>
        /// Determines whether the Hashtable contains a specific key.
        /// </summary>
        /// <param name="item">Item containing the key to locate.</param>
        /// <returns>True if item.Key is found, otherwise false.</returns>
        public bool Contains(KeyValuePair<TKey, TValue> item)
        {
            return _items.ContainsKey(item.Key);
        }

        bool IDictionary.Contains(object key)
        {
            return this.ContainsKey((TKey)key);
        }
        /// <summary>
        /// Gets an ICollection containing the keys in the Hashtable.
        /// </summary>
        public ICollection<TKey> Keys
        {
            get { return _items.ToList<TKey>(); }
        }

        ICollection IDictionary.Keys
        {
            get { return this.Keys.ToList(); }
        }
        /// <summary>
        /// Gets the value associated with the specified key.
        /// </summary>
        /// <param name="key">The key of the value to get.</param>
        /// <param name="value">When this method returns, contains the value associated with the specified key,
        /// if the key is found; otherwise, the default value for the type of the value parameter. This parameter 
        /// is passed uninitialized.</param>
        /// <returns>true if the hashtable contains an element with the specified key, otherwise false.</returns>
        public bool TryGetValue(TKey key, out TValue value)
        {
            value = (TValue)_items[key];
            return (value != null);
        }
        /// <summary>
        /// Gets an ICollection containing the values in the Hashtable.
        /// </summary>
        public ICollection<TValue> Values
        {
            get { return _items.Values.ToList<TValue>(); }
        }

        ICollection IDictionary.Values
        {
            get { return this.Values.ToList(); }
        }
        /// <summary>
        /// Gets or sets the value associated with the specified key.
        /// </summary>
        /// <param name="key">The key whose value to get or set. </param>
        /// <returns>The value associated with the specified key. If the specified key is not found, 
        /// attempting to get it returns null, and attempting to set it creates a new element using the specified key.</returns>
        public TValue this[TKey key]
        {
            get
            {
                return (TValue)_items[key];
            }
            set
            {
                _items[key] = value;
            }
        }
        /// <summary>
        /// Removes all elements from the Hashtable.
        /// </summary>
        public void Clear()
        {
            _items.Clear();
        }
        /// <summary>
        /// Copies all key/value pairs in the hashtable to the specified array.
        /// </summary>
        /// <param name="array">Object array to store objects of type "KeyValuePair&lt;TKey, TValue&gt;"</param>
        /// <param name="arrayIndex">Starting index to store objects into array.</param>
        public void CopyTo(Array array, int arrayIndex)
        {
            _items.CopyTo(array, arrayIndex);
        }
        /// <summary>
        /// Copies all key/value pairs in the hashtable to the specified array.
        /// </summary>
        /// <param name="array">Object array to store objects of type "KeyValuePair&lt;TKey, TValue&gt;"</param>
        /// <param name="arrayIndex">Starting index to store objects into array.</param>
        public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
        {
            _items.CopyTo(array, arrayIndex);
        }
        /// <summary>
        /// Gets the number of key/value pairs contained in the Hashtable.
        /// </summary>
        public int Count
        {
            get { return _items.Count; }
        }
        /// <summary>
        /// Gets a value indicating whether the Hashtable has a fixed size.
        /// </summary>
        public bool IsFixedSize
        {
            get { return _items.IsFixedSize; }
        }
        /// <summary>
        /// Gets a value indicating whether the Hashtable is read-only.
        /// </summary>
        public bool IsReadOnly
        {
            get { return _items.IsReadOnly; }
        }
        /// <summary>
        /// Gets a value indicating whether access to the Hashtable is synchronized (thread safe).
        /// </summary>
        public bool IsSynchronized
        {
            get { return _items.IsSynchronized; }
        }
        /// <summary>
        /// Gets an object that can be used to synchronize access to the Hashtable.
        /// </summary>
        public object SyncRoot
        {
            get { return _items.SyncRoot; }
        }
        /// <summary>
        /// Removes the element with the specified key from the Hashtable.
        /// </summary>
        /// <param name="key">Key of the element to remove.</param>
        public void Remove(TKey key)
        {
            _items.Remove(key);
        }
        /// <summary>
        /// Removes the element with the specified key from the Hashtable.
        /// </summary>
        /// <param name="item">Item containing the key of the element to remove.</param>
        public void Remove(KeyValuePair<TKey, TValue> item)
        {
            this.Remove(item.Key);
        }

        bool IDictionary<TKey, TValue>.Remove(TKey key)
        {
            var numValues = _items.Count;
            _items.Remove(key);
            return numValues > _items.Count;
        }

        bool ICollection<KeyValuePair<TKey, TValue>>.Remove(KeyValuePair<TKey, TValue> item)
        {
            var numValues = _items.Count;
            _items.Remove(item.Key);
            return numValues > _items.Count;
        }

        void IDictionary.Remove(object key)
        {
            _items.Remove(key);
        }
        /// <summary>
        /// Returns an enumerator that iterates through the hashtable.
        /// </summary>
        /// <returns>An enumerator for a list of key/value pairs.</returns>
        public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
        {
            foreach (DictionaryEntry? item in _items)
                yield return new KeyValuePair<TKey, TValue>((TKey)item.Value.Key, (TValue)item.Value.Value);
        }
        /// <summary>
        /// Returns an enumerator that iterates through the hashtable.
        /// </summary>
        /// <returns>An enumerator for a list of key/value pairs as generic objects.</returns>
        IEnumerator IEnumerable.GetEnumerator()
        {
            return this.GetEnumerator();
        }

        IDictionaryEnumerator IDictionary.GetEnumerator()
        {
            // Very old enumerator that no one uses anymore, not supported.
            throw new NotImplementedException(); 
        }

        object IDictionary.this[object key]
        {
            get
            {
                return _items[(TKey)key];
            }
            set
            {
                _items[(TKey)key] = value;
            }
        }
    }
}

Ich habe einige Tests dieser Hashtabelle im Vergleich zum Wörterbuch durchgeführt und festgestellt, dass die beiden bei Verwendung eines Paares aus Zeichenfolgenschlüssel und Zeichenfolgenwert ungefähr dieselbe Leistung erbringen, außer dass die Hashtabelle anscheinend weniger Speicher benötigt. Die Ergebnisse meines Tests sind wie folgt:

TestInitialize Dictionary_50K_Hashtable
Number objects 50000, memory usage 905164
Insert, 22 milliseconds.
A search not found, 0 milliseconds.
Search found, 0 milliseconds.
Remove, 0 milliseconds.
Search found or not found, 0 milliseconds.
TestCleanup Dictionary_50K_Hashtable

TestInitialize Dictionary_50K_Dictionary
Number objects 50000, memory usage 1508316
Insert, 16 milliseconds.
A search not found, 0 milliseconds.
Search found, 0 milliseconds.
Remove, 0 milliseconds.
Search found or not found, 0 milliseconds.
TestCleanup Dictionary_50K_Dictionary
Michael Erickson
quelle
3

Die generische Version von System.Collection.Hashtableist System.Collections.Generic.Dictionary<TKey, TValue>.

bdukes
quelle
wann würde ich eins über das andere verwenden?
Johnny
Wenn Sie nicht versuchen, die Interoperabilität mit .NET 1.x aufrechtzuerhalten, bietet die Verwendung der nicht generischen Sammlungen keinen Vorteil gegenüber den generischen Sammlungen.
Bdukes
Die vollständige Erklärung finden Sie unter Wann generische Sammlungen ( msdn.microsoft.com/en-us/library/ms172181.aspx ) auf MSDN zu verwenden sind
bdukes
Das Wörterbuch <T, U> erlaubt keine Duplikate. Hashtable tut. Das ist ein wesentlicher Unterschied.
Wayne.blackmon
@ wayne.blackmon mit beiden Hashtableund Dictionary<int, int>ich bekomme den ArgumentExceptionVersuch, den gleichen Schlüssel (oder Wert) zweimal über Add(und in beiden Fällen keine Ausnahme über den Indexer) hinzuzufügen . Wie sehen Sie, dass Duplikate nicht zugelassen werden?
Bdukes