Reading this post try to imagine how much I have worked, with my code, before create a test to test the behavior of the class Attribute.
Assertion
In .Net an Attribute is a class as any other and may have state and behavior.
True ? so and so… first of all, as class, it has some restriction as: it must inherit from System.Attribute and can’t be a generic class; second as any class it was implemented by a human with his fantasy (and this is the problem).
Perhaps, add behavior to an Attribute is not so common but, since even Microsoft is doing it, we can assume that it is a legal usage… anyway this is not the problem.
The Equal and GetHashCode issue
[AttributeUsage(AttributeTargets.Property)]
public class MyMarkerAttribute: Attribute
{
}
var attribute = new MyMarkerAttribute();
var another = new MyMarkerAttribute();
Console.WriteLine("Using == the result is: " + (attribute == another));
Console.WriteLine("Using Equals the result is:" + attribute.Equals(another));
Console.WriteLine("attribute hash:" + attribute.GetHashCode() + " another hash:" + another.GetHashCode());
This is the output:
Using == the result is: False
Using Equals the result is:True
attribute hash:10001680 another hash:10001680
Note: I haven’t implemented neither Equals nor GetHashCode. Every thing is as expected ? In a normal class this is not the expected behavior, by the way so far I can accept the situation.
Now I will change the attribute adding a integer property (about integer you know that its hash-code is its value):
[AttributeUsage(AttributeTargets.Property)]
public class MyMarkerAttribute: Attribute
{
public int Min { get; set; }
}
and now the output is:
Using == the result is: False
Using Equals the result is:True
attribute hash:0 another hash:0
What ? The HashCode now is cero ? … wait… wait… it does not end here.
[AttributeUsage(AttributeTargets.Property)]
public class MyMarkerAttribute: Attribute
{
public int Min { get; set; }
public int Max { get; set; }
}
var attribute = new MyMarkerAttribute { Min=5, Max= 456 };
var another = new MyMarkerAttribute { Min = 5, Max = 123 };
And the output is:
Using Equals the result is:False
attribute hash:5 another hash:5
As you can see the hash code is the hash of the first property (the same if it would be a field) and the most important inconsistence is that the two instance result as NOT EQUALS but they has the same HashCode.
[AttributeUsage(AttributeTargets.Property)]
public class MyMarkerAttribute: Attribute
{
private int myBergamota = 123;
public int Min { get; set; }
public int Max { get; set; }
}
var attribute = new MyMarkerAttribute {Min = 5, Max = 456};
var another = new MyMarkerAttribute { Min = 5, Max = 456 };
The output is:
Using Equals the result is:True
attribute hash:123 another hash:123
So the HashCode is the hash-code of the first property and the Equals work using the state of each property of instance of the attribute… such fantasy…
[AttributeUsage(AttributeTargets.Property)]
public class MyMarkerAttribute: Attribute
{
private int myBergamota = 123;
private int min;
public int Min
{
get { return min; }
set { min = value; }
}
public int Max { get; set; }
public void SetFieldValue()
{
myBergamota = 789;
}
}
var attribute = new MyMarkerAttribute {Min = 5, Max = 456};
var another = new MyMarkerAttribute { Min = 5, Max = 456 };
Console.WriteLine("attribute hash:" + attribute.GetHashCode() + " another hash:" + another.GetHashCode());
another.SetFieldValue();
Console.WriteLine("Using Equals the result is:" + attribute.Equals(another));
Console.WriteLine("attribute hash:" + attribute.GetHashCode() + " another hash:" + another.GetHashCode());
And the output is:
attribute hash:123 another hash:123
Using Equals the result is:False
attribute hash:123 another hash:789
What ? The hash changing at run-time ? but one of your recommendation is not that the hash-code should never change during execution ? what happen with Attribute ?
Wait a minute… who said that the hash-code change at run-time; it depend!!! depend about what… try to guess…
Now I will change only the position of the declaration of the private field:
[AttributeUsage(AttributeTargets.Property)]
public class MyMarkerAttribute: Attribute
{
private int min;
private int myBergamota = 123;
public int Min
{
get { return min; }
set { min = value; }
}
public int Max { get; set; }
public void SetFieldValue()
{
myBergamota = 789;
}
}
As you can see the only change is the position of the declaration, of the field myBergamota, from the first position to the second inside the class implementation; nothing more than the position inside the implementation!!! and now the output is:
attribute hash:5 another hash:5
Using Equals the result is:False
attribute hash:5 another hash:5
Re WHAT!???!!???! now I have the same hash-code ?!?!?!?!? only moving a declaration inside the class ?!?!?!?
Now you know that there is an ugly issue inside the .NET’s Attribute implementation and you may have a very very ugly surprise if you are writing a framework using attribute to cache metadata especially if the Attribute has behavior changing the internal state of the instance.
ResumingThe Attribute class override Equals and GetHashCode. The Equals is using the whole internal state, the GetHashCode is using only state of the first field and its value may change during execution.
The workaround
If you want a deterministic behavior the solution is simple and is the same you are applying in your classes: override Equals and GetHashCode.
If you don’t need a special behavior the implementation to use the Reference (the base of any other class) is:
public override bool Equals(object obj)
{
return ReferenceEquals(this, obj);
}
public override int GetHashCode()
{
return RuntimeHelpers.GetHashCode(this);
}Conclusion
I like the application of your fantasy during software development, but be moderate please.
