Che*_*hen 14 .net c# attributes equals gethashcode
从Artech的博客看,然后我们在评论中进行了讨论.由于该博客仅以中文撰写,我在此处作简要说明.代码重现:
[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));
Run Code Online (Sandbox Code Playgroud)
代码全部获取FooAttribute并删除名称为"C"的代码.显然输出是"A"和"B"?如果一切顺利,你就不会看到这个问题.事实上,理论上你会得到"AC""BC"甚至是"AB"(我的机器上有AC,博客作者有BC).问题源于System.Attribute中GetHashCode/Equals的实现.实现的片段:
[SecuritySafeCritical]
public override int GetHashCode()
{
Type type = base.GetType();
Run Code Online (Sandbox Code Playgroud)
Run Code Online (Sandbox Code Playgroud)//*****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();
}
Run Code Online (Sandbox Code Playgroud)
它使用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).还有其他一些,但只有这些可以直接反映在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().相当于IEqualityComparer和IEqualityComparer<T>.
如果这不成立,那么我们又有一个错误.
除此之外,没有关于平等必须意味着什么的总体规则.它取决于由自己的Equals()覆盖提供的类的语义,或者由相等比较器强加给它的类的语义.当然,这些语义应该是公然明显的,或者在类或相等比较器中记录.
总之,一个Equals和/或一个GetHashCode有bug的方式:
GetHashCode,并Equals没有如上述.有了覆盖Attribute,equals确实具有自反,对称和传递属性,它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);
Run Code Online (Sandbox Code Playgroud)
您首先要求一个符合标准的项目,然后要求删除一个等于它的项目.没有理由不检查相关类型的相等语义,以期望getC将被删除.
你应该做的是:
bool calledAlready;
attributes.RemoveAll(item => {
if(!calledAlready && item.Name == "C")
{
return calledAlready = true;
}
});
Run Code Online (Sandbox Code Playgroud)
也就是说,我们使用与第一个属性匹配Name == "C"而不与其他属性匹配的谓词.