国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 學院 > 開發設計 > 正文

[玩來玩去]將自定義的值類型用作字典的鍵,要特別注意什么?

2019-11-17 03:05:56
字體:
來源:轉載
供稿:網友

[玩來玩去]將自定義的值類型用作字典的鍵,要特別注意什么?

  前天關注了老趙的微信公眾號:趙人希。昨天就推送了一個快速問答,下面把我的答題經歷跟大家分享,希望對于菜鳥同胞們有所幫助啟發。

  其實這個問題在他的博客里有專門的篇幅講解,我猜到了,哈哈。但是大牛講問題都是在一個高度上,水平差點的需要費點力氣才能理解,而這個過程就是拉進我們與大牛距離的過程,用心總結下,才能不斷強化自己靠近他們。

一、通常的字典用法(熟練可直接略過)

  在我平時的編碼中,用的最頻繁的就是代碼段[1]:

 1     public class Example 2     { 3         public static void Main() 4         { 5             Dictionary<string, string> openWith = new Dictionary<string, string>(); 6  7             openWith.Add("txt", "notepad.exe"); 8             openWith.Add("bmp", "paint.exe"); 9             openWith.Add("rtf", "Wordpad.exe");10 11             if (!openWith.ContainsKey("ht"))12             {13                 openWith.Add("ht", "hypertrm.exe");14             }15 16             foreach (KeyValuePair<string, string> kvp in openWith)17             {18                 Console.WriteLine("Key = {0}, Value = {1}",19                     kvp.Key, kvp.Value);20             }21 22             Dictionary<string, string>.ValueCollection valueColl = openWith.Values;23             foreach (string s in valueColl)24             {25                 Console.WriteLine("Value = {0}", s);26             }27 28             Dictionary<string, string>.KeyCollection keyColl = openWith.Keys;29             foreach (string s in keyColl)30             {31                 Console.WriteLine("Key = {0}", s);32             }33             Console.ReadKey();34         }
Dictionary的通常用法

  其中Dictionary<鍵,值>的鍵通常就是int或者string類型,那現在我們需要自定義值類型,并把它作為字典的鍵應該怎么做?

二、自定義值類型,并考慮用于字典的鍵時遇到的問題

  整天定義引用類型,值類型還真寫的少,其實是對值類型的優勢了解的少!代碼段[2]:

 1   PRivate struct MyKey 2   { 3       private readonly int _a; 4       private readonly int _b; 5       public MyKey(int a, int b) 6       { 7            _a = a; 8            _b = b; 9        }10    }

  這就OK了,可以根據自己的需求加入一些屬性和方法,對于一些簡單的需求,定義一個struct要更高效節省。

  代碼段[2]中的值類型就可以用于字典的鍵,但是,這就夠了嗎?萬事都有不完美,你有沒有考慮到值類型隨之而來的[裝箱]!比如代碼段[3]:

1  public static void Main()2   {3       Dictionary<MyKey, string> testDic = new Dictionary<MyKey, string>();4       MyKey key12 = new MyKey(1, 2);5       testDic.Add(key12, "1&2");6       Console.ReadKey();7   }

  插一句:在我們分析問題時,要想明白原理,弄清楚.net框架中是怎么實現的,就必須抄家伙(.NET Reflector)!這是我們進步的一個重要工具。

  Dictionary中Add方法調用的Insert方法的部分實現,如代碼段[4]:

 1     int num = this.comparer.GetHashCode(key) & 0x7fffffff; 2     int index = num % this.buckets.Length; 3     int num3 = 0; 4     for (int i = this.buckets[index]; i >= 0; i = this.entries[i].next) 5     { 6          if ((this.entries[i].hashCode == num) && this.comparer.Equals(this.entries[i].key, key)) 7          { 8              if (add) 9             {10                  ThrowHelper.ThrowArgumentException(ExceptionResource.Argument_AddingDuplicate);11             }12             this.entries[i].value = value;13             this.version++;14             return;15          }16          num3++;17      }

注意:其中用到了對象的GetHashCode和Equals方法,我們知道所有類型最終都繼承自System.Object,如果在自定義的類型以及其繼承層次的所有類中沒有重寫GetHashCode和Equals方法(自定義值類型的繼承層次是MyKey=>System.ValueType=>System.Object),那么就會調用基類Object的相應方法,那必然會導致[裝箱]操作。

三、發現問題了,那就解決問題!

  而在Dictionary中,代碼段[4]顯示的是通過this.comparer調用的這兩個方法,那this.comparer是什么呢?繼續挖掘,它是Dictionary類中維護的一個IEqualityComparer<T>類型的對象。代碼段[5]:

 1 public Dictionary(int capacity, IEqualityComparer<TKey> comparer) 2 { 3     if (capacity < 0) 4     { 5         ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.capacity); 6     } 7     if (capacity > 0) 8     { 9         this.Initialize(capacity);10     }11     this.comparer = comparer ?? EqualityComparer<TKey>.Default;12 }

  如果在創建Dictionary時沒有在參數中提供比較器,則會使用默認的EqualityComparer<T>.Default對象給this.comparer賦值,它的構造方法是代碼段[6]:

 1 [SecuritySafeCritical] 2 private static EqualityComparer<T> CreateComparer() 3 { 4     RuntimeType c = (RuntimeType) typeof(T); 5     if (c == typeof(byte)) 6     { 7         return (EqualityComparer<T>) new ByteEqualityComparer(); 8     } 9     if (typeof(IEquatable<T>).IsAssignableFrom(c))10     {11         return (EqualityComparer<T>) RuntimeTypeHandle.CreateInstanceForAnotherGenericParameter((RuntimeType) typeof(GenericEqualityComparer<int>), c);12     }13     if (c.IsGenericType && (c.GetGenericTypeDefinition() == typeof(Nullable<>)))14     {15         RuntimeType type2 = (RuntimeType) c.GetGenericArguments()[0];16         if (typeof(IEquatable<>).MakeGenericType(new Type[] { type2 }).IsAssignableFrom(type2))17         {18             return (EqualityComparer<T>) RuntimeTypeHandle.CreateInstanceForAnotherGenericParameter((RuntimeType) typeof(NullableEqualityComparer<int>), type2);19         }20     }21     if (c.IsEnum && (Enum.GetUnderlyingType(c) == typeof(int)))22     {23         return (EqualityComparer<T>) RuntimeTypeHandle.CreateInstanceForAnotherGenericParameter((RuntimeType) typeof(EnumEqualityComparer<int>), c);24     }25     return new ObjectEqualityComparer<T>();26 }
CreateComparer方法

  可以看出,根據不同的情況它會使用各式不同的比較器。其中最適合我們的自然就是實現IEquatable<T>接口的分支了:

1     if (typeof(IEquatable<T>).IsAssignableFrom(c))2     {3   return (EqualityComparer<T>) RuntimeTypeHandle.CreateInstanceForAnotherGenericParameter((RuntimeType) typeof(GenericEqualityComparer<int>), c);4     }

  為了能夠符合這個if的條件,我們的自定義值類型MyKey應當實現IEquatable<MyKey>接口(實現Equals方法),并且重寫GetHashCode方法(Object的GetHashCode方法也會導致裝箱),才能徹底避免裝箱操作。

  因此,我的最終實現是代碼段[7]:

 1     public struct MyKey : IEquatable<MyKey> 2     { 3         private readonly int _a; 4         private readonly int _b; 5  6         public MyKey(int a, int b) 7         { 8             _a = a; 9             _b = b;10         }11         public override int GetHashCode()12         {13             return (this._a ^ this._b);14         }15 16         public bool Equals(MyKey that)17         {18             return (this._a == that._a) && (this._b == that._b);19         }20     }

  就這樣,一個簡單的問答被我們剖析了個遍,很過癮吧!

  最后,文中如果有不對的地方,歡迎各位大牛指正!這里先謝過了!

  如果你覺得本文對你有幫助,那就點個贊吧,權當鼓勵。


發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 扶风县| 兰坪| 溆浦县| 扎囊县| 洛扎县| 静乐县| 贺州市| 临安市| 武定县| 义乌市| 晋中市| 中江县| 宁城县| 衡阳县| 信宜市| 江油市| 偃师市| 襄樊市| 无为县| 化德县| 永仁县| 阳高县| 革吉县| 汽车| 烟台市| 伽师县| 乌拉特前旗| 和静县| 宁安市| 大关县| 保山市| 通渭县| 离岛区| 岐山县| 宁明县| 五莲县| 衡东县| 密云县| 华安县| 浪卡子县| 同心县|