何时调用自定义属性类的析构函数?

the*_*row 5 .net c# attributes custom-attributes

说我有这门课:

[AttributeUsage(AttributeTargets.Method)] 
public class MyAttribute : Attribute 
{ 
  public MyAttribute() 
  { 
    // Do stuff
  }

  ~MyAttribute()
  {
    // When is this called? When the function ends? Whenever the GC feels like?
  }
}
Run Code Online (Sandbox Code Playgroud)

And*_*tan 2

在 Reflector 中调用 GetCustomAttributes 的示例之后,代码的托管部分(即转换到运行时并成为外部调用的点)位于 CustomAttribute.GetCustomAttributes 中。

此时,该方法正在检查正在为其加载属性的对象周围的元数据的字节。

那里有代码,然后进一步反射以找到被调用的运行时构造函数。例如

[MyAttribute]
Run Code Online (Sandbox Code Playgroud)

将调用默认值,而

[MyAttribute(1, "hello", typeof(T))]
Run Code Online (Sandbox Code Playgroud)

将调用一个需要(Int, String, Type).

我看不到任何证据表明执行了任何实例缓存,因此这意味着属性实例是在反映时按需创建的。

证明

上述托管运行时转换发生在 CustomAttribute._CreateCaObject 处。虽然静态分析此方法是否确实缓存了它创建的实例并不容易(它确实可能以内存缓冲区指针的形式获取足够的状态信息,大概指示属性声明所在的元数据中的位置),但我们可以查看事实:

  • 构造函数总是被调用,并且
  • 新的构造函数参数总是被读取并输入。

这告诉我该属性总是被构造的。

当然,我们可以通过在测试中编写一段代码来对此进行测试。

[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");
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

因此,元数据属性的有效使用是将它们缓存在用户代码中,当然,除非该属性以某种方式可变,使其不适用于给定的所有实例T,或所有“实例”(在引号中,因为当然方法仅在内存中存储一​​次)m类型实例的方法T)。

因此,在此之后,一旦代码中对某个属性的所有引用都已被清空,GC 就可以使用该属性。对于该属性的所有成员也是如此。

因此,使用 GetCustomAttributes() 检索属性、使用它然后丢弃引用的方法刚刚释放了该属性的新实例,以便 GC 在需要时进行清理。

因此,属性实例与所有类实例一样受完全相同的内存管理和生命周期规则的控制;因此@PieterG 在他的回答中所说的是正确的 - 在释放对该属性的所有引用后,可以随时调用析构函数。