Why Attributes?
Attributes are elements that allow you to add declarative information to your programs. This declarative information is used for various purposes during runtime and can be used at design time by application development tools. For example, there are attributes such as DllImportAttribute that allow a program to communicate with the Win32 libraries. Another attribute, ObsoleteAttribute, causes a compile-time warning to appear, letting the developer know that a method should no longer be used. When building Windows forms applications, there are several attributes that allow visual components to be drag-n-dropped onto a visual form builder and have their information appear in the properties grid.Attributes are also used extensively in securing .NET assemblies, forcing calling code to be evaluated against pre-defined security constraints.
The reason attributes are necessary is because many of the services they provide would be very difficult to accomplish with normal code. You see, attributes add what is called metadata to your programs. When your C# program is compiled, it creates a file called an assembly, which is normally an executable or DLL library. Assemblies are self-describing because they have metadata written to them when they are compiled. Via a process known as reflection, a program's attributes can be retrieved from its assembly metadata. Attributes are classes that can be written in C# and used to decorate your code with declarative information. This is a very powerful concept because it means that you can extend your language by creating customized declarative syntax with attributes.
When do we need attributes ?
The advantage of using attributes resides in the fact that the information that it contains is inserted into the assembly. This information can then be consumed at various times for all sorts of purposes:
- An attribute can be consumed by the compiler. The System.ObsoleteAttribute attribute that we have just described is a good example of how an attribute is used by the compiler, certain standard attributes which are only destined for the compiler are not stored in the assembly. For example, the SerializationAttribute attribute does not directly mark a type but rather tells the compiler that type can be serialized. Consequently, the compiler sets certain flags on the concerned type which will be consumed by the CLR during execution such attributes are also named pseudo-attributes.
- An attribute can be consumed by the CLR during execution. For example the .NET Framework offers the System.ThreadStaticAttribute attribute. When a static field is marked with this attribute the CLR makes sure that during the execution, there is only one version of this field per thread.
- An attribute can be consumed by a debugger during execution. Hence, the System.Diagnostics.DebuggerDisplayAttribute attribute allows personalizing the display of an element of the code(the state of an object for example) during debugging.
- An attribute can be consumed by a tool, for example, the .NET framework offers the System.Runtime.InteropServices.ComVisibleAttribute attribute. When a class is marked with this attribute, the tlbexp.exe tool generates a file which will allow this class to be consumed as if it was a COM object.
- An attribute can be consumed by your own code during execution by using the reflection mechanism to access the information. For example, it can be interesting to use such attributes to validate the value of fields in your classes. Such a field must be within a certain range. Another reference field must not be null. A string field can be atmost 100 character. Because of the reflection mechanism, it is easy to write code to validate the state of any marked fields. A little later, we will show you such an example where you can consume attributes by your own code.
- An attribute can be consumed by a user which analyses an assembly with a tool such as ildasm.exe or Reflector. Hence you could imagine an attribute which would associate a character string to an element of your code. This string being contained in the assembly, it is then possible to consult these comments without needing to access source code.
Developing Custom Attributes
Derive our attribute class from System.Attribute class as stated in C# language specification . A class that derives from the abstract class System.Attribute, whether directly or indirectly, is an attribute class. The declaration of an attribute class defines a new kind of attribute that can be placed on a declaration.Note: it is a convention to use the word Attribute as a suffix in attribute class names. However, when we attach the attribute to a program entity, we are free not to include the Attribute suffix. The compiler first searches the attribute in
System.Attribute
derived classes. If no class is found, the compiler will add the word Attribute to the specified attribute name and search for it. Defining or Controlling Usage of Attribute
AttributeUsage class is another pre-defined class that will help us in controlling the usage of our custom attributes. That is, we can define attributes of our own attribute class. It describes how a custom attribute class can be used.
AttributeUsage
has three properties which we can set while placing it on our custom attribute. The first property is: ValidOn
Through this property, we can define the program entities on which our custom attribute can be placed. The set of all possible program entities on which an attribute can be placed is listed in theAttributeTargets
enumerator. We can combine several AttributeTargets
values using a bitwise OR operation. AllowMultiple
This property marks whether our custom attribute can be placed more than once on the same program entity.Inherited
We can control the inheritance rules of our attribute using this property. This property marks whether our attribute will be inherited by the class derived from the class on which we have placed it.Example:
using System;
[AttributeUsage(AttributeTargets.Class), AllowMultiple = false,
Inherited = false ]public class HelpAttribute : Attribute
{
public HelpAttribute(String Description_in)
{
this.description = Description_in;
}
protected String description;
public String Description
{
get
{
return this.description;
}
}
}[Help("this is a do-nothing class")]
public class AnyClass
{
}
Example for Querying Attributes at Run-Time
using System;
using System.Reflection;
using System.Diagnostics;
//attaching Help attribute to entire assembly[assembly : Help("This Assembly demonstrates custom attributes
creation and their run-time query.")]
//our custom attribute classpublic class HelpAttribute : Attribute
{
public HelpAttribute(String Description_in)
{
//
// TODO: Add constructor logic here this.description = Description_in;
//
}
protected String description;
public String Description
{
get
{
return this.deescription;
}
}
}
//attaching Help attribute to our AnyClass[HelpString("This is a do-nothing Class.")]
public class AnyClass
{
//attaching Help attribute to our AnyMethod [Help("This is a do-nothing Method.")]
public void AnyMethod()
{
}
//attaching Help attribute to our AnyInt Field [Help("This is any Integer.")]
public int AnyInt;
}class QueryApp
{
public static void Main()
{
HelpAttribute HelpAttr;
//Querying Assembly Attributes String assemblyName;
Process p = Process.GetCurrentProcess();
assemblyName = p.ProcessName + ".exe";
Assembly a = Assembly.LoadFrom(assemblyName);
foreach (Attribute attr in a.GetCustomAttributes(true))
{
HelpAttr = attr as HelpAttribute;
if (null != HelpAttr)
{
Console.WriteLine("Description of {0}:\n{1}",
assemblyName,HelpAttr.Description);
}
}
}
}
No comments:
Post a Comment