Attributes

   


When you declare a variable to of type int, specify a method to be public, or specify a class to be a subclass of another class called, say, Shape, you are adding declarative information to each of these elements (the variable, method, and class). As mentioned in Chapter 6, "Types Part I: The Simple Types," declarative information can be compared to telling someone what to do, whereas the following imperative statement:

 sum = number1 + number2; 

is similar to telling someone how to do something (how to calculate the sum of two numbers). The "what" is significantly more expressive than the "how," so your ability to decorate code elements with additional information (adding declarative information) is a powerful concept.

Rather than restricting your ability (like most other languages) to decorate code elements with only the predefined C# language constructs and keywords like int and public and a colon : (for declaring a subclass), C# allows you to extend this ability through the use of attributes.

An attribute allows you to add declarative information to C#'s code elements beyond that already made possible by C#'s predefined keywords and constructs. All attributes are derived from the abstract class System.Attribute.

Generally, you can decorate the following code elements with attributes: assemblies, classes, structs, class and struct members, formal parameters, and return values. Each attribute is often meant to target only one or a few of these elements.

The .NET Framework contains many predefined attributes you can apply. The following are just three examples with a short description of their impact on your code:

  • Prevent selected elements from being compiled and called, by decorating them with the System.Diagnostics.ConditionalAttribute. In some ways, this is similar to what you can achieve with the preprocessor directives.

  • Mark selected elements obsolete with the System.ObsoleteAttribute and triggering the compiler to return a warning or an error if any of these elements are used by other parts of the program. This is useful, for example, when your program contains an old and a new version of a method for performing a certain function, and you want to entice other programmers to use the new version.

  • Mark selected methods with the System.Runtime.InteropServices. DllImportAttribute to call functions outside of .NET that, for example, exists within the basic Windows Application Programming Interface.

Note

graphics/common.gif

You can define your own custom-made attributes, but most programmers will rarely make use of this ability. As a result, this topic is not discussed in this book.


To decorate an element with a particular kind of attribute, you must enclose the attribute name in square brackets and position this construct just before the element you want to decorate. For example, to decorate the StartSteamEngine method with the System.ObsoleteAttribute, you can write the following:

 [System.ObsoleteAttribute] public void StartSteamEngine() {     ... } 

This will cause the compiler (during the compilation) to report the following warning for every method that attempts to call StartSteamEngine:

 ...Warning CS061 "...StartSteamEngine() is obsolete 

Note

graphics/common.gif

The attribute decorations of an element are stored as part of this element's metadata in the assembly. As discussed in Chapter 7, "Types Part II: Operators, Enumerators, and Strings," metadata (including the attribute decorations) can be inspected during runtime through an advanced mechanism called reflection.


Conventionally, an attribute name ends with Attribute. All the intrinsic .NET attributes follow this naming style.

The compiler allows you to omit the Attribute part of the attribute name when you specify its name in the square brackets. You can substitute [System.ObsoleteAttribute] with [System.Obsolete] or even [Obsolete] if you include the usual line using System; in your program.

The sheer presence of an attribute often provides sufficient declarative information on the target element. However, some attributes, like ObsoleteAttribute, allow you to supply extra information in the form of arguments, similar to those provided to a conventional constructor when you create a new object. For example, you might want the ObsoleteAttribute (annotated on the StartSteamEngine before) to cause the compiler to write the message "StartSteamEngine is obsolete: Use the StartJetEngine method instead" instead of just "StartSteamEngine is obsolete:" as before, and to cause an error instead of just a warning, when a call to this method is encountered by the compiler in the source code. This is accomplished as follows:

 [System.ObsoleteAttribute("Use the StartJetEngine method instead", true)] public void StartSteamEngine() {     ... } 

The second parameter true, enclosed by the parentheses, is assigned to a variable called IsError in the ObsoleteAttribute, which is false by default. When IsError is true, the Obsolete attribute will trigger a compiler error instead of a warning.

You must position the text ("Use the StartJetEngine…") first, and the Boolean value (true) second for the attribute annotation to be correct. For this reason, these arguments are often referred to as positional parameters.

Some attributes also allow you to assign values to named parameters. These named parameters refer to properties in the corresponding attribute's definition containing set accessors. For example, the XmlArrayAttribute attribute in the System.Xml.Serialization namespace contains the read-write (get, set) property called ElementName (it is not relevant in this context what this attribute does). You assign values to named parameters by naming them followed by an equals sign followed by the value, as shown in the following example:

 [XmlArrayAttribute("YourString", ElementName = "MyDouble")] 

which assigns "MyDouble" to XmlArrayAttribute's property ElementName. Notice that the named parameter must come after any positional parameters.

You can annotate one element with several attributes. The syntax for doing this is flexible, you can either list each attribute enclosed in square brackets one after the other before the target element as follows:

 [Obsolete] [Conditional("TEST")] [Serializable] class MyClass {     ... } 

Alternatively, you can list all attributes inside one pair of square brackets and separate them by commas:

 [Obsolete, Conditional("TEST"), Serializable] class MyClass {     ... } 

Finally, you can combine these two styles:

 [Obsolete] [Conditional("TEST"), Serializable] class MyClass {     ... } 

A Simple Attribute Annotation Example

Listing 21.3 demonstrates the two intrinsic attributes System.ObsoleteAttribute and System.Diagnostics.ConditionalAttribute. The former has already been described in some detail, and the latter needs a further introduction here.

Like the #if preprocessor directive, the ConditionalAttribute tests whether a specified identifier exists (has been defined by the #define directive). For example, the following construct tests whether the TEST identifier exists:

 [ConditionalAttribute("TEST")] 

The ConditionalAttribute can only be used to annotate methods with return type void. If the specified identifier (TEST in this case) is undefined, the target method is announced non-callable, and all calls to this method are cancelled during compilation.

You could achieve the same effect by enclosing all calls to the target method inside the familiar #if-#endif pair. However, if the target method is called from twenty different parts of a program, this requires you to insert twenty different #if-#endif pairs, compared with only one ConditionalAttribute.

The ConditionalAttribute is, like the preprocessor directives, useful for including and excluding code inserted for testing purposes.

Listing 21.3 Apollo13.cs
01: #define TEST 02: 03: using System; 04: using System.Diagnostics; 05: 06: class Rocket 07: { 08:     private double speed; 09:     private double fuel; 10:     private double distanceFromMoon; 11: 12:     public Rocket(double initSpeed, double initFuel, double initDistanceFromMoon) 13:     { 14:         speed = initSpeed; 15:         fuel = initFuel; 16:         distanceFromMoon = initDistanceFromMoon; 17:     } 18: 19:     [Obsolete("Please use Rocket.CurrentState instead")] 20:     public void CurrentSituation() 21:     { 22:         if (fuel > 5000) 23:             Console.WriteLine("Everything seems OK"); 24:         else 25:             Console.WriteLine("Houston, I think we've got a problem"); 26:     } 27: 28:     public void CurrentState() 29:     { 30:         Console.WriteLine("Current speed: { 0}   Current fuel left: { 1} ", speed,  graphics/ccc.giffuel); 31:     } 32: 33:     [Conditional("TEST")] 34:     public void TestWriteAllDetails() 35:     { 36:         Console.WriteLine("Testing: Instance variables: speed: { 0} , fuel: { 1} ,  graphics/ccc.gifdistanceFromMoon: { 2} ", speed, fuel, distanceFromMoon); 38:     } 39: } 40: 41: class ControlCenter 42: { 43:     public static void Main() 44:     { 45:         Rocket apollo13 = new Rocket(10000, 2000, 20000); 46: 47:         apollo13.CurrentSituation(); 48:         apollo13.CurrentState(); 49:         apollo13.TestWriteAllDetails(); 50:     } 51: } 

Compiler warning during compilation:

 Apollo13.cs(47,9): warning CS0618: 'Rocket.CurrentSituation()' is obsolete: 'Please use  graphics/ccc.gifRocket.CurrentState instead' 

Sample output 1: No changes made to Listing 21.3:

 Houston, I think we've got a problem Current speed: 10000  Current fuel left: 2000 Testing: Instance variables: speed: 10000, fuel: 2000, distanceFromMoon: 20000 

Sample output 2: With line 1 removed and the program recompiled

 Houston, I think we've got a problem Current speed: 10000  Current fuel left: 2000 

Line 19 decorates the method CurrentSituation with the Obsolete attribute. The additional positional parameter of type string asks the compiler to write the text "Please use Rocket.CurrentState instead" as part of its warning message. Consequently, whenever the compiler during the compilation encounters a call to CurrentSituation, it issues the warning message shown in the sample output. The single warning message displayed is caused by line 47.

If, despite the warning, you run the program, you will see sample output 1. The third line of this sample output is the output from TestWriteAllDetails, which was called from line 49. The Conditional attribute decorating the TestWriteAllDetails method in line 33 didn't deter the compiler from including the calls to this method, because TEST exists at line 33. TEST is defined in line 1.

If you remove line 1 and recompile the program, you will see sample output 2. With an undefined TEST in line 33 the compiler prevented the call to TestWriteAllDetails from being made.

The formal syntax for specifying an attribute annotation is shown in Syntax Box 21.1.

Syntax Box 21.1 Attribute Annotation

 Attribute_annotation::= [[<Target_element>:] <Attribute_name> ( [<Positional_parameter_list>], graphics/ccc.gif [<Named_parameter_list>] ) ] 

Where

 <Named_parameter_list> ::= <Named_parameter_1> = <Expression_1>, < graphics/ccc.gifNamed_parameter_2> = <Expression_2>... 

Note:

Generally, the attribute target is the element immediately following the attribute annotation. However, sometimes this rule is not sufficient to pinpoint the actual element we want to annotate. Then we must resort to the <Target_element> : specification included in the attribute annotation definition shown earlier in this syntax box. For example, it is impossible to position an attribute annotation in front of an assembly. To decorate an assembly with the CLSCompliantAttribute, we must instead include the following line in one of the source code files that are compiled to this assembly:

 [assembly: CLSCompliantAttribute(true)] 


   


C# Primer Plus
C Primer Plus (5th Edition)
ISBN: 0672326965
EAN: 2147483647
Year: 2000
Pages: 286
Authors: Stephen Prata

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