Attributes

Introduction to Attributes

C# provides a mechanism for defining declarative tags, called attributes, that you can place on certain entities in your source code to specify additional information. The information that attributes contain can be retrieved at run time through Reflection. You can use or you can define your own custom attributes.

The common language runtime (CLR) allows you to add keyword-like descriptive declarations (called attributes) to annotate programming elements such as types, fields, methods, and properties.

Attributes are used to make new attributes and also to decorate our code with some extra information. This information can be retrieved from those programming elements at run time by using Reflection. For example, we can use the System.ComponentModel.DescriptionAttribute to provide some information about a class.

How Attributes Are Written

Attribute classes implement attributes. Attributes are written in square brackets and written just before the element to which they are applied. The following example shows two of the most commonly used attributes:

Example

#define x

using System;
using System.Diagnostics;

namespace attr
{
   class Class1
   {
      static void Main(string[] args)
      {
         test t = new test( ) ;
         t.func1 ( 10 ) ;
      }
   }
   public class test
   {
      [Obsolete]
      int i;

      [Conditional ("x") ]
      public void func1( int k )
      {
         i = k ;
         Console.WriteLine(" func1 {0} ",i) ;
      }
   }
}

Output

func1 10.
And the compiler flashes two errors:
'attr.test.i' is obsolete
'attr.test.i' is obsolete

Explanation

We used two attributes: obsolete and conditional. Obsolete marks the attribute obsolete and flashes a warning when that element is used. So, when we use i in the function, we get two warnings. Next, we have used conditional; this means that if the specified identifier is not #defined, it cannot be accessed. Here we have #defined x; hence, we can call the function func1. If we remove the #define or #undef, the Function is not called at all.

There is one restriction of conditional attributes on methods; they can be used only with those methods that return void.

Using Attributes

Attributes can be placed on most any declaration. Syntactically, an attribute is specified by placing the name of the attribute, enclosed in square brackets, in front of the declaration of the entity to which it applies. For example, a class with the attribute:

[DllImport]
public class MyDllimportClass { ... }

Many attributes have parameters, which can be either positional (unnamed) or named. Any unnamed parameters must be specified in a certain order and cannot be omitted; named parameters are optional and can be specified in any order. Positional parameters are specified first. For example, these three attributes are equivalent:

[DllImport("user32.dll", ExactSpelling=false, SetLastError=false)]

The first parameter, the DLL name, is positional and always comes first. The others are named.

Positional vs. Named Parameters

Positional parameters are parameters of the constructor of the attribute. They are mandatory and a value must be passed every time the attribute is placed on any program entity. On the other hand, Named parameters are actually optional and are not parameters of the attribute’s constructor.

Global Attributes

Some attributes are restricted to a certain language, such as its classes or methods, but other attributes are declared globally and can be used in other assemblies or modules.

Why Use Attributes?

At first sight, the usage of attributes seems to be limited. Why would you provide information about the code you write, which can only be used at run time? By using custom attributes, you can make more generic functionality. The goal is to build a set of attributes that allows developers to easily add validation, with a minimum of code:

[Validators.NotEmptyStringValidator ("Name cannot be empty.")]
[Validators.LengthValidator ("Max length of name is 30.",
                             MaxLength:=30)]

Public Property Name() As String
   Get
      Return _name
   End Get
   Set (ByVal Value As String)
   name = Value
   End Set
End Property

As you can see, two attributes are attached to the Name property. They will enforce two validation rules for that property: the name cannot be empty and the maximum length is 30 characters. These attributes will be the only thing you need to add to your class properties to have validation!

Attribute Class

The attribute class is a part of the .NET framework; hence, it is available in all languages supported by .NET, including C# and VB.NET. An attribute is an abstract base class and any attribute class (custom or .NET provided) is directly or indirectly a subclass. 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 also can apply different instances of an attribute to the same target element. Information stored via virtue of attributes can be accessed at run time.

Definition of Custom Attributes

You can create your own custom attributes by defining an attribute class, a class that derives directly or indirectly from System.Attribute. The mechanism to invent new attributes is also referred as “Custom Attributes.” We can use custom attributes to associate information with C# classes. 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 of target element, depending upon the design of the application.

Custom Attributes with .NET (for Example, C#)

“C# is an imperative language, but like all imperative languages, 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.”

There are four sections of any custom attribute: AttributeUsage, Class declaration, Constructor, and Properties.

AttributeUsage

The first section of custom attribute declaration is AttributeUsage. Attribute usage is defined by System.AttributeUsageAttribute, which is another attribute. Because 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
  • AllowMultiple

We need to provide values for all these members in [AttributeUsage()].

AttributeTarget

AttributeTarget specifies the target elements to which a custom attribute can be applied. In other words, AttributeTarget is the access level of the attribute.

  • 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.

The 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. Let’s consider few examples to make this clearer.

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

If the inherited property of the custom is set to “True,” it will make our attribute accessible to any other subclass where it is inherited.

public class MyAttribute :Attribute
{
}

[AttributeUsage( Inherited = false)]
public class YourAttribute : Attribute
{
}

Here, the inherited property is set to “False,” which means the attribute can’t be inherited in the YourAttribute class.

AllowMultiple

A value of ‘true’ for AllowMultiple property indicates whether multiple instances of the attribute can exist on same target element; ‘false’ indicates otherwise.

Class Declaration

The Attribute class is declared just as any other class, but some conditions must be kept in mind while doing this.

  • Attributed classes must be declared as public.
  • The name of the attribute must end with the keyword such as “Attribute.”
  • The Attribute class must directly or indirectly inherit from a System.Attribute class.

Constructor

Constructors in the custom attribute class are declared just as they are declared in any other class. They have optional and required parameters. We also can overload our constructors with positional or named attributes when initializing them.

Properties

Required Properties should be declared for access and setting the information (as per design requirement) for the attribute class.

Example of Custom Attribute

using System;

namespace CustomAttributes
{

   [AttributeUsage(AttributeTargets.All,AllowMultiple=true,
                   Inherit= false)]
   public class HelpAttribute : Attribute
   {
      protected string version;
      protected string description;
      public HelpAttribute(string Description_in,string vvv)
      {
         this.description = Description_in;
         this.version = vvv;
      }

      public string Description
      {
         get
         {
            return this.description;
         }
         set
         {
            this.description = value;
         }
      }

      public string Version
      {
         get
         {
            return this.version;
         }
         set
         {
            this.version = value;
         }
      }
   }
}

Explanation

In class 1, we have made an attribute name “HelpAttribute”, with target Attribute set to all, allow multiple property set to true, and inherited property set to “false”, meaning the attribute can be accessed at all levels and multiple usage of the attribute is forbidden and the attribute can’t be inherited. We made a class inherited by the attribute class, which is a must and declared two variables of a string type (version, description). We made a constructor and gave it two string types (Description_in, vvv). We assigned the variables to each other. Then made two properties (did “get” and “set” in it).

In class2, first we tell the code which attribute we are using by “[HelpAttribute(“syma”,”510″)]” and assigning two values to it. The first value is the description and the second one is the version to which we assigned values in the first class. Then, we made an object of this class and the object of a “type” type and assigned it to the object we made of the class. Then, we declared another object of the Object type and got the attributes from our previous class. After that, we just called the attribute and printed it.

****************CLASS2*****************
using System;

namespace CustomAttributes
{
   /// <summary>
   /// Summary description for Class3.
   /// </summary>
   [HelpAttribute("syma","510")]
   public class Class3
   {
      static void Main(string[] args)
      {
         Class3 cc= new Class3();
         Type hehe= cc.GetType();
         Object obj = hehe.GetCustomAttributes(false)[0];
         if (obj is HelpAttribute)
         {
            Console.WriteLine (((HelpAttribute)obj).Description);
            Console.WriteLine (((HelpAttribute)obj).Version);

            //setting the property of HelpAttribute
            ((HelpAttribute)obj).Description="xulaikha";
            ((HelpAttribute)obj).Version="626";

            Console.WriteLine (((HelpAttribute)obj).Description);
            Console.WriteLine (((HelpAttribute)obj).Version);
         }
         else
         {
            Console.WriteLine ("help me ");
         }

      }
   }
}

Some Ambiguities Involved

Suppose that sometimes we want to place this attribute on an entire attribute. The thing that comes to mind is where should we place the attribute so that the complier will know that it is placed at the assembly level? To resolve this, we use identifiers.

[assembly: Help("this a do-nothing assembly")]

The assembly identifier before the Help attribute tells the compiler that this attribute is attached to the entire assembly. The possible identifiers are:

  • Assembly
  • Module
  • Type
  • Method
  • Property
  • Event
  • Field
  • Param

Reflection

To make any transactions on a program that can be any thing done at run time, we use “Reflection.” Reflection is the ability to discover type information at run time. We can use the .NET Framework Reflection to get the metadata for an entire assembly and produce a list of all classes, types, and methods that have been defined for that assembly.

Retrieving Information About the Attribute

If we are defining custom attributes and using them in a source code, they could be of no value unless we are using them for the retrieval. This is done by the method of “reflection” (which is used to retrieve metadata from the assemblies).

More by Author

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Must Read