【问题标题】:What is the generic version of a Hashtable?什么是 Hashtable 的通用版本?
【发布时间】:2010-10-20 20:17:26
【问题描述】:

我一直在学习 .NET 中的泛型基础知识。但是,我看不到Hashtable 的通用等价物。请分享一些用于创建通用哈希表类的示例 C# 代码。

【问题讨论】:

    标签: c# .net generics


    【解决方案1】:

    Dictionary<TKey, TValue>

    请注意,Dictionary 并不是 HashTable 的 100% 替代品。

    它们处理 NULL 的方式略有不同。 如果您尝试引用不存在的键,字典将引发异常。 HashTable 只会返回 null。 原因是该值可能是一个值类型,不能为空。在 Hashtable 中,值始终是 Object,因此返回 null 至少是可能的。

    【讨论】:

    • 另一个区别是,如果从不删除项目,HashTable 可以在写入时被任意数量的读取器线程安全地访问(对于多个写入器来说,它不是线程安全的)。相比之下,Dictionary<TKey,TValue> 对于除多读取器、零写入器之外的任何场景都不是线程安全的,即使在仅添加场景中也是如此。
    • 这是一个很好的观点。但是请注意,.NET 4 引入了ConcurrentDictionary<TKey, TValue>,它与System.Collections.Concurrent 中的其他集合一样是线程安全的。
    【解决方案2】:

    Hashtable 类的通用版本是System.Collections.Generic.Dictionary 类。

    Sample code:

    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);
       }
    

    【讨论】:

    • 很好奇。为什么微软称其为字典而其他人称其为哈希表?在我读到泛型字典之前,Hashtable 对我来说总是很神秘。
    【解决方案3】:

    Hashtable 的通用版本是 Dictionary&lt;TKey,TValue&gt; 类 (link)。这是一些从使用 Hashtable 转换为最直接等效的 Dictionary 的示例代码(为简洁起见,删除了参数检查)

    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;
    }
    

    这是一个相当直接的翻译。但问题是这实际上并没有利用泛型的类型安全特性。第二个函数可以编写如下,并且类型更安全,并且不会产生装箱开销

    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;
    }
    

    更好。这是一个完全通用的版本

    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;
    }
    

    还有一个更加灵活(感谢 Joel 指出我错过了这一点)

    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;
    }
    

    【讨论】:

    • 下一步是使用 IEnumerable 而不是 T[] 作为函数参数。
    • 是的。只需几秒钟即可添加它
    • 可以堆叠“using()”语句而无需缩进和花括号。使多次使用更具可读性
    【解决方案4】:

    对于那些感兴趣的人,我创建了一个泛型 Hashtable 包装类,它对强制类型安全很有用,可以作为泛型 IDictionary、ICollection 和 IEnumerable 类型传递,而非泛型 Hashtable 则不能。下面是实现。

    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;
                }
            }
        }
    }
    

    我已经对此 Hashtable 与 Dictionary 进行了一些测试,发现两者在与字符串键和字符串值对一起使用时表现大致相同,除了 Hashtable 似乎使用更少的内存。我的测试结果如下:

    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
    

    【讨论】:

      【解决方案5】:

      【讨论】:

      • 我什么时候会使用其中一个?
      • 除非您试图保持与 .NET 1.x 的互操作性,否则使用非泛型集合而不是泛型集合没有任何好处。
      • 查看何时使用 MSDN 上的通用集合 (msdn.microsoft.com/en-us/library/ms172181.aspx) 以获得完整说明
      • 字典 不允许重复。哈希表可以。这是一个显着的差异。
      • @wayne.blackmon 与HashtableDictionary&lt;int, int&gt; 我得到一个ArgumentException 试图通过Add 添加相同的键(或值)两次(在任何一种情况下都没有例外通过索引器)。您如何看待不允许重复的内容?
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2013-11-25
      • 2016-09-18
      • 1970-01-01
      • 2010-11-22
      • 1970-01-01
      • 2011-04-30
      • 1970-01-01
      相关资源
      最近更新 更多