7.4 Attributes

only for RuBoard

7.4 Attributes

Classes in .NET contain all kinds of declarative information that describes how the class works. Modifiers like Public , Private , and Friend describe the accessibility of a class, field, or method. Attributes like MustInherit , NotInheritable , and Overrides describe the inheritance- and polymorphic- related behaviors that a class exhibits. Also, data can be declared as a specific type.

Throughout this chapter, reflection has been used to query types, load them, and make method calls on them. Virtually any secret can be discovered by reflection. This ability by itself is pretty amazing when you think of the kinds of architectures that can be accommodated with it. However, there is another level beyond this: declarative attributes can be created programmatically and associated with any entity in .NET. These attributes are stored with the type's metadata and are available at all times: design time, compile time, and runtime. Reflection can examine this information and make decisions based on it.

Quite a few attributes are already defined in the framework. For instance, both the Visual Basic .NET and C# compilers recognize the Obsolete attribute. The attribute allows the programmer to associate a warning with a class.

In Visual Basic .NET, the System.ObsoleteAttribute attribute (like all attributes) is contained within angular brackets on the same line as the class definition. Unfortunately, this containment is unwieldy:

 <Obsolete("Try NewClass instead.")> Public Class OldClass
    
End Class 

Instead, use the line continuation character, which allows the attribute to be placed above the class:

 <Obsolete("Try NewClass instead.")> _
Public Class OldClass
    
End Class 

This attribute does not come into play until someone actually declares an instance of the class. Any code that uses this class compiles, but the compiler issues a warning announcing that the class is no longer in use. The attribute provides a string constructor that allows a resolution message to be specified. In this case, it tells the person compiling to try another class:

 Microsoft (R) Visual Basic .NET Compiler version 7.00.9466
for Microsoft (R) .NET Framework version 1.00.3705.209
Copyright (C) Microsoft Corporation 1987-2001. All rights reserved.
   
C:\oc.vb(11) : warning BC40000: 'OldClass' is obsolete: 
  'Try NewClass instead.'
   
    Dim oc As New OldClass( ) 

The Obsolete attribute is a great way to inform other developers of newer classes that are available. Old classes can be left in place so that existing client code will not break and at the same time give users a push towards a newer implementation.

You can treat warnings as errors to prevent compilation by using the /warnaserror+ compiler switch.

Even though the class is named ObsoleteAttribute , it can be referred to it as Obsolete when applied. This is true for any attribute, unless that attribute's short name is also a VB keyword.

The .NET class library contains numerous attribute definitions (the largest collection is contained in the System and System.Reflection namespaces), and each one is named using the attributename Attribute format. Thus, seeing which classes in the framework are attributes is fairly easy.

By now, you should recognize a pattern in the naming conventions for classes in the framework library. Exception classes end with " Exception "; EventArgs derivatives end with " EventArgs "; EventHandler classes end with " EventHandler "; and attribute classes are appended with " Attribute ." When it comes to naming conventions, use the framework as your guide.

VB.NET uses other attributes to implement features of the language. For instance, when declaring a method with a variable argument list, the ParamArray keyword, as shown in Example 7-5, is used. This keyword actually resolves to an attribute.

Example 7-5. Variable argument lists
 Imports System
   
Public Class Bag
   
    Public ReadOnly Objects( ) As Object  Public Sub New(ParamArray objects( ) As Object)   Me.Objects = objects   End Sub  Public Sub ShowItems( )
        Console.WriteLine("I have {0} items.", Objects.Length)
    End Sub
    
End Class
   
Friend Class Test
    Public Shared Sub Main( )
        
        Dim o As New Object( )  Dim thisBag As Bag = New Bag(o,o,o,o,o)   Dim thatBag As Bag = New Bag(o,o,o)  thisBag.ShowItems( )
        thatBag.ShowItems( )
        
    End Sub
End Class 

In Example 7-5, two instances of Bag are createdone with five objects, and the other with three. Any number of objects can be passed as individual arguments; the objects are used to initialize an array. Disassemble this listing and notice that the ParamArray keyword is implemented with a System.ParamArrayAttribute . Equivalently, it is possible to rewrite the constructor of the Bag class by using the attribute instead:

 Public Sub New(  <[ParamArray]( )>  objects( ) As Object)
    Me.Objects = objects    
End Sub 

The square brackets [...] are placed around ParamArray because the attribute name is a reserved word in VB. This behavior is possible with any VB reserved word.

Alternatively, remember that you can also apply attributes by using their full name. In the case of ParamArray (since it is a reserved word), it could be declared like this:

 Public Sub New(  <ParamArrayAttribute( )>  objects( ) As Object)
    Me.Objects = objects    
End Sub 

The ParamArray attribute is applied at the parameter level, but the Obsolete attribute is applied to the class (it can also be applied to a method). Attributes can be applied to any number of class elements, or even directly to the assembly; it all depends on how they were defined. Some, like ParamArray , can be applied to only one element.

The default property is another language feature that is built upon attributesin this case, the DefaultMemberAttribute class, which is a class-level attribute defined in the System.Reflection namespace. Recall that default properties allow objects to be indexed like an array. The properties themselves are prefixed with the Default keyword and are required to have at least one parameter. For example, the following code defines a default property named Item :

 Imports System
   
Public Class WorthlessArray  Default  ReadOnly Public Property Item(  ByVal index As Integer  ) _
        As Integer
        Get
            Return index
        End Get
    End Property
    
End Class 

Considering the fragment above, an instance of WorthlessArray can be treated as if it were an array:

 Dim wa As New WorthlessArray ( )
Dim answer As Integer = wa(5) 'Returns 5 

This same behavior can be accomplished by adding the DefaultMember attribute to WorthlessArray :

 Imports System
Imports System.Reflection
   
<DefaultMember("Item")> _ 
Public Class NewClass
   
    ReadOnly Public Property Item(ByVal index As Integer) _
        As Integer
        Get
            Return 42
        End Get
    End Property
    
End Class 

The Default keyword is no longer necessary, so it is removed. To test this behavior, compile this class into a library and then reference the assembly from another executable.

Another interesting attribute is System.CLSCompliantAttribute , which can be applied to almost every element in an assembly. By decorating an assembly with this attribute, everything in the assembly is declared as CLS-compliant; by default, this is not the case. Even so, an assembly that declared compliance is permitted to contain noncompliant elements. However, these elements must be designated explicitly with the ClsCompliant attribute, as shown in the following code:

 Imports System  <Assembly: CLSCompliant(True)>  Public Class NewClass  <CLSCompliant(False)> _  Public Sub NotCompliant(ByVal someNumber As SByte)
        'SByte is not CLS-compliant
    End Sub
   
End Class 

The attribute is designed to trigger a compiler warning when compliance is suspect. In other words, removing the attribute from the NotCompliant method should trigger a warning in future versions of the VB compiler. The key phrase here is future versions . Currently, the compiler ignores this attribute, but this will not always be the case. You should start using this attribute now to prepare ahead.

The previous example is a good demonstration of assembly-level attribute application. Versioning is another object-related task that is handled with an attribute at the assembly level. In fact, there are quite a few assembly-specific attributes. These attributes allow miscellaneous information such as a company name, copyright, and trademark to be associated with an assembly, as the following code illustrates:

 Imports System
Imports System.Reflection
   
<Assembly:AssemblyVersion("1.0.0.0")>
<Assembly:AssemblyCompany("Big Bad Software")>
<Assembly:AssemblyCopyright("(c) 2001 Big Bad Software.")>
<Assembly:AssemblyDescription("Chapter 7 Example.")>
<Assembly:AssemblyProduct("Object Attribute-thingy")>
<Assembly:AssemblyTrademark("Putting the YOU in Useless")> 

All of these attributes are stored in the manifest. Fire up ILDASM and check it out. This information can be located and used by anyone interested in obtaining it. Visual Studio .NET, for instance, makes the description attribute available from its property window.

Several major attributes have not been covered so far in this chapter. One notable attribute is System.SerializableAttribute , which says that a class can be serialized. Serialization is the process by which an object is converted to a form that can be stored or transported across a network. The process usually involves writing the class' member variables to a database or some other form of persistent storagea file perhapsor encoding the object's state into binary or XML and transporting it across a network. Speaking of XML, several attributes are designed to map the member variables of a class to an XML document or SOAP header. These attributes play a major role in .NET remoting.

As for remoting, don't forget WebMethodAttribute , which is used to build a web serviceundoubtedly, one of the most exciting features available in .NET. This attribute injects all plumbing necessary to call a class method over the Web by using standard XML and HTTP.

These attributes will have their day in the sun, but first things first. One final aspect of attributes, and possibly the coolest, is the ability to define your own. While the VB compiler will never know about these attributes, you could write code that can use these custom attributes meaningfully and productively.

only for RuBoard