905
汽車大全
為什麼System.Attribute的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<FooAttribute>().ToList<FooAttribute>();
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
然後我們通過如下的方式判定兩個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
實際上兩個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
但是不要以為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());
1: attribute1.Equals(attribute2) = False
2: attribute1.GetHashCode() == attribute2.GetHashCode() = False
3: attribute1.GetHashCode() == typeof(FooAttribute).GetHashCode() = False
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; i < fields.Length; i++)
8: {
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: }
微信公眾賬號:大內老A
微博:www.weibo.com/artech
如果你想及時得到個人撰寫文章以及著作的消息推送,或者想看看個人推薦的技術資料,可以掃描左邊二維碼(或者長按識別二維碼)關注個人公眾號(原來公眾帳號蔣金楠的自媒體將會停用)。
本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁麵明顯位置給出原文連接,否則保留追究法律責任的權利。
最後更新:2017-10-26 14:04:34