なぜ?AttributeのGetHashCodeメソッドはこのように設計する必要がありますか?

13714 ワード

昨日、私は「拡張によりASPを改善する.NET MVCの検証メカニズム[使用編]」を実現したとき、Attributeの小さな問題のために半日も精力を費やしました.最終的に問題の結末を見つけて問題を解決しましたが、マイクロソフトがこのように設計した目的が何なのか分かりません.余計なことは言わないで、私が具体的に直面した問題がどのように発生したのかを説明しましょう.
目次:一、問題再現二、AttributeのEqualsメソッドとGetHashCodeメソッドによる対等判断三、AttributeオブジェクトとAttributeタイプのHashCode四、FooAttributeに属性/フィールド五を追加する場合、AttributeのGetHashCodeメソッドはどのように実現されますか?

一、問題の再現


次のコード断片に示すように,2つのAttributeを定義した.抽象的なBaseAttributeではName属性が定義されていますが、FooAttributeはBaseAttributeから直接継承され、属性やフィールドは定義されていません.タイプBarでは,それぞれA,B,Cという名前の3つのFooAttribute特性を適用した.
   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プロパティのリストをフィルタします.このリストには、名前プロパティがA、B、Cの3つの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法による対等判断


次に,2つのFooAttributeオブジェクトの対等性を以下のように判定した.次のコード断片に示すように、コンストラクション関数を直接呼び出して、Nameプロパティが「ABC」および「123」に設定された2つのFooAttributeオブジェクトを作成します.最後の2つのコードは、EqualsとHashCodeを呼び出すことによって、2つの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


実際に2つのFooAttributeオブジェクトのHashCodeとFooAttributeタイプは等しい.この目的のために、typeof(FooAttribute)とFooAttributeオブジェクトのHashCodeとの間のピアツーピア性を判断する追加の2行のコードを追加しました.
   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());

2つの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: }