2012-01-12 13 views
14

Artech's blogから見て、コメントに議論がありました。そのブログは中国語で書かれているので、私はここで簡単に説明します。再現するコード:GetHashCodeとEqualsはSystem.Attributeで正しく実装されていませんか?

[AttributeUsage(AttributeTargets.Class, Inherited = true, AllowMultiple = true)] 
public abstract class BaseAttribute : Attribute 
{ 
    public string Name { get; set; } 
} 

public class FooAttribute : BaseAttribute { } 

[Foo(Name = "A")] 
[Foo(Name = "B")] 
[Foo(Name = "C")] 
public class Bar { } 

//Main method 
var attributes = typeof(Bar).GetCustomAttributes(true).OfType<FooAttribute>().ToList<FooAttribute>(); 
var getC = attributes.First(item => item.Name == "C"); 
attributes.Remove(getC); 
attributes.ForEach(a => Console.WriteLine(a.Name)); 

コードはすべてFooAttributeを取得し、その名を「C」であるものを削除します。明らかに出力は "A"と "B"ですか?すべてがスムーズに進んでいれば、この質問は表示されません。実際には、理論的に "AC" "BC"または正しい "AB"を得ることができます(私のマシンにはACがあり、ブログの著者はBCになっています)。この問題は、System.AttributeのGetHashCode/Equalsの実装に起因します。実装のスニペット:

[SecuritySafeCritical] 
    public override int GetHashCode() 
    { 
     Type type = base.GetType(); 
 //*****NOTICE***** 
     FieldInfo[] fields = type.GetFields(BindingFlags.NonPublic 
      | BindingFlags.Public 
      | BindingFlags.Instance); 
 object obj2 = null; 
     for (int i = 0; i < fields.Length; i++) 
     { 
      object obj3 = ((RtFieldInfo) fields[i]).InternalGetValue(this, false, false); 
      if ((obj3 != null) && !obj3.GetType().IsArray) 
      { 
       obj2 = obj3; 
      } 
      if (obj2 != null) 
      { 
       break; 
      } 
     } 
     if (obj2 != null) 
     { 
      return obj2.GetHashCode(); 
     } 
     return type.GetHashCode(); 
    } 

これは基本クラスから継承されたプロパティがFooAttribute(そしてRemoveの3つのインスタンスの等価性は、したがって、無視されるので、Type.GetFieldsを使用します方法はをランダムにとする)。したがって、問題は、実装に特別な理由があるかどうかです。それとも単なるバグでしょうか?

+0

私はそれが問題を引き起こす現実のシナリオをイメージングするのが難しいですが、それはバグだと思います。 connect.microsoft.comで報告することもできます。 – Joe

+0

バグはEquals()にありますが、GetHashCode()が同じ値を返すことは大丈夫です。合意し、接続時にこれを投稿してください。それが突然の変化になるので、私は実際に彼らがそれを修正することを疑う。 –

+0

@HansPassant:あなたは正しいです。ここで私は 'GetHashCode'のコードを投稿しています。なぜなら、著者がコードを投稿し、このPCに逆アセンブルされていないからです。 –

答えて

7

明確なバグです。良いアイデアかもしれないし、おそらくそうでないかもしれない。

1つのものが他と等しいとはどういう意味ですか?私たちが本当に欲しいのであれば、私たちはかなり哲学的になる可能性があります。

  1. 平等は反射的である:アイデンティティは、平等を伴う

    わずかに哲学的なので、保持しなければならない点がいくつかあります。 x.Equals(x)を保持する必要があります。

  2. 等価は対称です。 x.Equals(y)の場合はy.Equals(x)!x.Equals(y)の場合は!y.Equals(x)となります。
  3. 等価は推移的です。 x.Equals(y)y.Equals(z)の場合は、x.Equals(z)です。

これ以外にもいくつかありますが、これらはEquals()のコードで直接反映されます。

object.Equals(object)IEquatable<T>.Equals(T)IEqualityComparer.Equals(object, object)IEqualityComparer<T>.Equals(T, T)==または!=ののオーバーライドの実装では、上記を満たしていない場合、それは明確なバグです。

.NETでの同等性を反映するその他の方法は、object.GetHashCode(),IEqualityComparer.GetHashCode(object)およびIEqualityComparer<T>.GetHashCode(T)です。ここには単純なルールがあります:

a.Equals(b)の場合は、それはa.GetHashCode() == b.GetHashCode()を保持する必要があります。同等物はIEqualityComparerIEqualityComparer<T>の場合に相当します。

これが成立しない場合は、もう一度バグがあります。

これを超えると、平等が意味する必要があるすべてのルールはありません。それは、それ自身のEquals()オーバーライドによって提供されるクラスのセマンティクス、または等価比較子によってそれに課せられたクラスのセマンティクスに依存する。もちろん、これらのセマンティクスは、明らかに明白であるか、あるいはクラスまたは等価比較子で文書化されるべきである。全てにおいて

、どのようEqualsおよび/またはGetHashCodeバグ持っています:それは先に詳述反射的、対称と推移特性を提供するために失敗した場合

  1. を。
  2. GetHashCodeEqualsの関係が上記のとおりでない場合。
  3. 文書化されたセマンティクスと一致しない場合。
  4. 不適切な例外がスローされた場合。
  5. 無限ループに迷い込んだ場合。
  6. 実際には、物事を傷つけるほど時間がかかっても、ここでは理論対練習問題があると主張することができます。 Attributeに上書きして

、イコールは反射的、対称と推移性質を持っている、それはGetHashCodeだ、それが一致しない、そしてそれはEqualsオーバーライドだのドキュメントは、次のとおりです。

このAPIをサポートしています。 NET Frameworkインフラストラクチャであり、コードから直接使用するためのものではありません。

あなたの例がそれを否定するとは本当に言えません。

これらの点で不平を言うコードは失敗しないので、バグではありません。

var attributes = typeof(Bar).GetCustomAttributes(true).OfType<FooAttribute>().ToList<FooAttribute>(); 
var getC = attributes.First(item => item.Name == "C"); 
attributes.Remove(getC); 

あなたが最初の基準を満たすアイテムをお願いし、それを除去することに等しい1を求める:

このコードでただしバグがあります。 getCが削除されると予想される問題のタイプの平等のセマンティクスを調べなければ、理由はありません。

何をすべきことである:ある

bool calledAlready; 
attributes.RemoveAll(item => { 
    if(!calledAlready && item.Name == "C") 
    { 
    return calledAlready = true; 
    } 
}); 

と言って、我々はName == "C"なし他との最初の属性に一致する述語を使用しています。

+0

'=='での組み込み型の振る舞いが等価関係を表現していない場合(浮動小数点型でも可能です)、 '=='が 'Equals'との関係を持つべきであるという期待は、役に立たないです。 (long、double)と '(double、long)'オーバーロードを定義することである。 == 'おそらくフレームワークは' == 'をコンパイルするすべてのケースで等価関係にするべきですが、そうでないので、私はそれが最も良いと思います... – supercat

+0

...' == 'と'Equals'は完全に独立したコンセプトであり、重なり合っても実際の関係はありません – supercat

0

はい、他のバグとしてコメントに記載されています。

オプション1、属性クラスで継承を使用しないでください。これにより、デフォルトの実装が機能するようになります。もう1つのオプションは、アイテムを削除するときに参照の等価性を使用していることを確認するためにカスタムの比較関数を使用することです。あなたは十分に簡単に比較機能を実装することができます。比較のためにObject.ReferenceEqualsを使用するだけで、タイプのハッシュコードを使用するか、System.Runtime.CompilerServices.RuntimeHelpers.GetHashCodeを使用することができます。

public sealed class ReferenceEqualityComparer<T> : IEqualityComparer<T> 
{ 
    bool IEqualityComparer<T>.Equals(T x, T y) 
    { 
     return Object.ReferenceEquals(x, y); 
    } 
    int IEqualityComparer<T>.GetHashCode(T obj) 
    { 
     return System.Runtime.CompilerServices.RuntimeHelpers.GetHashCode(obj); 
    } 
} 
+0

どうしたのですか?どのようにして' Equals'と 'GetHashCode'の一般的な要件を満たしていないか、 'Attribute.Equals'のドキュメント? –

+0

もっとクエストあなたがWindows Phone 7でそれを走らせるなら、あなたが意図した通りに 'RuntimeHelpers.GetHashCode'が動作しないという事実です。ここでは、バグレポートに対するMSの応答に「直接使用するつもりはありません」とありますが、実際のドキュメントには表示されません。私はhttps://github.com/hackcraft/Ariadne/blob/master/Collections/ReferenceEqualityComparer.csのようにILを使用するのではなく、ILを使用することをお勧めします。 –

+0

@Jon Hanna、インテントがフィールド値の比較であり、正しく機能しないため、バグ。 WP7については、あなたは正しいですAPIはサポートされていません。しかし、OPはWP7の目標について何の言及もしていない。 –