Wednesday, 7 November 2012

Using Custom Attributes in C#

Introduction
Attributes give you the ability to store additional information with your application or assembly metadata, which can be queried at runtime by using the .NET Frameworks Reflection capabilities. For example, attributes could be used to indicate whether a class is serializable, or which field in a database a particular property should be written to. In this article we will create a simple custom attribute, and demonstrate how easy it is to retrieve this information at run-time.
Creating our Custom Attribute class
To create our custom attribute class, we will descend from the System.Attribute class as follows :-
public class DescriptionAttribute: Attribute {
 private string description;

 public string Description {get {return description;}}

 public DescriptionAttribute(string description) {
  this.description = description;
 }
}
There is nothing too complicated about our attribute class. It simply allows for a description string to be associated with it (passed into the constructor), and exposes this as a property which can be queried at runtime.
Using our Custom Attribute
We will start off by creating a test class, and use our attribute to associate a description with this class. To do this, we use the following syntax :-
[DescriptionAttribute("This is our test class")]
class AttributeClass {}
or
[Description("This is our test class")]
class AttributeClass {}

Both methods are identical in functionality, and are both valid syntax. The compiler will initially attempt to locate an attribute using the specified class name. If it is unsuccessful, the compiler will append 'Attribute' to the end of the class name and search again. As it is common practice to declare attribute classes using the Attribute suffix but reference them without it, this is the approach I will be taking for the rest of this article.
Querying for our Custom Attribute
Once we have associated our attribute with various source code elements, we can query the metadata of these elements at run-time by using the .NET Framework Reflection classes. Reflection can be used to gain information about every aspect of our class or assembly, including the class or assembly itself.
First we need to extract the metadata for our class, which we do by using the typeof function :-
Type type = typeof(AttributeClass);
The custom attribute information is retrieved into an object array by calling the Type.GetCustomAttributes() function. The method signatures for both overloaded versions are as follows :-
public object[] GetCustomAttributes(
 bool inherit  //Search the members inheritance heirarchy
);
public object[] GetCustomAttributes(
 Type attributeType, //Type of attribute to search for.
 bool inherit  //Search the members inheritance heirarchy
);
We can now retrieve the attribute information for our class, and loop through this array to obtain information about each attribute:-
object[] attributes = type.GetCustomAttributes(true);
foreach (object attribute in attributes) {
 Console.Write("  {0}", attribute.ToString());
 DescriptionAttribute da = attribute as DescriptionAttribute;
 if (da != null)
  Console.WriteLine(".Description={0}", da.Description);
 else
  Console.WriteLine();
}
We are using the as operator to typecast from System.Object to our custom attribute class, so we can access our Description property. Using this technique, if the typecast is invalid our DescriptionAttribute reference will be set to null. If we had used an explicit type cast, for example :-
DescriptionAttribute da = (DescriptionAttribute)attribute;
and the cast was invalid, a System.InvalidCastException exception would be thrown at run-time.
Our output should look something like this :-
Console Output
Attributes can also be associated with class members and properties in the same way as we did for the class. Lets add some more elements to our class and annotate them with our attribute:-
[Description("Here is a public field")]
public string PublicField;

[Description("Here is a private field")]
private string privateField;

[Description("Here is a protected field")]
protected string protectedField;

[Description("Here is a static field")]
static private string staticField;

[Description("Here is a public property")]
public string PublicProp {get {return privateField;}}
Extracting the attribute information for all members of a class is slightly more involved than our previous example. We need get details of each member in the class, and then iterate through the member's attributes :-
MemberInfo[] members = classType.GetMembers();
foreach (MemberInfo member in members)
 ParseAttributes(member);
As the logic to parse the attributes for a class and its members is identical, lets move it to a method so we don't need to duplicate the code. All the type information classes we have used descend from the MemberInfo class, so we will use that in our method's signature :-
static void ParseAttributes(MemberInfo type) {
 object[] attributes = type.GetCustomAttributes(true);
 Console.WriteLine("{0} {1} attributes", type.MemberType.ToString(), type.Name);

 if (attributes.Length != 0) {
  foreach (object attribute in attributes) {
   Console.Write("  {0}", attribute.ToString());
   DescriptionAttribute da = attribute as DescriptionAttribute;
   if (da != null)
    Console.WriteLine(".Description = {0}", da.Description);
   else
    Console.WriteLine();
  }
 }
 else
  Console.WriteLine("  {0} has no attributes", type.Name);
}
It is almost identical to our original code, but now we also check to see whether the member has any attributes, and report if it doesn't. We also use
type.MemberType.ToString()
to extract the type of member we are currently parsing. The output should be as follows :-
Console Output
But where is the information for privateField, staticField and protectedField? When we made the call to classType.GetMembers() we used one of two possible overloaded signatures for that method, and the one we chose returns public members only. The alternative signature looks like this :-
public MemberInfo[] GetMembers(BindingFlags bindingAttr);
The bindingAttr parameter is a bitmask of one or more BindingFlags enumeration members, to indicate which members we are interested in. For a detailed list of all possible values, consult the .NET Framework Help.
The output also showed members declared in the System.Object class, which gives more us information than we desire. We will change our call so we can see the missing members, and suppress the ones in System.Object :-
MemberInfo[] members = classType.GetMembers(
 BindingFlags.Public |      //Get public members
 BindingFlags.NonPublic |   //Get private/protected/internal members
 BindingFlags.Static |      //Get static members
 BindingFlags.Instance |    //Get instance members
 BindingFlags.DeclaredOnly);//Get only members declared in classType
Our output now looks like this :-
Console Output
You will notice that type information for the class constructor, and the getter method for PublicProp were also parsed. Technically this is correct, as they do meet the rules governed by the BindingFlags combination we specified. Although they aren't members we have explicitly created in our class definition, they were created by the C# compiler. It is possible to associate more than one attribute with a member. For example :-
[SomeOther]
[Description("Here is a public field")]
public string PublicField;
You notice from the above method that, unlike methods or class constructors, if an attribute's constructor takes no parameters it is not necessary to specify the () brackets, although it is valid to do so.
Positional vs Named Parameters
Attributes can have both positional and named parameters. Positional parameters are any parameters that were specified in the attribute's constructor. Named parameters are any public members of the attribute class, and should be initialized in the attribute's constructor. To specify a named parameter when using an attribute, use the following syntax :-
[Attribute(ParamName = paramValue)]
When using positional parameters in conjunction with named parameters, any positional parameters must be specified first, and in the same order as they were declared in the constructor. Named parameters can be specified in any order, providing they follow the positional parameters. We will now change our DescriptionAttribute and AttributeClass classes to demonstrate using named parameters and positional parameters. :-
public class DescriptionAttribute: Attribute {
 private string description;
 public string Description {get {return description;}}

 //**ADDED**
 private string extraInfo;
 public string ExtraInfo {get {return extraInfo;} set {extraInfo = value;}}

 public DescriptionAttribute(string description) {
  this.description = description;
  this.extraInfo = "";
 }
}

[Description("This is our test class")]
public class AttributeClass {
 [Description("Here is a public field")]
 public string PublicField;

 [Description("Here is a private field")]
 private string privateField;

 [Description("Here is a protected field")]
 protected string protectedField;

 //**CHANGED**
 [Description("Here is a static field", ExtraInfo = "It is also private")]
 static private string staticField;

 [Description("Here is a public property")]
 public string PublicProp {get {return privateField;}}
}
We will also change our ParseAttributes method to show the value of ExtraInfo, if it has been set :-
..
DescriptionAttribute da = attribute as DescriptionAttribute;
if (da != null) {
 Console.WriteLine(".Description = {0}", da.Description);
 //**ADDED**
 if (da.ExtraInfo != "")
  Console.WriteLine("  {0}.ExtraInfo = {1}",
   attribute.ToString(), da.ExtraInfo);
}
..
Running the program will produce the following output :-

Console Output
Specifying how Attributes can be used
The context in which it is valid to use a custom attribute can be specified by associating the AttributeUsage attribute with the custom attribute class. It allows you to specify which members it is valid for the custom attribute to be used on, whether the attribute can be inherited in derived classes and overriding members, and whether the attribute can be associated with a member multiple times :-
[AttributeUsage(
   AttributeTargets validon, //Positional
   AllowMultiple=bool,       //Named
   Inherited=bool            //Named
)]
The validon positional parameter is a bitmask of one or more AttributeTargets enumerated members, and is used to specify which member elements an attribute can be used with. The AllowMultiple named parameter specifies whether the attribute can be specified multiple times against the same member. The Inherited named parameter specifies whether the attribute should be inherited in descendant classes.
We will now associate another DescriptionAttribute attribute with our class declaration. As attributes by default have AllowMultiple set to false, we will need to use the AttributeUsage attribute to enable us to do this:-
[AttributeUsage(AttributeTargets.All, AllowMultiple = true)]
public class DescriptionAttribute: Attribute {
..
[Description("This is our test class")]
[Description("It demonstrates using the DescriptionAttribute attribute")]
public class AttributeClass {
Summary
Attributes are an extremely useful and flexible feature of the .NET Framework. They can be used to associate extra information with the metadata of your assemblies, classes, and class members. It is relatively straightforward to create custom attributes, associate them with source code elements, and query them at runtime.

No comments:

Post a Comment