Search Forum
(53671 Postings)
Search Site/Articles

Archived Articles
712 Articles

C# Books
C# Consultants
What Is C#?
Download Compiler
Code Archive
Archived Articles
Advertise
Contribute
C# Jobs
Beginners Tutorial
C# Contractors
C# Consulting
Links
C# Manual
Contact Us
Legal

GoDiagram for .NET from Northwoods Software www.nwoods.com


 
Printable Version

Custom Attributes in C#, Part I - Basics
By Gaurav Mantro

"C# is an imperative language, but like all imperativelanguages it does have some declarative elements. For example, the accessibility of a method in a class is specified by decorating it public,protected, internal, protected internal, or private. Through its support for attributes, C# generalizes this capability, so that programmers can invent new kinds of declarative information, attach this declarative information to various program entities, and retrieve this declarative information at run-time. Programs specify this additional declarative information by defining and using attributes." - C# Language Specification

The mechanism to invent new attributes is also referred as ‘Custom Attributes’. We can use custom attributes to associate information with C# classes.

Example of Pre-defined Attribute usage in C#

We will look at usage of ‘Attribute’ first to understand functionality that can be achieved, which hopefully should make it easier to understand concept of Attribute. Then later on we will work on a custom attribute and use it in our code. One of the most popular uses of attributes is implementation of Serialization support in .net. To indicate that a given class supports serialization we use the attribute Serialization, code listing 1.0 is an example of a serializable class. Serializable attribute indicates that MySerialableClass is serializable class, which supports the default serialization functionality provided by .net framework. To indicate that a particular attribute should not be serialized in a class we use the NonSerialized attribute

[Serializable()]
public class MySerialableClass
{
 public string strSerializeMe;
 public intSerializeMe;
 [NonSerialized()] public string strLeaveMeAlone;
}
Code List 1.0

Code-listing 1.0 indicates that serialization of MySerializableClasswill include the members strSerializableMe,intSerializeMe but strLeaveMeAlonewill not be serialized. Although we can also implement ISerializable to have more control over serialization process but we still need to have Serializable attribute in our class. Till now we looked at an example of attribute usage, lets try to dig deeper into how attributes work in C#.

Attribute Class

Attribute class is part of .net framework and hence is available in all languages supported by .net including C#, VB.net and VB.net. Attribute is an abstract base class and any attribute class (custom or .net provided) is directly or indirectly a subclass. To understand how Attributes work, let us consider code-listing 1.0 again. [Serializable()] creates a new instance of SerializableAttribute class and associates it with MySerializableClass, similarly [NonSerialized()] creates new instance of NonSerializedAttribute class and associates it with strLeaveMeAlone. Now when we try to serialize MySerializabeClass the runtime .net environment checks for the presence of SerializableAttribute and if not present throws an exception. Further while trying to serialize information of the instance if it finds NonSerializedAttribute associated with a member it skips serialization of that member. Attribute is an instance of class (derived from System.Attribute) that gets embodied into class. It can be applied to assembly, class, constructor, delegate, enum, event, field, interface, method, module, parameter, property, return value and struct. We will cover this topic again when we talk about custom attributes. We can apply different instances of an attribute to different target elements, which can even belong to same class; we can also apply different instances of an attribute to same target element. Information stored via virtue of attributes can be accessed at runtime; we will see an example of the same in our custom attribute section below.

Custom Attribute

So far we saw an example of pre-defined attributes that are provided by .net framework and read about how they are used. .net allows the use of custom attributes. Custom Attributes can be used to store information that can be stored at compile time and retrieved at run time. This information can be related to any aspect/feature of target element depending upon the design of application. We will develop a custom attribute ClassInfoAttribute, which will be used for storing information, related to source code. CodeInfoAttribute will have the following information.
  • DeveloperName
  • ReviewerName
  • ReviewDate

We are keeping the attribute light for easier concept understanding purpose only, please feel free to add as much as information you want. There are four sections of any custom attribute AttributeUsage, Class declaration, Constructor and Properties.

AttributeUsage

First section of custom attribute declaration is AttributeUsage. Attribute usage is defined by System.AttributeUsageAttribute, which is another attribute. As AttributeUsage is an attribute we use it in the same way we used the Serialization attribute in our discussion earlier. AttributeUsage has three members; AttributeTarget, Inherited and AllowMultiple. We need to provide values for all these members in [AttributeUsage()]

AttributeTarget

AttributeTarget specifies the target elements to which custom attribute can be applied. We can declare our attribute to be applied to all possible targets, one only or a combination of targets. Valid values of AttributeTarget are shown in table below.

Name Target Element
All Any application element.
Assembly Attribute can be applied to an assembly.
Class Attribute can be applied to a class.
Constructor Attribute can be applied to a constructor.
Delegate Attribute can be applied to a delegate.
Enum Attribute can be applied to an enumeration.
Event Attribute can be applied to an event.
Field Attribute can be applied to a field.
Interface Attribute can be applied to an interface.
Method Attribute can be applied to a method.
Module Attribute can be applied to a module.
Parameter Attribute can be applied to a parameter.
Property Attribute can be applied to a property.
ReturnValue Attribute can be applied to a return value.
Struct Attribute can be applied to a structure; that is, a value type.

Syntax for using AttributeTarget is [AttributeUsage(AttributeTarget.XXX | AttributeTarget.XXX)] where XXX denotes a valid value from the above table. '|' is used for targeting more then one target elements. Lets consider few examples to make this more clear.

AttributeTarget Description
[AttributeUsage(AttributeTarget.All)] This attribute can be applied to all target elements.
[AttributeUsage(AttributeTarget.Class|
AttributeTarget.Constructor)]
This attribute can be applied to only class or constructor.
[AttributeUsage(AttributeTarget.Class)] This attribute can be applied to Class only.

Inherited

A 'true' value for property Inherited indicates that if attribute is applied to class 'A' then it also gets applied to any subclass. A value of false stops the attribute being applied to subclass. Following code sample will help us understand. MeInheritedAttribute has Inherited property set to true and MeStiffAttribute has false.

public class Base
{
   [MeInherited]
   [MeStiff]
   public virtual void MethodA()
   {
      ...
   }
}
public class Sub : Base
{
     
}

Now for an instance of Sub, MethodA will have attribute MeInherited applied but not MeStiff as Inherited property of AttributeUsage of MeStiffAttribute is set to false. For our CodeInfoAttribute we will set Inherited to be true.

AllowMultiple

A value of 'true' for AllowMultiple property indicates whether multiple instances of the attribute can exist on same target element, 'false' indicates otherwise. For our CodeInfoAtribute we will have this set to true as there can be a scenario where more then one programmer works on a given method or property and we want to capture all that information in our code.

From our knowledge about AttributeUsage so far lets define the AttributeUsage for CodeInfoAttribute class.

[AttributeUsage(AttributeTarget.All,Inherited=true,AllowMultiple=true)]

Class Declaration

Attribute class is declared as any other class in C#, but we need to make sure that following conditions are met with declaration of custom attribute class.

  • Attribute classes must be declared as public classes.
  • By convention, the name of the attribute class ends with the word Attribute.
  • While not required, this convention is recommended for readability. When the attribute is applied, the inclusion of the word Attribute is optional.
  • All attribute classes must inherit directly or indirectly from System.Attribute

We will make our custom attribute class a direct subclass of Attribute and also add the private members that we will need to store information. Lets look at class declaration our custom attribute class CodeInfoAttr

[AttributeUsage(AttributeTarget.All,Inherited=true,AllowMultiple=true)]
public class CodeInfoAttribute : Attribute
{
  //Private Data
   private string reviewerName;
   private string reviewDate;
   private string developerName;
}

Constructor

Just like any other class we need to provide a constructor for our custom attribute class. Constructors can have required and optional parameters the same way as in a regular class. For our attribute class we will like to have DeveloperName, ReviewerName and ReviewDate as mandatory information, below mentioned code demonstrates constructor for our class.

[AttributeUsage(AttributeTarget.All,Inherited=true,AllowMultiple=true)]
public class CodeInfoAttribute : Attribute
{
  // Private Data
  private string reviewerName;
  private string reviewDate;
  private string developerName;
 
  // Constructor
  public CodeInfoAttribute(string developerName,
                           string reviewerName,
                           string reviewDate)
  {
    this.developerName = developerName;
    this.reviewerName  = reviewerName;
    this.reviewDate    = reviewDate;
  }
}

Properties

When required Properties should be declared, for access and setting the information (as per design requirement) for the attribute class. In our code sample we will create properties to enable getting the information but will not allow modifying these values. Modifying these values is also achievable but we are not doing that as a choice. After defining these properties we have a custom attribute class ready for use.

[AttributeUsage(AttributeTarget.All,Inherited=true,AllowMultiple=true)]
public class CodeInfoAttribute : Attribute
{
  // Private Data
  private string reviewerName;
  private string reviewDate;
  private string developerName;
 
  // Constructor
  public CodeInfoAttribute(string developerName,
                           string reviewerName,
                           string reviewDate)
  {
    this.developer_name = developerName;
    this.reviewer_name  = reviewerName;
    this.review_date    = reviewDate;
  }
 
  public string Devloper
  {
    get
    {
      return developerName;
    }
  }
 
  public string Reviewer
  {
    get
    {
      return reviewerName;
    }
  }
 
  public string ReviewDate
  {
    get
    {
      return reviewDate;
    }
  }
}

Using our Custom Attribute class - CodeInfoAttribute

We have coded a custom attribute CodeInfoAttribute, let's look at code example of how we can retrieve the information stored in instance of custom attribute class using reflection.

using System;
 
namespace CustomCode
{
  [CodeInfo("Gaurav Mantro","John Doe","10/10/01")]
  class CustomAttrDemo
  {
    static void Main(string[] args)
    {
      CustomAttrDemo cattDemo = new CustomAttrDemo();
      DisplayCustomAttribute(cattDemo);
    }
 
    static void DisplayCustomAttribute(CustomAttrDemo cattDemo)
    {
      Type type = cattDemo.GetType();
      Object obj = type.GetCustomAttributes(false)[0];
      if(obj is CodeInfoAttribute)
      {
       System.Console.Write("Developer - ");
       System.Console.WriteLine(((CodeInfoAttribute)obj).Devloper);
       System.Console.Write("Reviewer - ");
       System.Console.WriteLine(((CodeInfoAttribute)obj).Reviewer);
       System.Console.Write("Review Date - ");
     System.Console.WriteLine(((CodeInfoAttribute)obj).ReviewDate);
      }
      else
       System.Console.WriteLine("Attribute not found");
    }
}
 
[AttributeUsage(AttributeTarget.All,Inherited=true,AllowMultiple=true)]
public class CodeInfoAttribute : Attribute
{
  // Private Data
  private string reviewerName;
  private string reviewDate;
  private string developerName;
 
  // Constructor
  public CodeInfoAttribute(string developerName,
                           string reviewerName,
                           string reviewDate)
  {
    this.developer_name = developerName;
    this.reviewer_name  = reviewerName;
    this.review_date    = reviewDate;
  }
 
  public string Devloper
  {
    get
    {
      return developerName;
    }
  }
 
  public string Reviewer
  {
    get
    {
      return reviewerName;
    }
  }
 
  public string ReviewDate
  {
    get
    {
      return reviewDate;
    }
  }
}

When we compile and execute the above-mentioned code we get the following output on console

D:\Gaurav\Projects_Learn\c#\CustomCode\bin\Debug>CustomCode
Developer    - Gaurav Mantro
Reviewer     - John Doe
Review Date  - 10/10/01

In the code example we just saw, we applied our custom attribute to class but we can apply it to other target elements also. We are not demonstrating those capabilities here but will encourage that you should try applying CodeInfoAttribute to other target elements also.