为什么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二、通过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() = True3: 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());
1: attribute1.Equals(attribute2) = False 2: attribute1.GetHashCode() == attribute2.GetHashCode() = False3: 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; 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: }

昨天我为了Attribute 的一个小问题后耗费了大半天的精力,虽然最终找到了问题的症结并解决了问题,但是我依然不知道微软如此设计的目的何在。闲话少说,我们先来演示一下我具体遇到的问题如何发生的。
浙公网安备 33010602011771号