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