Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Do custom attribute classes inherit the "Inherited" AttributeUsage flag?

Consider the following scenario:

  • A base attribute class BaseAttribute has an AttributeUsageAttribute specifying that it is not inheritable (Inherited = False).
  • A derived attribute class DerivedAttribute inherits from that base attribute class.
  • A base domain class Base has the derived attribute applied.
  • A domain class Derived inheriting from the base domain class is asked for its custom attributes, including inherited attributes (inherit: true).

Here is the corresponding code:

using System;
using System.Linq;

namespace ConsoleApplication26
{
  class Program
  {
    static void Main ()
    {
      var attributes = typeof (Derived).GetCustomAttributes (true);
      foreach (var attribute in attributes)
      {
        Console.WriteLine (
            "{0}: Inherited = {1}",
            attribute.GetType().Name,
            attribute.GetType().GetCustomAttributes (typeof (AttributeUsageAttribute), true).Cast<AttributeUsageAttribute>().Single().Inherited);
      }
    }
  }

  [AttributeUsage (AttributeTargets.All, Inherited = false)]
  public class BaseAttribute : Attribute
  {
  }

  public class DerivedAttribute : BaseAttribute
  {
  }

  [Derived]
  public class Base
  {
  }

  public class Derived : Base
  {
  }
}

In this scenario, the GetCustomAttributes API returns an instance of the DerivedAttribute class. I would have expected it to not return that instance because http://msdn.microsoft.com/en-us/library/system.attributeusageattribute.aspx says that the AttributeUsageAttribute is itself inheritable.

Now, is this a bug, or is it to be expected/documented somewhere?

Note (2013-02-20): Experiments show that the AttributeTargets part of the BaseAttribute class is indeed inherited by the DerivedAttribute class. E.g., when I change the allowed targets on BaseAttribute to AttributeTargets.Method, the C# compiler won't allow me to apply DerivedAttribute to a class. Therefore, it doesn't make sense that the Inherited = false part is not inherited by DerivedAttribute, and I'm thus inclined to think of a bug in the implementation of GetCustomAttributes.

like image 294
Fabian Schmied Avatar asked May 31 '26 18:05

Fabian Schmied


1 Answers

At compile time, metadata for class Derived is constructed. The compiler checks the inheritance chain to see if any attributes need to be inherited. There it finds attribute DerivedAttribute. (I don't know if the attributes on Object are considered, but they are set to be not inherited anyway.) To see if it must be inherited, it checks for the AttributeUsageAttribute on the discovered attribute class. It does not have one of it's own; the language specification states:

An attribute class X not having an AttributeUsage attribute attached to it, as in

class X : Attribute { ... }

is equivalent to the following:

[AttributeUsage(
    AttributeTargets.All,
    AllowMultiple = false,
    Inherited = true)
]
class X : Attribute { ... }

My guess is that the compiler behaves this way and creates metadata which indicates that class Derived inherits custom attribute DerivedAttribute.

Then, at runtime, when getting custom attributes for class Derived, the class-ineritance chain is followed from class Derived to class Base, where a DerivedAttribute is found. Upon inspection, it can be seen that class DerivedAttribute has no AttributeUsageAttribute of it's own, but since AttributeUsageAttribute can be inherited, it inherits one from a parent.

During runtime the inheritance is performed as expected and the AttributeUsageAttribute of BaseAttribute is inherited, which has Inherited set to false.

I elongated the inheritance chain to check if the inherited attribute is that of the closest parent and not that of the root:

using System;
using System.Linq;

public class Program
{
    public static void Main ()
    {
        var attributes = typeof (Level4Class).GetCustomAttributes (true);
        foreach (var attribute in attributes)
        {
            Console.WriteLine (
                "{0}: Inherited = {1}; ClassLevel = {2}",
                attribute.GetType().Name,
                attribute.GetType().GetCustomAttributes (typeof (AttributeUsageAttribute), true).Cast<AttributeUsageAttribute>().Single().Inherited,
                ((Level1Attribute)attribute).ClassLevel);
        }
    }
}

[AttributeUsage (AttributeTargets.Class, Inherited = false)]
public class Level1Attribute : Attribute
{
    public int ClassLevel { get; set; }
}

public class Level2Attribute : Level1Attribute
{
}

public class Level3Attribute : Level2Attribute
{
}

[Level1(ClassLevel=1)]
[Level2(ClassLevel=1)]
[Level3(ClassLevel=1)]
public class Level1Class
{
}

[Level1(ClassLevel=2)]
[Level2(ClassLevel=2)]
[Level3(ClassLevel=2)]
public class Level2Class : Level1Class
{
}

public class Level3Class : Level2Class
{
}

public class Level4Class : Level3Class
{
}

Console output:

Level3Attribute: Inherited = False; ClassLevel = 2
Level2Attribute: Inherited = False; ClassLevel = 2

It seems that the compiler follows the language specification, but the runtime follows the coded definitions.

like image 105
Galactus Avatar answered Jun 03 '26 07:06

Galactus