c# - GetHashCode 和 Equals 在 System.Attribute 中实现不正确?

标签 c# .net attributes equals gethashcode

Artech's blog看然后我们在评论中进行了讨论。由于那个博客只有中文,所以我在这里做一个简单的解释。重现代码:

[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 所以从基类继承的属性被忽略,因此 FooAttribute 的三个实例是等价的(然后是 Remove 方法随机)。那么问题来了:执行上有什么特殊原因吗?或者这只是一个错误?

最佳答案

一个明显的错误,不。一个好主意,也许是也可能不是。

一件事等于另一件事是什么意思?如果我们真的想的话,我们可以变得非常哲学。

只是有点哲学,有几件事必须坚持:

  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) .

还有一些其他的,虽然只有这些可以直接反射(reflect)在 Equals() 的代码中。一个人。

如果实现重写 object.Equals(object) , IEquatable<T>.Equals(T) , IEqualityComparer.Equals(object, object) , IEqualityComparer<T>.Equals(T, T) , ==!=不满足以上,就是明显的bug。

.NET 中反射(reflect)平等的其他方法是 object.GetHashCode() , IEqualityComparer.GetHashCode(object)IEqualityComparer<T>.GetHashCode(T) .这里有一个简单的规则:

如果a.Equals(b)那么它必须持有 a.GetHashCode() == b.GetHashCode() .等价于 IEqualityComparerIEqualityComparer<T> .

如果那不成立,那么我们又遇到了一个错误。

除此之外,没有关于平等必须意味着什么的全面规则。取决于自身提供的类的语义Equals()覆盖或由相等比较器强加给它的那些。当然,这些语义应该非常明显,或者记录在类或相等比较器中。

总而言之,Equals 是如何实现的?和/或 GetHashCode有一个错误:

  1. 如果它无法提供上面详述的自反、对称和传递属性。
  2. 如果GetHashCode之间的关系和 Equals和上面的不一样。
  3. 如果它与其记录的语义不匹配。
  4. 如果它抛出不适当的异常。
  5. 如果它进入无限循环。
  6. 在实践中,如果返回所需的时间太长以至于削弱了东西,尽管有人可能会争辩说这里存在理论与实践的区别。

覆盖了 Attribute , equals 确实具有自反、对称和传递属性,它是 GetHashCode确实匹配它,它的文档是 Equals覆盖是:

This API supports the .NET Framework infrastructure and is not intended to be used directly from your code.

你真的不能说你的例子反驳了这一点!

由于您提示的代码在这些方面都没有失败,所以它不是错误。

这段代码中有一个错误:

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"的谓词。没有别的。

关于c# - GetHashCode 和 Equals 在 System.Attribute 中实现不正确?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/8839445/

相关文章:

c# - 在独特的部分拆分字符串,再次排序和连接

c# - 居中对齐最后一行中的 GridView 项目

.net - 解析来自 FTP LIST 命令的响应(语法变体)

php - 在 html 属性中使用 php 变量的最佳方法是什么?

c# - 我可以废弃某个属性的特定用法吗?

c# - 如何将整个流加载到 MemoryStream 中?

c# - 日期时间参数抛出异常

c# - SQL 中的 "Dynamic"表?

c# - 无法访问派生类中的 protected 方法

dll - 无法从程序集 "...\Microsoft.Search.Interop.dll"中嵌入互操作类型,因为它缺少GuidAttribute属性