Standard Modules Conditional Compilation

104 Public Sub producer_AnotherEventByVal MyData As Integer Console.WriteLineReceived the AnotherEvent event. Console.WriteLineThe value of MyData is {0}., FormatMyData End Sub Public Sub DoSomething ... producer.DoSomething ... End Sub End Class The hookup of the handler method to the event producer is done with this statement in the EventConsumer classs constructor: AddHandler producer.AnotherEvent, _ New EventProducer.SomeDelegateAddressOf producer_AnotherEvent The AddHandler statement and its companion, the RemoveHandler statement, allow event handlers to be dynamically registered and unregistered. The RemoveHandler statement takes exactly the same parameters as the AddHandler statement.

2.21 Standard Modules

A standard module is a type declaration. It is introduced with the Module statement, as shown here: Public Module ModuleTest ... End Module Dont confuse the Visual Basic .NET term, standard module, with the .NET term, module. They are unrelated to each other. See Chapt er 3 for information about .NET modules. Standard module definitions are similar to class definitions, with these differences: • Standard module members are implicitly shared. • Standard modules cannot be inherited. • The members in a standard module can be referenced without being qualified with the standard module name. Standard modules are a good place to put global variables and procedures that arent logically associated with any class.

2.22 Attributes

An attribute is a program element that modifies some declaration. Here is a simple example: SomeAttribute Public Class SomeClass ... End Class This example shows a fictitious SomeAttribute attribute that applies to a class declaration. Attributes appear within angle brackets and are following by parentheses , which may 105 contain a list of arguments. To apply multiple attributes to a single declaration, separate them with commas within a single set of angle brackets, like this: SomeAttribute, SomeOtherAttribute Public Class SomeClass ... End Class Attributes can be placed on the following kinds of declarations: Types This includes classes, delegates, enumerations, events, interfaces, Visual Basic .NET standard modules, and structures. The attribute is placed at the beginning of the first line of the type declaration: SomeAttribute Public Class SomeClass ... End Class Constructors The attribute is placed at the beginning of the first line of the constructor declaration: SomeAttribute Public Sub New ... End Sub Fields The attribute is placed at the beginning of the field declaration: SomeAttribute Public SomeField As Integer Methods The attribute is placed at the beginning of the first line of the method declaration: SomeAttribute Public Sub SomeMethod ... End Sub Parameters The attribute is placed immediately prior to the parameter declaration. Each parameter can have its own attributes: Public Sub SomeMethodSomeAttribute ByVal SomeParameter As Integer Properties An attribute that applies to a property is placed at the beginning of the first line of the property declaration. An attribute that applies specifically to one or both of a propertys Get or Set methods is placed at the beginning of the first line of the respective method declaration: SomeAttribute Public Property SomeProperty As Integer Get ... End Get SomeOtherAttribute SetByVal Value As Integer ... 106 End Set End Property Return values The attribute is placed after the As keyword and before the type name: Public Function SomeFunction As SomeAttribute Integer ... End Function Assemblies The attribute is placed at the top of the Visual Basic .NET source file, following any Imports statements and preceding any declarations. The attribute must be qualified with the Assembly keyword so that the compiler knows to apply the attribute to the assembly rather than the module. Assemblies and modules are explained in Chapt er 3 . Imports ... Assembly: SomeAttribute Public Class ... Modules The attribute is placed at the top of the Visual Basic .NET source file, following any Imports statements and preceding any declarations. The attribute must be qualified with the Module keyword so that the compiler knows to apply the attribute to the module rather than the assembly. Assemblies and modules are explained in Chapt er 3 . Imports ... Module: SomeAttribute Public Class ... Some attributes are usable only on a subset of this list. The .NET Framework supplies several standard attributes. For example, the Obsolete attribute provides an indication that the flagged declaration should not be used in new code. This allows component developers to leave obsolete declarations in the component for backward compatibility, while still providing a hint to component users that certain declarations should no longer be used. Heres an example: ObsoleteUse ISomeInterface2 instead. Public Interface ISomeInterface ... End Interface When this code is compiled, the Obsolete attribute and the associated message are compiled into the application. Tools or other code can make use of this information. For example, if the compiled application is a code library referenced by some project in Visual Studio .NET, Visual Studio .NET warns the developer when she tries to make use of any items that are flagged as Obsolete . Using the previous example, if the developer declares a class that implements ISomeInterface , Visual Studio .NET displays the following warning: Obsolete: Use ISomeInterface2 instead. See Appendix A for the list of attributes defined by the .NET Framework. 107

2.22.1 Creating Custom Attributes

The attribute mechanism is extensible. A new attribute is defined by declaring a class that derives from the Attribute type in the System namespace and that provides an indication of what declarations the attribute should be allowed to modify. Heres an example: AttributeUsageAttributeTargets.All Public Class SomeAttribute Inherits System.Attribute End Class This code defines an attribute called SomeAttribute . The SomeAttribute class itself is modified by the AttributeUsage attribute. The AttributeUsage attribute is a standard .NET Framework attribute that indicates which declarations can be modified by the new attribute. In this case, the value of AttributeTargets.All indicates that the SomeAttribute attribute can be applied to any and all declarations. The argument of the AttributeUsage attribute is of type AttributeTargets defined in the System namespace. The values in this enumeration are: Assembly , Module , Class , Struct , Enum , Constructor , Method , Property , Field , Event , Interface , Parameter , Delegate , ReturnValue , and All . To create an attribute that takes one or more arguments, add a parameterized constructor to the attribute class. Heres an example: AttributeUsageAttributeTargets.Method _ Public Class MethodDocumentationAttribute Inherits System.Attribute Public ReadOnly Author As String Public ReadOnly Description As String Public Sub NewByVal Author As String, ByVal Description As String Me.Author = Author Me.Description = Description End Sub End Class This code defines an attribute that takes two parameters: Author and Description . It could be used to modify a method declaration like this: MethodDocumentationDave Grundgeiger, This is my method. _ Public Sub SomeMethod ... End Sub By convention, attribute names end with the word Attribute . Visual Basic .NET references attributes either by their full names—for example, MethodDocumentationAttribute —or by their names less the trailing Attribute —for example, MethodDocumentation . Attributes whose names do not end with the word Attribute are simply referenced by their full names.

2.22.2 Reading Attributes

Compiled applications can be programmatically examined to determine what attributes, if any, are associated with the applications various declarations. For example, it is possible to write a Visual 108 Basic .NET program that searches a compiled component for the Obsolete attribute and produces a report. This is done by using the .NET Frameworks reflection capability. Reflection is the ability to programmatically examine type information. The .NET Framework provides a great deal of support for reflection in the Type class in the System namespace and in the types found in the System.Reflection namespace. Reflection deserves a book of its own, but heres a brief look to get you started: Imports System Imports System.Reflection ... Dim typ As Type = GetTypeSystem.Data.SqlClient.SqlConnection Dim objs As Object = typ.GetCustomAttributesFalse Dim obj As Object For Each obj In objs Console.WriteLineobj.GetType .FullName Next This code fragment does the following: • Uses the GetType function to get a Type object that represents the SqlConnection type defined in the System.Data.SqlClient namespace. You can experiment with putting any type name here including the types that you create. I chose SqlConnection because I know that it happens to have an attribute associated with it. • Calls the GetCustomAttributes method of the Type object to get an array of objects that represent the attributes associated with the type. Each object in the array represents an attribute. • Loops through the object array and prints the type name of each object. The type name is the name of the attribute. The output is shown here: System.ComponentModel.DefaultEventAttribute Reflection is not discussed further in this book. Review the .NET documentation for the System.Reflection namespace for more information.

2.23 Conditional Compilation

Conditional compilation is the ability to specify that a certain block of code will be compiled into the application only under certain conditions. Conditional compilation uses precompiler directives to affect which lines are included in the compilation process. This feature is often used to wrap code used only for debugging. For example: Const DEBUG = True Public Sub SomeMethod If DEBUG Then Console.WriteLineEntering SomeMethod End If ... If DEBUG Then Console.WriteLineExiting SomeMethod 109 End If End Sub The Const directive defines a symbolic constant for the compiler. This constant is later referenced in the If directives. If the constant evaluates to True , the statements within the If block are compiled into the application. If the constant evaluates to False , the statements within the If block are ignored. The scope of constants defined by the Const directive is the source file in which the directive appears. However, if the constant is referenced prior to the definition, its value is Nothing . It is therefore best to define constants near the top of the file. Alternatively, compiler constants can be defined on the command line or within the Visual Studio .NET IDE. If youre compiling from the command line, use the define compiler switch, like this: vbc MySource.vb define:DEBUG=True You can set multiple constants within a single define switch by separating the symbol=value pairs with commas, like this: vbc MySource.vb define:DEBUG=True,SOMECONSTANT=42 To assign compiler constants in Visual Studio .NET: 1. Right-click on the project name in the Solution Explorer window and choose Properties. This brings up the Project Property Pages dialog box. If the Solution Explorer window is not visible, choose View Solution Explorer from the Visual Studio .NET main menu to make it appear. 2. Within the Project Property Pages dialog box, choose the Configuration Properties folder. Within that folder, choose the Build property page. This causes the configuration build options to appear on the right side of the dialog box. 3. Add values to the Custom constants text box on the right side of the dialog box.

2.24 Summary