Basic Concept of Attribute
Friends often ask, what is Attribute? What is it for? The program seems to run fine without it. In fact, in .NET, Attribute is a very important component. To help everyone understand and master Attribute and how to use it, I’ve specially collected a few examples of Attribute usage for your reference.
Before the concrete demo, I’d like to give a rough introduction to Attribute. We know that among a class’s members there is a property member, and both are translated as “attribute” in Chinese. So are they the same thing? From the code, clearly not — first, their positions in the code differ, and second, the writing differs (Attribute must be written within a pair of square brackets).
What Is Attribute
First, we can be sure Attribute is a class. Here is the description from the MSDN documentation: The common language runtime lets you associate descriptive declarations, called attributes, with program elements such as types, fields, methods, and properties. Attributes are saved with the metadata of your .NET Framework file and can be used to describe your code to the runtime or to affect the behavior of your application at run time.
In .NET, Attribute is used to handle many problems, such as serialization, the security characteristics of a program, and preventing the JIT compiler from optimizing the program code so it’s easier to debug, and so on. Below, let’s first look at a few standard attribute usages in .NET, and then come back later to discuss the Attribute class itself. (The code in the article is written in C#, but applies equally to all .NET-based languages)
Attribute as a Compiler Directive
In C# there are a certain number of compiler directives, such as: #define DEBUG, #undefine DEBUG, #if, etc. These directives are specific to C#, and their number is fixed. Attribute used as a compiler directive, on the other hand, is unlimited in number. For example, the following three Attributes:
- Conditional: acts as conditional compilation — only when the condition is met does the compiler compile its code. Generally used when debugging a program.
- DllImport: used to mark a non-.NET function, indicating that the method is defined in an external DLL.
- Obsolete: this attribute is used to mark that the current method is deprecated and no longer used.
The following code demonstrates the use of the three attributes above:
#define DEBUG // define the condition here
using System;
using System.Runtime.InteropServices;
using System.Diagnostics;
namespace AttributeDemo
{
class MainProgramClass
{
[DllImport("User32.dll")]
public static extern int MessageBox(int hParent, string Message, string Caption, int Type);
static void Main(string[] args)
{
DisplayRunningMessage();
DisplayDebugMessage();
MessageBox(0,"Hello","Message",0);
Console.ReadLine();
}
[Conditional("DEBUG")]
private static void DisplayRunningMessage()
{
Console.WriteLine("开始运行Main子程序。当前时间是"+DateTime.Now);
}
[Conditional("DEBUG")]
[Obsolete]
private static void DisplayDebugMessage()
{
Console.WriteLine("开始Main子程序");
}
}
}
If you declare an Attribute before a program element, that means the Attribute is applied to that element. In the code above, [DllImport] is applied to the MessageBox function, [Conditional] is applied to the DisplayRunningMessage and DisplayDebugMessage methods, and [Obsolete] is applied to the DisplayDebugMessage method.
From the descriptions of the three Attributes above, we can guess the output produced when the program runs: the DllImport Attribute indicates that MessageBox is a function in User32.DLL, so we can call this function just like an internal method.
One important point is that Attribute is a class, so DllImport is also a class. The Attribute class is instantiated at compile time, not at run time like ordinary classes. When an Attribute is instantiated, depending on the design of its Attribute class, it can take parameters or not — for example, DllImport takes the parameter “User32.dll”. Conditional compiles the code that satisfies the parameter’s definition condition; if DEBUG is not defined, that method won’t be compiled. Readers can comment out the #define DEBUG line to see the output (the release build; in the Debug build, Conditional’s debug is always true). Obsolete indicates that the DisplayDebugMessage method is obsolete and there is a better method to replace it. When our program calls a method declared Obsolete, the compiler will give a message. Obsolete has two other overloaded versions; you can refer to the MSDN description of the ObsoleteAttribute class.
The Attribute Class
Besides the Attribute-derived classes provided by .NET, we can define our own Attribute; all custom Attributes must derive from the Attribute class. Now let’s look at the details of the Attribute class:
protected Attribute(): protected constructor, can only be called by Attribute’s derived classes.
Three static methods:
static Attribute GetCustomAttribute(): this method has 8 overloaded versions; it is used to retrieve the Attribute of a specified type applied to a class member.
static Attribute[] GetCustomAttributes(): this method has 16 overloaded versions, used to retrieve the array of Attributes of a specified type applied to a class member.
static bool IsDefined(): has eight overloaded versions; checks whether a custom attribute of the specified type is applied to a class member.
Instance methods:
bool IsDefaultAttribute(): returns true if the Attribute’s value is the default value.
bool Match(): indicates whether this Attribute instance equals a specified object.
Public property: TypeId: gets a unique identifier, used to distinguish different instances of the same Attribute.
We’ve briefly introduced the methods and properties of the Attribute class; some are inherited from object and are not listed here.
Below is how to define a custom Attribute: defining a custom Attribute doesn’t require special knowledge — it’s almost like writing a class. Your custom Attribute must derive directly or indirectly from the Attribute class, e.g.:
public MyCustomAttribute : Attribute { … }
Here it should be pointed out that the naming convention for Attributes is your Attribute’s class name + “Attribute”. When your Attribute is applied to a program element, the compiler first looks for your Attribute’s definition; if it’s not found, it looks for the definition of “Attribute name” + Attribute. If neither is found, the compiler reports an error.
For a custom Attribute, you can use the AttributeUsage attribute to limit the type of element your Attribute can be applied to. The code form is: [AttributeUsage(parameter settings)] public custom Attribute : Attribute { … }
What’s interesting is that AttributeUsage itself is also an Attribute — an Attribute specifically applied to Attribute classes. AttributeUsage also derives from Attribute, and it has a parameterized constructor whose parameter is the AttributeTargets enumeration. Below is the definition of AttributeTargets:
public enum AttributeTargets
{
All=16383,
Assembly=1,
Module=2,
Class=4,
Struct=8,
Enum=16,
Constructor=32,
Method=64,
Property=128,
Field=256,
Event=512,
Interface=1024,
Parameter=2048,
Delegate=4096,
ReturnValue=8192
}
The value of the AttributeTargets parameter as an argument allows combining multiple values via the “or” operation. If you don’t specify a parameter, the default parameter is All. Besides inheriting the methods and properties of Attribute, AttributeUsage also defines the following three properties:
AllowMultiple: reads or sets this property, indicating whether multiple Attributes can be applied to a program element.
Inherited: reads or sets this property, indicating whether the applied Attribute can be inherited or overridden by derived classes.
ValidOn: reads or sets this property, specifying the type of element the Attribute can be applied to.
Example of Using AttributeUsage:
using System;
namespace AttTargsCS
{
// This Attribute is valid only for classes.
[AttributeUsage(AttributeTargets.Class)]
public class ClassTargetAttribute : Attribute
{
}
// This Attribute is valid only for methods.
[AttributeUsage(AttributeTargets.Method)]
public class MethodTargetAttribute : Attribute
{
}
// This Attribute is valid only for constructors.
[AttributeUsage(AttributeTargets.Constructor)]
public class ConstructorTargetAttribute : Attribute
{
}
// This Attribute is valid only for fields.
[AttributeUsage(AttributeTargets.Field)]
public class FieldTargetAttribute : Attribute
{
}
// This Attribute is valid for classes or methods (combined).
[AttributeUsage(AttributeTargets.Class|AttributeTargets.Method)]
public class ClassMethodTargetAttribute : Attribute
{
}
// This Attribute is valid for all elements.
[AttributeUsage(AttributeTargets.All)]
public class AllTargetsAttribute : Attribute
{ }
// Usage of the Attributes defined above applied to program elements
[ClassTarget] // applied to class
[ClassMethodTarget]// applied to class
[AllTargets] // applied to class
public class TestClassAttribute
{
[ConstructorTarget] // applied to constructor
[AllTargets] // applied to constructor
TestClassAttribute()
{
}
[MethodTarget] // applied to method
[ClassMethodTarget] // applied to method
[AllTargets] // applied to method
public void Method1()
{
}
[FieldTarget] // applied to field
[AllTargets] // applied to field
public int myInt;
static void Main(string[] args)
{
}
}
}
So far we’ve introduced the Attribute class and their code format. You must be wondering how to actually use Attribute in your application. If the content above alone isn’t enough to show Attribute’s practical value, then from the next chapter we’ll introduce several different uses of Attribute, and I believe you’ll gain a new understanding of Attribute. (To be continued)
