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