Attribute-Based Programming

It is often desirable to expose certain aspects of the code such as architectural constraints, behaviors, features, and so forth, in a nonprocedural way. You are already familiar with C++ and C# keywords such as public and private. These keywords further define the behavior of the class members by describing their accessibility to other classes. .NET lets you define additional aspects of a programming element, such as types and fields, by means of annotating the entity with attributes. The following code excerpt illustrates this idea:

// Project Attributes

[ObsoleteAttribute("Please don't use this method")]
public static int Add(int x, int y) {
  return x + y;
}

public static void Main() {
  int z = Add(10,20);
}

In this code, method Add is marked with an attribute named ObsoleteAttribute. If any other part of the code tries to call this method, the compiler generates a warning such as follows (note that this is just a warning, and not error, so the code still works):

main.cs(16,11): warning CS0618: 'MyApp.Add(int, int)'
    is obsolete: 'Please don't use this method'

The C# compiler has hard-coded logic to specifically deal with some attributes such as ObsoleteAttribute. In general, attributes are meant to provide meaningful information to whoever is interested in such information. For example, consider the following declaration for a class:

[SerializableAttribute]
class Foo {
     ...
}

Annotating a class with [SerializableAttribute] informs the run-time that it is okay to serialize the instances of this class. We will look at the serialization process in a later chapter.

To make attribute information available to the runtime (or any other interested party), attributes are stored in the type metadata when the assembly is built. Contrast this to source code comments that get stripped off in the compiled output.

Note that many .NET compilers, including the one for C#, allow you to drop the Attribute suffix for convenience. For example, [SerializableAttribute] can also be represented as [Serializable]. The compiler takes care of expanding it to the proper form while saving the information in the metadata.

Custom Attributes

The beauty of attributes is that they provide a mechanism to extend the metadata. Extra information can be added to the code either by using the standard attributes defined by the .NET Framework or by creating your own custom attributes. The kind of information that you can add (and inspect later) is limited only by your imagination.

Under .NET, an attribute is implemented as a class. The .NET Framework requires that the attribute class be inherited from System.Attribute. Here is an example of a custom attribute, DeveloperAttribute, that stores the name of the developer working on a piece of the code.

// Project Attributes

[AttributeUsage(
     AttributeTargets.Assembly |
     AttributeTargets.Module |
     AttributeTargets.Delegate |
     AttributeTargets.Event |
     AttributeTargets.Enum |
     AttributeTargets.Interface |
     AttributeTargets.Struct |
     AttributeTargets.Class |
     AttributeTargets.Field |
     AttributeTargets.Property |
     AttributeTargets.Constructor |
     AttributeTargets.Method |
     AttributeTargets.Parameter |
     AttributeTargets.ReturnValue,
     AllowMultiple=true)]
public class DeveloperAttribute : System.Attribute {
     private String m_Name;
     public DeveloperAttribute(String name) {
       m_Name = name;
     }
}

.NET requires that an attribute class itself be annotated with an attribute called AttributeUsage. It takes a parameter of enumeration type AttributeTargets to indicate the element of the code the attribute can be applied on. As can be seen in the preceding example, the enumeration list for AttributeTargets is quite extensive; it includes classes, structs, interfaces, methods, method parameters, and so on.

Attribute That Is Valid on Any Target

If your attribute is designed to work with any target, you can simply use an enumeration value AttributeTargets.All instead of listing each target individually.


The following example shows how DeveloperAttribute can be used to annotate various parts of the code:

[Developer("Jay")]
public class MyTest {

     [Developer("Pradeep")]
     public void MyMethod() {}
}

Class AttributeUsage also defines a boolean property AllowMultiple that can be used to control whether the attribute can be applied multiple times on the same code element. For our class DeveloperAttribute, this value is set to true, indicating that multiple developers can be assigned to the same part of the code, as illustrated here:

[Developer("Jay")]
[Developer("Pradeep")]
public class MyTest {
     ...
}

Besides the AllowMultiple property, AttributeUsage defines some other properties. Check the SDK documentation for more information.

It is also possible to define properties for custom attributes. The following code excerpt defines a property Team (to indicate the team the developer belongs to) on the class DeveloperAttribute.

public enum CompanyTeam {Development, QA, Support, Unknown};

...
public class DeveloperAttribute : System.Attribute {
     ...
     private CompanyTeam m_Team = CompanyTeam.Unknown;
     public CompanyTeam Team {
       get {return m_Team;}
       set {m_Team = value;}
     }
}

Using this property, developers can optionally specify the team they belong to, as illustrated here:

public class MyTest {
     [Developer("Pradeep")]
     public void MyMethod() {}

     [Developer("Jay", Team = CompanyTeam.Development)]
     public void AnotherMethod() {}
}

When the code is compiled, the attribute information is saved in the assembly. One way to inspect this information is to run the assembly through the IL Disassembler. A better way under .NET is to write a program using the Reflection APIs to obtain the needed information. We will see how to do this later in this chapter.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.222.155.58