Following an example call of GetCustomAttributes through in Reflector, the managed part of the code (i.e. the point at which it transitions to the runtime and becomes an external call) is down in CustomAttribute.GetCustomAttributes.
At this point, the method is examining the bytes of the metadata surrounding the object for which the attributes are being loaded.
There is code in there which then does further reflection to find the runtime constructor being called. E.g.
[MyAttribute]
Would be calling the default, whereas
[MyAttribute(1, "hello", typeof(T))]
Would be calling a constructor that takes (Int, String, Type)
.
I can't see any evidence in there that any caching of instances is performed which therefore means that attribute instances are created on demand when they are reflected.
Proof
The aforementioned managed-runtime transition happends at CustomAttribute._CreateCaObject. Whilst not easy to statically analyse whether this method does in fact cache the instances it creates (it does potentially get enough state information in the form of a memory buffer pointer presumably indicating the location in metadata where the attribute declaration resides) we can look at the facts:
- The constructor is always called, and
- New constructor parameters are always read and fed in.
This tells me that the attribute is always constructed.
We can test for this, of course, by writing a piece of code in a test.
[TestMethod]
public void TestMethod1()
{
//if running in MSTest you have to allow for the test runner to reflect
//over the class as it looks for the TestClass attribute - therefore if our
//assumption is correct that a new instance is always constructed when
//reflecting, our counter check should start at 2, not 1.
Type t = typeof(AttributeTest);
var attributes =
t.GetCustomAttributes(typeof(AttributeTest.TheAttributeAttribute), false);
//check counter
Assert.AreEqual(2, AttributeTest.TheAttributeAttribute.Counter);
var attributes2 =
t.GetCustomAttributes(typeof(AttributeTest.TheAttributeAttribute), false);
//should be one louder (sorry, 'one bigger' - the Spinal Tap influence :) )
Assert.AreEqual(3, AttributeTest.TheAttributeAttribute.Counter);
}
[TheAttribute]
public class AttributeTest
{
public class TheAttributeAttribute : Attribute
{
static int _counter = 0;
public static int Counter { get { return _counter; } }
public TheAttributeAttribute()
{
_counter++;
Console.WriteLine("New");
}
}
}
Therefore an efficient use of metadata attributes would be to cache them in user code, unless of course the attribute is mutable in some way that makes it not applicable for all instances of a given T
, or all 'instances' (in quotes because of course a method is only stored in memory once) of a method m
for instances of type T
).
Following this, therefore, an attribute is available to the GC once all references to it in code have been nulled. The same is true for all members of that attribute as well.
Therefore a method which uses GetCustomAttributes() to retrieve an attribute, uses it and then throws away the reference has just released a new instance of that attribute for the GC to clean up when it needs to.
Therefore - attribute instances are governed by the exact same memory management and lifetime rules as all class instances; therefore what @PieterG says in his answer is correct - the destructor could be called at any time after all references to that attribute have been released.