Attributes


C# allows you to add declarative information to a program in the form of an attribute. An attribute defines additional information (metadata) that is associated with a class, structure, method, and so on. For example, you might define an attribute that determines the type of button that a class will display. Attributes are specified between square brackets, preceding the item to which they apply. Thus, an attribute is not a member of a class. Rather, an attribute specifies supplemental information that is attached to an item.

Attribute Basics

An attribute is supported by a class that inherits System.Attribute. Thus, all attribute classes must be subclasses of Attribute. Although Attribute defines substantial functionality, this functionality is not always needed when working with attributes. By convention, attribute classes often use the suffix Attribute. For example, ErrorAttribute would be a name for an attribute class that described an error.

When an attribute class is declared, it is preceded by an attribute called AttributeUsage. This built-in attribute specifies the types of items to which the attribute can be applied. Thus, the usage of an attribute can be restricted to methods, for example.

Creating an Attribute

In an attribute class, you will define the members that support the attribute. Often attribute classes are quite simple, containing just a small number of fields or properties. For example, an attribute might define a remark that describes the item to which the attribute is being attached. Such an attribute might look like this:

 [AttributeUsage(AttributeTargets.All)] public class RemarkAttribute : Attribute {   string pri_remark; // underlies remark property   public RemarkAttribute(string comment) {     pri_remark = comment;   }   public string remark {     get {       return pri_remark;     }   } }

Let’s look at this class, line by line.

The name of this attribute is RemarkAttribute. Its declaration is preceded by the AttributeUsage attribute, which specifies that RemarkAttribute can be applied to all types of items. Using AttributeUsage, it is possible to narrow the list of items to which an attribute can be attached, and we will examine its capabilities later in this chapter.

Next, RemarkAttribute is declared and it inherits Attribute. Inside RemarkAttribute there is one private field, pri_remark, which supports one public, read-only property: remark. This property holds the description that will be associated with the attribute. There is one public constructor that takes a string argument and assigns it to remark.

At this point, no other steps are needed, and RemarkAttribute is ready for use.

Attaching an Attribute

Once you have defined an attribute class, you can attach the attribute to an item. An attribute precedes the item to which it is attached and is specified by enclosing its constructor inside square brackets. For example, here is how RemarkAttribute can be associated with a class:

 [RemarkAttribute("This class uses an attribute.")] class UseAttrib {   // ... }

This constructs a RemarkAttribute that contains the comment “This class uses an attribute.” This attribute is then associated with UseAttrib.

When attaching an attribute, it is not actually necessary to specify the “Attribute” suffix. For example, the preceding class could be declared this way:

 [Remark("This class uses an attribute.")] class UseAttrib {   // ... }

Here, only the name Remark is used. Although the short form is correct, it is usually safer to use the full name when attaching attributes, because it avoids possible confusion and ambiguity.

Obtaining an Object’s Attributes

Once an attribute has been attached to an item, other parts of the program can retrieve the attribute. To retrieve an attribute, you will usually use one of two methods. The first is GetCustomAttributes( ), which is defined by MemberInfo and inherited by Type. It retrieves a list of all attributes attached to an item. Here is one of its forms:

 object[ ] GetCustomAttributes(bool searchBases)

If searchBases is true, then the attributes of all base classes through the inheritance chain will be included. Otherwise, only those classes defined by the specified type will be found.

The second method is GetCustomAttribute( ), which is defined by Attribute. One of its forms is shown here:

 static Attribute GetCustomAttribute(MemberInfo mi, Type attribtype)

Here, mi is a MemberInfo object that describes the item for which the attributes are being obtained. The attribute desired is specified by attribtype. You will use this method when you know the name of the attribute you want to obtain, which is often the case. For example, assuming that the UseAttrib class has the RemarkAttribute, to obtain a reference to the RemarkAttribute, you can use a sequence like this:

 // Get a MemberInfo instance associated with a // class that has the RemarkAttribute. Type t = typeof(UseAttrib); // Retrieve the RemarkAttribute. Type tRemAtt = typeof(RemarkAttribute); RemarkAttribute ra = (RemarkAttribute)       Attribute.GetCustomAttribute(t, tRemAtt);

This sequence works because MemberInfo is a base class of Type. Thus, t is a MemberInfo instance.

Once you have a reference to an attribute, you can access its members. Thus, information associated with an attribute is available to a program that uses an element to which an attribute is attached. For example, the following statement displays the remark field:

 Console.WriteLine(ra.remark);

The following program puts together all of the pieces and demonstrates the use of RemarkAttribute:

 // A simple attribute example. using System; using System.Reflection; [AttributeUsage(AttributeTargets.All)] public class RemarkAttribute : Attribute {   string pri_remark; // underlies remark property   public RemarkAttribute(string comment) {     pri_remark = comment;   }   public string remark {     get {       return pri_remark;     }   } } [RemarkAttribute("This class uses an attribute.")] class UseAttrib {   // ... } class AttribDemo {   public static void Main() {     Type t = typeof(UseAttrib);     Console.Write("Attributes in " + t.Name + ": ");     object[] attribs = t.GetCustomAttributes(false);     foreach(object o in attribs) {       Console.WriteLine(o);     }     Console.Write("Remark: ");     // Retrieve the RemarkAttribute.     Type tRemAtt = typeof(RemarkAttribute);     RemarkAttribute ra = (RemarkAttribute)           Attribute.GetCustomAttribute(t, tRemAtt);     Console.WriteLine(ra.remark);   } }

The output from the program is shown here:

 Attributes in UseAttrib: RemarkAttribute Remark: This class uses an attribute.

Positional vs. Named Parameters

In the preceding example, RemarkAttribute was initialized by passing the description string to the constructor, using the normal constructor syntax. In this case, the comment parameter to RemarkAttribute( ) is called a positional parameter. This term relates to the fact that the argument is linked to a parameter by its position in the argument list. This is the way that all methods and constructors work in C#. For example, given a method called test( ) declared as shown here,

 void test(int a, double b, string c)

the following call to test( )

 test(10, 1.1, "hello");

passes 10 to a, 1.1 to b, and “hello” to c because of the position (that is, order) of the arguments. However, for an attribute, you can also create named parameters, which can be assigned initial values by using their names. In this case, it is the name of the parameter, not its position, that is important.

A named parameter is supported by either a public field or property, which must be read-write and non-static. Any such field or property is automatically able to be used as a named parameter. A named parameter is given a value by an assignment statement that is located within the argument list when the attribute’s constructor is invoked. Here is the general form of an attribute specification that includes named parameters:

 [attrib(positional-param-list, named-param1 = value, named-param2 = value, ...)]

The positional parameters (if they exist) come first. Next, each named parameter is assigned a value. The order of the named parameters is not important. Named parameters do not need to be given a value. In this case, their default values will be used.

To understand how to use a named parameter, it is best to work through an example. Here is a version of RemarkAttribute that adds a field called supplement, which can be used to hold a supplemental remark:

 [AttributeUsage(AttributeTargets.All)] public class RemarkAttribute : Attribute {   string pri_remark; // underlies remark property   public string supplement; // this is a named parameter   public RemarkAttribute(string comment) {     pri_remark = comment;     supplement = "None";   }   public string remark {     get {       return pri_remark;     }   } }

As you can see, supplement is initialized to the string “None” by the constructor. There is no way of using the constructor to assign it a different initial value. However, supplement can be used as a named parameter, as shown here:

 [RemarkAttribute("This class uses an attribute.",         supplement = "This is additional info.")] class UseAttrib {   // ... }

Pay close attention to the way RemarkAttribute’s constructor is called. First, the positional argument is specified as it was before. Next, is a comma, followed by the named parameter, supplement, which is assigned a value. Finally, the closing ) ends the call to the constructor. Thus, the named parameter is initialized within the call to the constructor. This syntax can be generalized. Positional parameters must be specified in the order in which they appear. Named parameters are specified by assigning values to their names.

Here is a program that demonstrates the supplement field:

 // Use a named attribute parameter. using System; using System.Reflection; [AttributeUsage(AttributeTargets.All)] public class RemarkAttribute : Attribute {   string pri_remark; // underlies remark property   public string supplement; // this is a named parameter   public RemarkAttribute(string comment) {     pri_remark = comment;     supplement = "None";   }   public string remark {     get {       return pri_remark;     }   } } [RemarkAttribute("This class uses an attribute.",                  supplement = "This is additional info.")] class UseAttrib {   // ... } class NamedParamDemo {   public static void Main() {     Type t = typeof(UseAttrib);     Console.Write("Attributes in " + t.Name + ": ");     object[] attribs = t.GetCustomAttributes(false);     foreach(object o in attribs) {       Console.WriteLine(o);     }     // Retrieve the RemarkAttribute.     Type tRemAtt = typeof(RemarkAttribute);     RemarkAttribute ra = (RemarkAttribute)           Attribute.GetCustomAttribute(t, tRemAtt);     Console.Write("Remark: ");     Console.WriteLine(ra.remark);     Console.Write("Supplement: ");     Console.WriteLine(ra.supplement);   } }

The output from the program is shown here:

 Attributes in UseAttrib: RemarkAttribute Remark: This class uses an attribute. Supplement: This is additional info.

As explained, a property can also be used as a named parameter. For example, here an int property called priority is added to RemarkAttribute:

 // Use a property as a named attribute parameter. using System; using System.Reflection; [AttributeUsage(AttributeTargets.All)] public class RemarkAttribute : Attribute {   string pri_remark; // underlies remark property   int pri_priority; // underlies priority property   public string supplement; // this is a named parameter   public RemarkAttribute(string comment) {     pri_remark = comment;     supplement = "None";   }   public string remark {     get {       return pri_remark;     }   }   // Use a property as a named parameter.   public int priority {     get {       return pri_priority;     }     set {       pri_priority = value;     }   } } [RemarkAttribute("This class uses an attribute.",                  supplement = "This is additional info.",                  priority = 10)] class UseAttrib {   // ... } class NamedParamDemo {   public static void Main() {     Type t = typeof(UseAttrib);     Console.Write("Attributes in " + t.Name + ": ");     object[] attribs = t.GetCustomAttributes(false);     foreach(object o in attribs) {       Console.WriteLine(o);     }     // Retrieve the RemarkAttribute.     Type tRemAtt = typeof(RemarkAttribute);     RemarkAttribute ra = (RemarkAttribute)           Attribute.GetCustomAttribute(t, tRemAtt);     Console.Write("Remark: ");     Console.WriteLine(ra.remark);     Console.Write("Supplement: ");     Console.WriteLine(ra.supplement);     Console.WriteLine("Priority: " + ra.priority);   } } 

The output is shown here:

 Attributes in UseAttrib: RemarkAttribute Remark: This class uses an attribute. Supplement: This is additional info. Priority: 10

There is one point of interest in the program. Notice the attribute specified before UseAttrib that is shown here:

 [RemarkAttribute("This class uses an attribute.",                  supplement = "This is additional info.",                  priority = 10)]

The named parameters supplement and priority are not in any special order. These two assignments can be reversed without any change to the attribute.

One last point: For both positional and named parameters, the type of an attribute parameter must be one of the built-in types, object, Type, or a one-dimensional array of one of these types.




C# 2.0(c) The Complete Reference
C# 2.0: The Complete Reference (Complete Reference Series)
ISBN: 0072262095
EAN: 2147483647
Year: 2006
Pages: 300

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net