為什麼GetHashCode方法需要如此設計?
昨天我在實現《透過擴充套件改善ASP.NET MVC的驗證機制[使用篇]》的時候為了Attribute 的一個小問題後耗費了大半天的精力,雖然最終找到了問題的癥結並解決了問題,但是我依然不知道微軟如此設計的目的何在。閒話少說,我們先來演示一下我具體遇到的問題如何發生的。
目錄:
一、問題重現
二、透過Attribute的Equals方法和GetHashCode方法進行對等判斷
三、Attribute物件和Attribute型別的HashCode
四、倘若為FooAttribute新增一個屬性/欄位
五、Attribute的GetHashCode方式是如何實現的?
一、問題重現
如下面的程式碼片斷所示,我們定義了兩個Attribute。其中抽象的BaseAttribute中定義了一個Name屬性,而FooAttribute直接繼承自BaseAttribute,並不曾定義任何屬性和欄位。在型別Bar上,我們應用了三個FooAttribute特性,其Name屬性分別為A、B和C。
1: [Foo(Name = "A")]
2: [Foo(Name = "B")]
3: [Foo(Name = "C")]
4: public class Bar
5: {
6:
7: }
8:
9: [AttributeUsage( AttributeTargets.Class, Inherited=true, AllowMultiple=true)]
10: public abstract class BaseAttribute : Attribute
11: {
12: public string Name { get; set; }
13: }
14: public class FooAttribute : BaseAttribute
15: {
17: }
在我的程式中具有類似於如下一段程式碼:我們呼叫Bar型別物件的GetCustomAttributes方法得到所有的Attribute特性並篩選出型別為FooAttribute特性列表,毫無疑問,這個列表包含Name屬性分別為A、B和C的三個FooAttribute物件。然後我們從該列表中將Name屬性為C的FooAttribute物件移掉,最終列印列表出餘下的FooAttribute的Name屬性。
1: var attributes = typeof(Bar).GetCustomAttributes(true).OfType().ToList ();
2: var attribute = attributes.First(item => item.Name == "C");
3: attributes.Remove(attribute);
4: Array.ForEach(attributes.ToArray(), a => Console.WriteLine(a.Name));
按照絕大部分人思維,最終列印出來的肯定是A和B,但是真正執行的結果卻是B和C。下面所示的確實就是最終的執行結果:
1: B
2: C
二、透過Attribute的Equals方法和GetHashCode方法進行對等判斷
然後我們透過如下的方式判定兩個FooAttribute物件的對等性。如下面的程式碼片斷所示,我們直接呼叫建構函式建立了兩個FooAttribute物件,它們的Name屬性分別設定為“ABC”和“123”。最後兩句程式碼分別透過呼叫Equals和HashCode判斷兩個FooAttribute是否相等。
1: FooAttribute attribute1 = new FooAttribute{ Name = "ABC" };
2: FooAttribute attribute2 = new FooAttribute{ Name = "123"};
3: Console.WriteLine("attribute1.Equals(attribute2) = {0}",attribute1.Equals(attribute2));
4: Console.WriteLine("attribute1.GetHashCode() == attribute2.GetHashCode() = {0}", attribute1.GetHashCode() == attribute2.GetHashCode());
透過如下的輸出結果我們可以看出這兩個分明具有不同Name屬性值FooAttribute居然被認定為是“相等的”:
1: attribute1.Equals(attribute2) = True
2: attribute1.GetHashCode() == attribute2.GetHashCode() = True
三、Attribute物件和Attribute型別的HashCode
實際上兩個FooAttribute物件的HashCode和FooAttribute型別是相等的。為此我們新增了額外兩行程式碼判斷typeof(FooAttribute)和FooAttribute物件的HashCode之間的對等性。
1: FooAttribute attribute1 = new FooAttribute{ Name = "ABC" };
2: FooAttribute attribute2 = new FooAttribute{ Name = "123"};
3: Console.WriteLine("attribute1.Equals(attribute2) = {0}",attribute1.Equals(attribute2));
4: Console.WriteLine("attribute1.GetHashCode() == attribute2.GetHashCode() = {0}", attribute1.GetHashCode() == attribute2.GetHashCode());
5: Console.WriteLine("attribute1.GetHashCode() == typeof(FooAttribute).GetHashCode() = {0}",
6: attribute1.GetHashCode() == typeof(FooAttribute).GetHashCode());
typeof(FooAttribute)和FooAttribute物件之間對等性可以透過如下的輸出結果看出來:
1: attribute1.Equals(attribute2) = True
2: attribute1.GetHashCode() == attribute2.GetHashCode() = True
3: attribute1.GetHashCode() == typeof(FooAttribute).GetHashCode() = True
四、倘若為FooAttribute新增一個屬性
但是不要以為Attribute的GetHashCode方法總是返回型別本身的HashCode,如果我們在FooAttribute定義一個屬性/欄位,最終的對等性判斷又會不同。為此我們在FooAttribute定義了一個Type屬性。
1: public class FooAttribute : BaseAttribute
2: {
3: public Type Type {get;set;}
4: }
然後我們在建立FooAttribute時指定其Type屬性:
1: FooAttribute attribute1 = new FooAttribute{ Name = "ABC", Type=typeof(string)};
2: FooAttribute attribute2 = new FooAttribute{ Name = "ABC" , Type=typeof(int)};
3: Console.WriteLine("attribute1.Equals(attribute2) = {0}",attribute1.Equals(attribute2));
4: Console.WriteLine("attribute1.GetHashCode() == attribute2.GetHashCode() = {0}", attribute1.GetHashCode() == attribute2.GetHashCode());
5: Console.WriteLine("attribute1.GetHashCode() == typeof(FooAttribute).GetHashCode() = {0}",
6: attribute1.GetHashCode() == typeof(FooAttribute).GetHashCode());
現在具有不同Type屬性值得兩個FooAttribute就不相等了,這可以透過如下所示的輸出結果看出來:
1: attribute1.Equals(attribute2) = False
2: attribute1.GetHashCode() == attribute2.GetHashCode() = False
3: attribute1.GetHashCode() == typeof(FooAttribute).GetHashCode() = False
五、Attribute的GetHashCode方式是如何實現的?
Attribute的HashCode是由定義在自身型別的欄位值派生,不包括從基類繼承下來的屬性值。如果自身型別不曾定義任何欄位,則直接使用型別的HashCode,這可以透過Attribute的GetHashCode方法的實現看出來,而Equals的邏輯與此類似。
1: [SecuritySafeCritical]
2: public override int GetHashCode()
3: {
4: Type type = base.GetType();
5: FieldInfo[] fields = type.GetFields(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance);
6: object obj2 = null;
7: for (int i = 0; i8: {9: object obj3 = ((RtFieldInfo) fields[i]).InternalGetValue(this, false, false);10: if ((obj3 != null) && !obj3.GetType().IsArray)11: {12: obj2 = obj3;13: }14: if (obj2 != null)15: {16: break;17: }18: }19: if (obj2 != null)20: {21: return obj2.GetHashCode();22: }23: return type.GetHashCode();24: }
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/2983/viewspace-2806153/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- Python 為什麼如此設計?Python
- 程式設計師,為什麼如此迷茫?程式設計師
- 為什麼 JavaScript 需要非同步程式設計JavaScript非同步程式設計
- 為什麼前端工程師需要關注設計前端工程師
- Java程式設計方法論-Spring WebFlux篇 01 為什麼需要Spring WebFlux 上Java程式設計SpringWebUX
- Java程式設計方法論-Spring WebFlux篇 01 為什麼需要Spring WebFlux 下Java程式設計SpringWebUX
- 為什麼量子計算如此難以解釋? - quantamagazine
- 邊緣計算是什麼以及為什麼需要它
- [翻譯] 為什麼 Golang 在系統程式設計中如此受歡迎?Golang程式設計
- 一個簡單的字串,為什麼 Redis 要設計的如此特別字串Redis
- 網頁設計需要學什麼?網頁
- 為什麼Web3如此重要?Web
- 為什麼 Dapr 如此令人興奮
- 【Java併發程式設計】一、為什麼需要學習併發程式設計?Java程式設計
- 為什麼Python程式設計師需要學習Linux系統?Python程式設計師Linux
- 為什麼需要Docker?Docker
- 為什麼Kubernetes如此受歡迎? -stackoverflow
- 為什麼我如此討厭scrums? - RedditScrum
- 回首 Kubernetes 發展,為什麼如此出色?
- 國產App為什麼如此“臃腫”?!APP
- Windows如此普及,為什麼要學LinuxWindowsLinux
- 日誌審計是什麼?為什麼企業需要日誌審計?
- UI設計師需要學習什麼呢?UI
- 做UI設計需要具備什麼技能UI
- 網站的設計需要注意什麼?網站
- 什麼是設計模式?為什麼要使用設計模式?有什麼好處?設計模式
- 雲同步: 什麼是雲同步以及為什麼它是如此重要?
- 為什麼Kubernetes的儲存如此艱難?
- 為什麼GOPROXY對Golang開發如此重要Golang
- 為什麼魂系列的敘事如此迷人?
- Redis為什麼是單執行緒?為什麼有如此高的效能?Redis執行緒
- 為什麼需要拆分NFT?
- Elasticsearch:是什麼?你為什麼需要他?Elasticsearch
- 為什麼Web 設計會“死”?Web
- 學習計算機程式設計需要什麼基礎?計算機程式設計
- 程式設計沒點為什麼,生活就是十萬個為什麼程式設計
- 譯文 | 為什麼軟體架構如此重要?架構
- 為什麼async/await關鍵字是如此重要AI