Attributes

Attributes are a .NET feature with no direct Java equivalent. For a Java developer new to C#, attributes may well be one of the hardest language features to appreciate. However, knowledge of attributes is essential to use the .NET Framework for anything but the most trivial programs.

Attributes are a generic mechanism for associating declarative information (metadata) with program elements. This metadata is contained in the compiled assembly, allowing programs to retrieve it through reflection at run time. Other programs, particularly the common language runtime (CLR), use this information to determine how they should interact with and manage program elements.

There are many predefined attributes in the .NET class libraries, but the power and flexibility of attributes stem from the fact that they are classes. Custom attributes can be created by deriving a class from the abstract base class System.Attribute. This provides a metadata mechanism that is extensible beyond the scope of the default .NET class libraries.

This section looks first at how to assign existing attributes to elements of code and then at how to define custom attributes. We don’t discuss all the attributes included in the .NET class libraries; these will be discussed throughout the rest of the book in sections relevant to their purpose.

More Info

The mechanisms used to access attribute metadata at run time are covered in Chapter 12.

Attribute Names

The names of all attribute classes defined in the .NET class libraries end with the word Attribute. However, attribute classes are normally referenced using short names without the word Attribute appended. When using attributes, the programmer has the choice of using either form; use of the short name improves readability, and the C# compiler will automatically generate the appropriate full name. For example, the Serializable and SerializableAttribute attribute names represent the same attribute within a C# program and can be used interchangeably.

Attribute Specification

Applying previously defined attributes to program elements is called attribute specification. Attributes can be specified on the following target elements: assembly, class, constructor, delegate, enum, event, field, interface, method, module, parameter, property, return value, and struct. There is also a meta-attribute specifically applicable to attribute declarations. Valid target elements are defined as part of an attribute declaration.

Attribute specification involves placing the attribute name and any necessary parameters in square brackets immediately preceding the target element. If you apply more than one attribute to a target, place each attribute in its own set of square brackets. Alternatively, use a comma-separated list of attributes in a single set of square brackets. The ordering of attributes is not important. The following code fragment demonstrates the syntax of attribute specification:

// Single attribute specified on a class definition
[Serializable] public class MyClass {

// Two attributes specified separately on a method
    [CLSCompliant(true)]
    [WebMethod(false, Description="My Web Method")]
    public int MyMethod(int someParam) {
    // implementation
    }

    // Two attributes specified in a single line on a method
    [WebMethod(true, Description="My Other Web Method"),
         CLSCompliant(true)]
    public int MyOtherMethod(string someParam) {
    // implementation
    }
}

In this example, we’ve used three different attributes. The Serializable attribute was placed on the same line as the target class declaration. The CLSCompliant and WebMethod attributes are both specified on two method declarations. For the first method, each attribute is on a separate line; for the second method, they are placed together in a single set of square brackets on a single line. Both approaches have the same effect.

Attribute Parameters

The foregoing examples also demonstrate how parameters are passed to attributes. The Serializable attribute takes no parameters. The CLSCompliant attribute takes one positional parameter, and the WebMethod attribute takes one positional and one named parameter. The way the compiler handles attribute parameters is one of the key behaviors that differentiate attributes from other classes.

Positional parameters

Positional parameters are the same as parameters in any normal instance constructor. They are expected in a predefined order, must be of the correct type, and must appear before any named parameters.

Named parameters

Named parameters are specified by providing a comma-separated list of name/value pairs. Named parameters must be placed after all positional parameters; their order is not important. Providing named parameters that are not supported by an attribute or values that cannot be implicitly converted to the expected type will cause a compile-time error.

Attribute Target Specification and Global Attributes

The target of an attribute is normally apparent by its position preceding the target element. However, there are circumstances in which the target is a module or an assembly, or in which the target is ambiguous. Ambiguity occurs most frequently when specifying an attribute on a member that returns a value; both the method and the return value are valid attribute targets.

In these situations, the programmer must clarify the target by prefixing the attribute name with the appropriate attribute target specifier from the following list: assembly, field, event, method, module, param, property, return, or type. The most common use of the attribute target specifier is to identify an assembly or module as the target of an attribute; these are called global attributes. For example:

[assembly:CLSCompliant(true)]
[module:DynamicLoad]

For global attributes, these statements must occur after any top level using statements but before any namespace or type declarations.

Tip

It’s common to put assembly attributes in a separate file. The wizard-generated projects in Microsoft Visual Studio .NET take this approach, using a file named AssemblyInfo.cs.

Custom Attributes

An attribute is a class that derives from System.Attribute. The following is an example of a simple attribute used to identify the developer who created a class or assembly:

using System ; // Needed for Attributes

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Assembly,
    AllowMultiple=true, Inherited = false)]
public class CreatorAttribute: System.Attribute {

    private String name;    // creator's name
    private String email;   // creator's email

    // Declare public constructor
    public CreatorAttribute (String creator) {
        name = creator;
        email = "";
    }

    // Declare a property to get/set the creator's email address
    public string Email {
        get {
            return email;
        }
        set {
            email = value;
        }
    }

    // Declare a property to get the creator's name
    public string Name {
        get { return name;}
    }
}

The following examples demonstrate the use of this attribute:

[assembly:Creator("Bob")]
[assembly:Creator("Jim" , Email = "[email protected]")]

[Creator ("Judy", Email = "[email protected]")]
public class MyClass {
    // class implementation
}

The System.AttributeUsage Attribute

The AttributeUsage attribute defines how a new attribute can be used. AttributeUsage takes one positional and two named parameters, which are described in Table 6-2. The table also specifies the default value applied to an attribute declaration if the AttributeUsage attribute is omitted or a parameter isn’t specified.

A compiler error occurs if an attempt is made to use the AttributeUsage attribute on a class that is not derived from System.Attribute.

Table 6-2. AttributeUsage Parameter Description

Parameter

Type

Description

Default

validOn

positional

Identifies which program elements the attribute is valid against. Valid values are any member of the System.AttributeTargets enumeration.

AttributeTargets.All

AllowMultiple

named

Whether the attribute can be specified more than once for a single element.

false

Inherited

named

Whether the attribute is inherited by derived classes or overridden members.

true

Attribute Declaration

The remainder of the attribute declaration is the same as for any other class; however, all attribute classes must be declared public.

Important

The documentation for the first release of .NET states that all user-defined attributes will implicitly have the Attribute string appended to their class name if not done so manually. However, our experience shows that this is not the case. Custom attributes should always be given a name ending in the word Attribute to maintain consistency, enabling the C# compiler to support the dual-name behavior described earlier.

Attribute Constructors

A custom attribute must have at least one public constructor. The constructor parameters become the attribute’s positional parameters. As with any other class, more than one constructor can be specified; providing overloaded constructors gives the users of the attribute the option of using different sets of positional parameters when specifying the attribute.

Named Parameters

Any public read/write fields and properties contained in an attribute are automatically exposed as named parameters. In the foregoing example, the Email property is exposed as a named parameter, but the Name property is read-only, meaning that it cannot be used as a named parameter.

Compile-Time Attributes

While the majority of attributes are used for specifying run-time-accessible metadata, a number of them are evaluated at compile time. The AttributeUsage attribute discussed earlier is evaluated at compile time. Two other important compile-time attributes are discussed in the following sections.

System.Diagnostics.ConditionalAttribute

The Conditional attribute marks a method whose execution depends on the definition of a preprocessor symbol. (See the #define and #undef section within the Preprocessor Directives section later in this chapter.) If a method is marked with the Conditional attribute, any attempts to execute the method will be removed by the compiler if the specified preprocessor symbol is not defined at the calling point.

The Conditional attribute is a more elegant but less flexible alternative to using #ifdef preprocessor directives. It centralizes the conditional logic rather than having many #ifdef statements throughout the code. To be a valid target of the Conditional attribute, a method must return void; otherwise, it won’t be possible to remove references without breaking the calling code.

For example, to define a method whose execution is conditional on the declaration of the symbol DEBUG, use the following syntax:

//The following method will execute if the DEBUG symbol is defined
[Conditional("DEBUG")]
public void SomeMethod() { // implementation here}

Multiple instances of the Conditional attribute can be specified for a method producing a logical OR behavior. For example:

// The following method will execute if WIN95 OR WIN2000
// symbols are defined
[Conditional("WIN95"), Conditional("WIN2000")]
public void SomeMethod() { // implementation here}

Achieving logical AND behavior is messy, involving the unpleasant use of intermediate Conditional methods. For example:

// The following method will execute if the DEBUG symbol is defined
[Conditional("DEBUG")]
public void SomeMethod() {
SomeOtherMethod()
}

// The following method will only execute if the WIN95 symbol is defined.
// When called from SomeMethod above, this has the effect of only executing
// SomeOtherMethod if both the DEBUG AND WIN95 symbols are defined
[Conditional("WIN95")]
private void SomeOtherMethod() { // implementation here}

System.ObsoleteAttribute

The Obsolete attribute marks a program element as obsolete and forces the compiler to raise a warning or an error when other code attempts to use the element. This is similar to marking an element as deprecated in Java but places more control in the hands of the code’s author.

The two positional parameters of the Obsolete attribute specify the message to be displayed by the compiler and whether the compiler raises a warning or an error. For example:

// Specifying true as the second argument means that code referencing
// the following method will generate a compiler error about the
// method being deprecated.
[Obsolete("This method has been deprecated.", true)]
public string MyMethod() { // implementation here}

// Specifying false as the second argument means that code referencing
// the following method will generate a compiler warning that the
// method is in the process of being phased out.
[Obsolete("This method is being phased out, use SomeMethod().", false)]
public string MyOtherMethod() { // implementation here}

The Obsolete attribute is not limited to specification on methods. Valid targets are class, struct, enum, constructor, method, property, field, event, interface, and delegate.

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

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