2012-01-12 3 views
14

Artech's blog에서 확인한 다음 댓글에 대한 토론이있었습니다. 그 블로그는 중국어로만 작성되었으므로 여기서 간단히 설명하겠습니다. 코드 재현 :GetHashCode 및 같음은 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(); 
    } 

Type.GetFields 그래서 속성은 다음 Remove 무시됩니다 기본 클래스에서 FooAttribute의 세 인스턴스 따라서 등가를 상속 (및 사용 방법은 임의로) 하나 걸립니다. 그래서 질문은 : 구현을위한 특별한 이유가 있습니까? 아니면 그냥 버그 야?

+0

나는 그것이 문제가되는 실제 시나리오를 이미징하는 데 어려움이 있지만 버그라고 말하고 싶습니다. connect.microsoft.com에보고 해보십시오. – Joe

+0

버그는 Equals()에 있습니다. GetHashCode()가 동일한 값을 반환해도 괜찮습니다. 동의하면 연결시 게시하십시오. 그게 큰 변화가 될 것이기 때문에 실제로 고칠 수 있을지는 의문입니다. –

+0

@HansPassant : 정확합니다. 여기에 필자가 코드를 게시하고이 컴퓨터에 디스어셈블러가 없기 때문에'GetHashCode' 코드를 게시합니다. –

답변

7

명백한 버그, 아니요. 좋은 생각 일 수도 있고 아닐 수도 있습니다.

한 가지는 다른 것으로 동일하다는 것은 무엇을 의미합니까? 우리가 정말로 원한다면 우리는 상당히 철학적이 될 수 있습니다.

  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. 는 위에 설명, 재귀 대칭 및 전이 특성을 제공하지 못할 경우

    . 과 Equals 사이의 관계가 위와 같지 않은 경우.
  2. 문서화 된 의미와 일치하지 않는 경우.
  3. 부적절한 예외가 발생하는 경우.
  4. 무한 루프로 방황하는 경우.
  5. 현실에서는 물건을 불구로 돌아 오는 데 너무 오래 걸리면 여기 이론과 연습이 있다고 주장 할 수 있습니다. Attribute에 대한 오버라이드 (override)으로

, 등호는 그것이 일치 않는 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); 

당신은 먼저 기준을 충족 항목을 요청하고 그것을 제거 할 동일 하나 질문 :

이 코드에서 비록 버그가 있습니다. getC이 제거 될 것으로 예상되는 문제 유형에 대한 평등의 의미를 검토하지 않고는 아무 이유도 없습니다.

당신이해야 할 것은 :이다

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

말, 우리는 Name == "C"과 다른과의 첫 번째 속성과 일치하는 술어를 사용합니다.

+0

'=='이 (가) 동등성 관계를 나타내지도 않는 내장 유형의 동작 범위를 고려하면'=='가'같음 '과 어떤 관계가 있어야한다는 기대는 도움이되지 않습니다 (부동 소수점 유형은 (long, double)과'(double, long)'오버플로를 정의하는 것이 암시 적 타입 변환 (typecast)이 'long'과 'double'을 전이 적으로 비교하는 유일한 방법이다. =='아마도 프레임 워크는 컴파일 된 모든 경우에'=='을 동등한 관계로 만들어야하지만 그렇지 않기 때문에 가장 좋다고 생각합니다 ... – supercat

+0

...'=='및 'Equals'는 오버랩을 가질 수는 있지만 실제로는 관계가없는 완전히 독립적 인 개념이 될 것입니다. – supercat

0

그래, 다른 사람들이 이미 코멘트에서 언급 한 것처럼 버그. 몇 가지 가능한 픽스를 제안 할 수 있습니다 :

옵션 1, Attribute 클래스에서 상속을 사용하지 마십시오. 기본 구현이 작동합니다. 다른 옵션은 사용자 정의 비교자를 사용하여 항목을 제거 할 때 참조 동등성을 사용하는지 확인하는 것입니다. 쉽게 비교자를 구현할 수 있습니다. 비교를 위해 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

무엇이 버그입니까? 어떤면에서 '평등'과 'GetHashCode'에 대한 일반적인 요구 사항을 충족시키지 못하거나 아니면 일치하지 않습니다. 'Attribute.Equals'에 대한 문서? –

+0

더 많은 퀘스트 ionable case는 Windows Phone 7에서 실행하면'RuntimeHelpers.GetHashCode'가 의도대로 작동하지 않는다는 것입니다. 여기서는 "직접적으로 사용하도록되어 있지 않습니다 ..."라는 메시지가 MS에서 버그 리포트에 대한 응답으로 존재합니다. 실제 문서에는 없습니다. 나는 그것을 사용하기보다는 https://github.com/hackcraft/Ariadne/blob/master/Collections/ReferenceEqualityComparer.cs 에서처럼 일리노이를 사용하는 것이 좋습니다. –

+0

@Jon Hanna, 인텐 트가 필드 값 비교이므로 제대로 작동하지 않습니다. WP7의 경우 API가 지원되지 않습니다. 그러나 OP는 WP7 목표를 언급하지 않습니다. –