从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
方法随机)。那么问题来了:执行上有什么特殊原因吗?或者这只是一个错误?
最佳答案
一个明显的错误,不。一个好主意,也许是也可能不是。
一件事等于另一件事是什么意思?如果我们真的想的话,我们可以变得非常哲学。
只是有点哲学,有几件事必须坚持:
- 平等是自反的:身份意味着平等。
x.Equals(x)
必须持有。 - 平等是对称的。如果
x.Equals(y)
然后y.Equals(x)
如果!x.Equals(y)
然后!y.Equals(x)
. - 平等是可传递的。如果
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()
.等价于 IEqualityComparer
和 IEqualityComparer<T>
.
如果那不成立,那么我们又遇到了一个错误。
除此之外,没有关于平等必须意味着什么的全面规则。取决于自身提供的类的语义Equals()
覆盖或由相等比较器强加给它的那些。当然,这些语义应该非常明显,或者记录在类或相等比较器中。
总而言之,Equals
是如何实现的?和/或 GetHashCode
有一个错误:
- 如果它无法提供上面详述的自反、对称和传递属性。
- 如果
GetHashCode
之间的关系和Equals
和上面的不一样。 - 如果它与其记录的语义不匹配。
- 如果它抛出不适当的异常。
- 如果它进入无限循环。
- 在实践中,如果返回所需的时间太长以至于削弱了东西,尽管有人可能会争辩说这里存在理论与实践的区别。
覆盖了 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/