When you compile code for the runtime, it is converted into common intermediate language (CIL) and placed inside a portable executable (PE) file along with metadata generated by the compiler. Attributes allow you to place extra descriptive information into metadata that can be extracted using runtime reflection services.
Common Attributes
A couple notable attributes are built into .NET Core:
[Obsolete]
is useful for providing declarative documentation, and supports aboolean
parameter to escalate it from a compiler warning to a compiler error.[Conditional]
is useful for stripping out calls to the target method if the input string doesn’t match a#define
directive; useful in debugging.[CallerMemberName]
is useful for injecting the name of the method that is calling another method; useful in eliminating magic strings.
Defining an Attribute
Snippets runnable at https://godbolt.org/z/xYKqeqhMa.
using System;
using System.Reflection;
// By convention, all attribute names end with "Attribute".
// System.AttributeUsageAttribute is used to define key characteristics of the
// attribute, e.g., targets, inheritance, multiplicity, etc.
[AttributeUsage(AttributeTargets.All, Inherited = true, AllowMultiple = false)]
public class DeveloperAttribute : Attribute
{
private string name;
private string level;
private bool reviewed;
// Define required parameters like name and level as positional params. Params
// to the ctor are limited to simple types/literals, e.g., bool, int, double,
// string, Type, enums, etc., and arrays of those types.
public DeveloperAttribute(string name, string level)
{
this.name = name;
this.level = level;
this.reviewed = false;
}
// Define Name property, a read-only attribute.
public virtual string Name
{
get { return name; }
}
// Define Level property, a read-only attribute.
public virtual string Level
{
get { return level; }
}
// Define Reviewed property, a read/write attribute. This can be set using
// optional named parameters.
public virtual bool Reviewed
{
get { return reviewed; }
set { reviewed = value; }
}
}
Mental Model for Attribute Syntax
Code like:
// Although the attribute is called "DeveloperAttribute", you can drop the
// "Attribute" suffix when using the attribute.
[Developer("Musa", "63", Reviewed = true)]
class SampleClass {}
… is conceptually equivalent to:
var anonymousAuthorsObject = new Developer("Musa", "63")
{
Reviewed = true
};
… with the caveat that the code is not executed until SampleClass
is queried
for attributes (e.g., via Attribute.GetCustomAttribute
).
In addition to lazy instantiation, Attribute
objects are instantiated each
time. Calling GetCustomAttribute
twice in a row returns two different
instances of the Attribute
.
AttributeTargets
AttributeTargets
controls the program elements to which the attribute can be
applied, e.g., class, method, entire assembly, etc.
// Applied to a method
[ValidatedContract]
int Method1() { return 0; }
// Applied to a method parameter
int Method2([ValidatedContract] string contract) { return 0; }
// Applied to a return value
[return: ValidatedContract]
int Method3() { return 0; }
Trying to use an attribute on a non-supported target is a compiler error, e.g.,
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct)]
public class FooAttribute : Attribute {}
public class Bar
{
[Foo] // Compiler error: Attribute 'Foo' is only valid on 'class, struct'...
public Baz() {}
}
AttributeUsageAttribute.Inherited
AttributeUsageAttribute.Inherited
defines how the attribute propagates to
classes derived from a base class to which the attribute is applied.
// Defaults to Inherited = true
public class FooAttribute : Attribute {}
[AttributeUsage(AttributeTargets.Method, Inherited = false)]
public class BarAttribute : Attribute {}
public class Base
{
[Foo]
[Bar]
public virtual void Qux() {}
}
public class Derived
{
// Qux will have the Foo attribute, but not the Bar attribute.
public override void Qux() {}
}
AttributeUsageAttribute.AllowMultiple
In a similar vein, AttributeUsageAttribute.AllowMultiple
indicates whether
multiple instances of the attribute can exist on an element. It defaults to
false
.
public class Foo
{
// Compiler error if DeveloperAttribute set AllowMultiple = false.
[Developer("Chege", "1")]
[Developer("Atieno", "1")]
public void Bar() {}
}
If both AllowMultiple
is set to false
and Inherited
is set to true
(the
default behavior in both cases), then values of any attributes in the parent
class will be overwritten by new instances of the same attribute in the child
class.
Sample Client of an Attribute
using System;
using System.Reflection;
[Developer("Joan Smith", "1")]
public class Foo
{
[Developer("Joan Smith", "1", Reviewed = true)]
static void Bar() {}
}
class Program
{
static void Main()
{
// DeveloperAttribute for Foo: Name=Joan Smith, Level=1, Reviewed=False
GetAttribute(typeof(Foo));
}
public static void GetAttribute(Type t)
{
DeveloperAttribute attr =
(DeveloperAttribute) Attribute.GetCustomAttribute(t, typeof (DeveloperAttribute));
if (attr is null)
{
Console.WriteLine("DeveloperAttribute not found.");
}
else
{
Console.WriteLine(
"DeveloperAttribute for {0}: Name={1}, Level={2}, Reviewed={3}",
t, attr.Name, attr.Level, attr.Reviewed);
}
}
}
To retrieve all instances of the same attribute applied to the same scope, use
Attribute.GetCustomAttributes
instead of Attribute.GetCustomAttribute
. To
retrieve attribute instances across different scopes, e.g., for all methods in
class, you’d need to supply every scope, e.g.,
public static void PrintMethodAttributes(Type t)
{
MemberInfo[] MyMemberInfo = t.GetMethods();
for (int i = 0; i < MyMemberInfo.length; i++) {
DeveloperAttribute attr =
(DeveloperAttribute) Attribute.GetCustomAttribute(
MyMemberInfo[i], typeof (DeveloperAttribute));
// Print the attribute information to the console.
}
}
… where methods like Type.GetMethods
, Type.GetProperties
, and
Type.GetConstructors
come in handy.
Example of reflection in C#: All types descended from the
System.Object
base class (the root of the type hierarchy in .NET) inherit theGetType()
method.Another example, obtaining the full name of the loaded assembly: