为什么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() = 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; 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
如果你想及时得到个人撰写文章以及著作的消息推送,或者想看看个人推荐的技术资料,可以扫描左边二维码(或者长按识别二维码)关注个人公众号(原来公众帐号蒋金楠的自媒体将会停用)。
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

原文链接

时间: 2024-11-08 18:15:26

为什么System.Attribute的GetHashCode方法需要如此设计?的相关文章

java中System.out.println( );这个方法是

问题描述 java中System.out.println( );这个方法是 java中System.out.println( );这个方法是System类里的out对象的println方法的意思,即类.对象.方法 解决方案 对,就是这个意思.... 解决方案二: 假设有一个实例Object o则当System.out.println(o);时,它其实是自动调用o.toString()方法,然后输出该方法返回的string字符串.当System.out.println(o.toString());

LINQ to Entities 不识别方法“System.String ToString(System.String)”因此该方法无法转换为存储表达式

问题描述 varweather=(fromcindb.CCISWeatherForecastswherec.Date.ToString("yyyy-MM-dd")=="2011-11-10"selectc).FirstOrDefault();LINQtoEntities不识别方法"System.StringToString(System.String)",因此该方法无法转换为存储表达式. 解决方案 解决方案二:c.Date.ToString(&q

Angularjs使用directive自定义指令实现attribute继承的方法详解_AngularJS

本文实例讲述了Angularjs使用directive自定义指令实现attribute继承的方法.分享给大家供大家参考,具体如下: 一.Html代码: <!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8&

[原创]一个为Process取得SYSTEM令牌的简单方法

一个为Process取得SYSTEM令牌的简单方法 (测试平台:windows XP sp2)             我们知道NT平台本地最高权限用户是SYSTEM,如果以SYSTEM用户打开 regedit.exe,就可以看到SAM目录下的内容,而用administrator用户却不能(除 非通过手动赋予).那么如何创建一个带有SYSTEM Token的进程呢?         我采用的方法参考了一些技术文章,只是简单的Hook Windows原生API : NtCreateProcessE

c#代码-c# System.IO 关于write方法问题

问题描述 c# System.IO 关于write方法问题 本人使用ZOutputStream 解压数据 public byte[] deCompression(byte[] bytes) { MemoryStream outStream = new MemoryStream(); ZOutputStream zOutputStream = new ZOutputStream(outStream); zOutputStream.Write(bytes, 0, bytes.Length); zOu

为什么android会有两种启动Aactivity的方法,这样设计的初衷是什么

问题描述 为什么android会有两种启动Aactivity的方法,这样设计的初衷是什么 startActivity(Intent)/startActivityForResult(Intent):来启动一个Activity 这两种方法有和区别和联系 解决方案 参考这个, 解决方案二: 另一种可以反回信息的,两个activity可以交互 解决方案三: android:两种启动activity的方法Android Activity启动的两种方法android 启动 service 的两种方法

《JavaScript应用程序设计》一一2.9 方法API的设计

2.9 方法API的设计 JavaScript中的语言特性可以帮助你更好地设计方法API,包括参数命名.函数多态.链式调用.lambda等.你应该了解并掌握这些语言特性,以便在合适的时候将它们引入.当你在设计方法API时,要时刻牢记章首介绍的四项原则,这里再强调一遍:· 保持简单· 一次只做一件事情· 不要重复造轮子· 少即是多

[C#]Attribute特性(2)——方法的特性及特性参数

 上篇博文[C#]Attribute特性介绍了特性的定义,类的特性,字段的特性,这篇博文将介绍方法的特性及特性参数相关概念. 3.方法的特性        之所以将这部分单列出来进行讨论,是因为对方法的特性查询的反射代码不同于对类的特性查询的反射代码.在这个例子里,我们将使用一个特性用来定义一种可进行事务处理的方法.    1 public class TransactionableAttribute : Attribute 2 { 3 public TransactionableAttribu

Win7开机提示无法连接到System notification service解决方法

  微软系统随着时代的进步,也在不断的推出新的系统,从最开始的win7.win8到之后的win10系统,很多人在追逐着微软的脚步,也有不少热衷粉执着于最初的win7系统,的确win7系统作为最经典的系统有它先进之处,但是最近有win7用户来反馈说,Win7系统开机出现Windows无法连接到System notification service的情况,开机的时候会出现各种各样的问题,这是难免的,下面小编就为大家介绍Win7开机提示无法连接到System notification service的