Defining Structures and Classes

We know that classes are reference types and structures are value types, literally derived from the System.ValueType . We also know that structures have replaced the type construct from VB6. What may not be so obvious is that both classes and structures support fields, properties, methods , constructors, and events. It also may not be immediately apparent that structures support only private or public members .

The structure is more like the C or C++ struct construct than it is like the VB6 type construct. However, because the VB6 type construct no longer exists, structure is the closest (and intended) replacement.

This section demonstrates how to declare each type of member that you can define in structures and classes. The examples in this section are brief since you will see dozens of examples throughout the rest of this book.

Adding Fields

Fields are the data members of structures and classes that maintain the type's state information. As a general rule, fields use the private access modifier, which means they are accessible only internally.

StickMan.sln contains a class and structure that are identical. Both the StickMan class and StickMan structure contain precisely the same elements. Each type draws a stick figure and is capable of making the StickMan perform some basic movements. The code can be identical because neither the class nor the structure uses things that aren't allowed in structures. (See the earlier Structures section to review the differences between classes and structures.)

The StickMan type defines four fields.

 Private FOwner As Form Private FX, FY As Integer Private FPosition As Integer 

NOTE

Visual Basic .NET is not case-sensitive. If it were, we could use camel-cased names for fields and Pascal-cased names for properties. (In camel- casing , the first initial of the first word is lower-cased and the first initial of each subsequent word in the name is upper-cased, for example, anInteger . In Pascal-casing, the first letter of all words in the name are upper-cased, for example, MyFunction .) However, since Visual Basic .NET is not case-sensitive and the Hungarian notation is discouraged for .NET, in this example I adopted an F prefix to denote a field and dropped the F for properties.

The first field is a form. Since the StickMan is not a control, we need a canvas to draw on; a form works fine. The next two fields are X and Y offsets. These values are used to position the StickMan. The FPosition field is used to make the legs move when the StickMan moves.

Making fields private is a wise strategy. The fundamental premise is that providing unfettered access to state variables (fields) makes it more difficult to prevent misuse. Of course, technically you can make data public, but this debate has been held and the outcome has been decided. Make fields private and provide limited access through properties.

Adding Properties

Properties are like fathers and fields are like daughters in old-fashioned farm tales. Fathers provide limited access to the daughters (showing the prospective suitor the double-barrel shotgun) until the daughters reach maturity and then continue to provide firm guidance.

Properties are members that are used like fields but behave like methods. About ten years ago the common thinking was that programmers would be wise to access data through functions; but functions are inconvenient to use to read and write fields, and some programmers simply ignored this advice. The property idiom was developed to provide the ease of use of fields and the benefit of methods. If fields are private and properties constrain access to fields, the only way the state of an object can be wrong is if the producer ”referring to the person who writes the code ”implemented the properties inappropriately.

There are two properties for the StickMan, an X property and a Y property. The X and Y properties are associated with the X and Y fields, respectively. (As you read the code below, keep in mind the convention adopted to use an F prefix for fields.)

 Public ReadOnly Property X() As Integer   Get     Return FX   End Get End Property Public ReadOnly Property Y() As Integer   Get     Return FY   End Get End Property 

These two properties are defined as read-only. Applying the ReadOnly modifier means that the value of the property can only be read. Grammatically this means that you can implement only a Get block. If you want consumers (the persons using the code) to be able to use properties as left-hand-side values and right-hand-side values ” shortened to l-value and r-value, respectively ”you will need to implement both Get and Set blocks. ( Get blocks are called getters and Set blocks are called setters.)

If you elect to allow consumers to move the StickMan by changing one positional value at a time, you could implement setters for the X and Y properties. The revised code would appear as follows .

 Public Property X() As Integer   Get     Return FX   End Get   Set(ByVal Value As Integer)     ' Validate Value before assigning     FX = Value   End Set End Property Public Property Y() As Integer   Get     Return FY   End Get   Set(ByVal Value As Integer)     ' Validate Value before assigning     FY = Value   End Set End Property 

The setters are the gatekeepers. Theoretically, as long as the producer does not introduce any errors internally and the setters screen potential values, the state of a class can never be invalid. I refer to the validation code in setters as sentries ; the role of the sentry is to keep bad data from getting into the underlying field.

Adding Methods

Methods are simply member procedures. Public methods are those that you want consumers to be able to call directly, and private methods are the ones that you as the producer will call to support the public behavior.

The StickMan implements several public methods that describe a kind of movement. The StickMan.Run method accepts a direction and a distance, moving the StickMan very quickly the number of times expressed by the Distance variable. Direction is implemented as an enumeration named Direction . The code for the Enum and Run methods follows.

 Public Enum Direction   Down   DownLeft   Left   UpLeft   Up   UpRight   Right   DownRight End Enum Public Sub Run(ByVal Where As Common.Direction, _   ByVal Distance As Integer)   Dim I As Integer = Distance   For I = 1 To Distance     Walk(Where)     Thread.Sleep(75)   Next End Sub 

TIP

You will need to set the Option Strict On statement to enforce strict type checking. This is a good practice.

The enumeration provides us with an opportunity to use strongly typed values that have semantic meaning rather than simple integers. The Run method takes a direction and uses the distance as a loop control to call Walk with a very short wait period.

As a general strategy, try to keep the number of public methods to no more than a handful. Use as many private methods as you need to support the public interface. Constraining the number of public methods will make your classes easier for consumers to use and less susceptible to instability if the classes do need to be changed.

Adding Constructors

Constructors are special methods invoked before an object is created. A constructor's job is to initialize an object. If you combine a constructor that initializes an object to a valid state and property methods that only allow valid states, your objects should remain in a valid state.

In VB6 we didn't have constructors; we did have the Class_Initialize method. The difference between Class_Initialize and a VB .NET constructor is that we can pass arguments to constructors. We can't do that with the Class_Initialize method.

NOTE

C++ implements constructors as methods with no return type that have the identical name as the class. C# follows C++ in this regard, and by convention Object Pascal uses the keyword constructor and the name Create .

Constructors are implemented as a Sub New method in VB .NET. When you instantiate an object with the new keyword, as in

 Dim Log As EventLog = New EventLog() 

you are really invoking the New method. If you use the F8 key (with the VB6 keyboard scheme), you can step into a statement that instantiates an object ( assuming you have the source for that object's class) and see for yourself that you are stepping into a subroutine named New .

Implementing a Sub New Method

Every form has a default constructor that accepts no arguments. Every time you create a Windows Form, the form will contain a constructor that calls InitializeComponent . Here is the constructor from the StickMan.sln example program.

 Public Sub New()   MyBase.New()   'This call is required by the Windows Form Designer.   InitializeComponent()   'Add any initialization after the InitializeComponent() call End Sub 

Calling an Initialize method is a good strategy for implementing constructors; as demonstrated in a Form's constructor, an Initialize method keeps the constructor simple. In the case of a Windows Form, InitializeComponent actually creates instances of the controls defined on your form.

In the listing above we can see that there is a statement, MyBase.New , and InitializeComponent . MyBase refers to the parent class of this class, and MyBase.New calls the parent constructor of the Form. Having to remember to call inherited methods is a wrinkle you will have to get used to in VB .NET. (Chapter 2 covers inheritance in greater detail.)

The other thing that is evident in the constructor in the listing is that there are no arguments. We can implement an overloaded constructor that contains arguments, also referred to as parameters .

Parameterized Constructors

A parameterized constructor is a Sub New method that accepts one or more arguments. Constructor parameters are defined the same way you would define any other method's parameters. First type the specifier , parameter name, type, and a default value if you want one. Applied to the StickMan, here is a parameterized constructor used for both the structure and class versions of StickMan.

 Public Sub New(ByVal Owner As Form, _   ByVal X As Integer, ByVal Y As Integer)   FOwner = Owner   FX = X   FY = Y   Draw() End Sub 

StickMan defines three required parameters: a form, an X value, and a Y value. The form is used as the drawing surface, and the X and Y values represent the horizontal and vertical positions , respectively. It is important to note that you may implement a parameterized constructor only for structures. The structure construct implements a default constructor that you may not override.

When you create an instance of a structure without parameters, you do not use the New keyword. If you invoke the parameterized constructor for a structure, you must use the New keyword. This difference between classes and structures is where we run into trouble.

The structure version of StickMan has a default constructor. This means that we can create a structure StickMan instance without calling the parameterized Sub New method. The implications are that we have a StickMan without a form to draw on. This leads to an improperly initialized StickMan and a NullReferenceException when the structure StickMan tries to draw itself. Alternatively, the class StickMan does not implement a nonparameterized Sub New method; as a consequence we must invoke the parameterized constructor for the class StickMan, which helps ensure that it is properly initialized.

As a general rule, reserve structures for very simple types and use classes for almost everything else. You have more control when you use a class, and control is the name of the game.

Adding Finalize and Dispose Methods

Visual Basic .NET uses nondeterministic memory management. Deterministic means you do it. Nondeterministic means that .NET does it. How does nondeterministic memory management work, and why do we have it? These are very insightful questions. As the resident code guru, you should know the answers.

Languages like C++ and Object Pascal use deterministic memory management. This means the producer creates an object and then releases the object when it is no longer needed. The problem deterministic memory management introduced is referred to as memory leaks , and one of the symptoms of memory leaks is referred to as the slicing problem . The slicing problem, and consequently a memory leak, occurs when two references refer to the same object in memory and one reference deletes the memory while the other reference attempts to use the memory. The memory has been sliced off. Another form of memory leak is when objects are created and the reference variable goes out of scope; that is, the pointer no longer exists but the memory does. Memory leaks are probably the biggest cause of serious defects for software developers.

To help us, the concept of garbage collection was invented. In garbage collection, the language keeps track of allocated memory and cleans up after programmers automatically. (Garbage collection is similar to the relationship most teenagers have with their mothers: Teenagers mess it up and moms clean it up. Perhaps only teenagers who are extremely fastidious should program in C++.) The idea is that computers are good at tedious tasks and people are not. If the garbage collector is implemented correctly, memory leaks will no longer exist, at least in theory.

NOTE

You can tell the garbage collector to run by invoking the GC.Collect method, but this is not a recommended practice. It's one thing to be sloppy and hope your mother cleans up after you; it's entirely another to order your mom to do it.

Visual Basic .NET uses nondeterministic memory management, which means that when you are finished with an object, the garbage collector decides when to destroy the memory. The implication of nondeterministic memory management in VB .NET is that you can implement a destructor, but you won't know when it will be called.

Implementing a Finalize Method

Destructors in VB .NET are implemented as a protected method named Finalize . The garbage collector will call your Finalize method if you implement one, but you won't know when that will be. The best thing to do is to try not to need deterministic memory management. This is not always possible, so the next subsection will demonstrate how to compensate for nondeterministic memory management. But first, let's finish talking about Finalize .

You may implement a Finalize method. If you do, note the following:

  • Finalize cannot be public.

  • You can't invoke Finalize directly.

  • Finalize is never overloaded by convention but must be overridden if you are going to introduce new behavior. (We will talk more about overriding and overloading in Chapter 2.)

  • Finalize never takes any arguments.

Finalize is always defined as follows:

 Protected Sub Finalize()   ' Cleanup code here End Sub 

Remember, you need to implement this method only if you have some specific cleanup you need to do. Managed .NET objects will be cleaned up automatically.

Implementing a Dispose Method

Another convention employed in VB .NET is to implement a public method Dispose that performs deterministic clean up for you. The Dispose method is implemented as a public subroutine that takes no arguments. Dispose can be implemented reliably as follows:

 Private Disposed As Boolean = False Public Sub Dispose()   If(Disposed) Return   Disposed = True   GC.SuppressFinalize(Me)   ' Clean up here End Sub 

Consumers can call Dispose if there is something specific that must be cleaned up before the garbage collector gets around to it. By using a Boolean you can prevent the Dispose method from running a second time, and since you are cleaning up specifically you can tell the garbage collector that it will no longer need to run Finalize for the object. In the case of a form, the Form class (as well as many other controls) already implements a Dispose method. If you want to extend the behavior of a form's Dispose method, you will need to overload the Dispose method. Assuming the parent class already has a Dispose method, you can overload it and call the parent method as follows:

 Private Disposed As Boolean = False Public Overloads Sub Dispose()   MyBase.Dispose()   If(Disposed) Return   Disposed = True   GC.SuppressFinalize(Me)   ' Clean up here End Sub 

The revision adds the Overloads keyword and invokes the MyBase.Dispose method. It is up to the parent class to prevent its Dispose behavior from running more than one time, so you just call SuppressFinalize in your Dispose method.

If you want consumers to know that your class implements a Dispose method, you can implement the IDisposable interface. IDisposable defines one method, Dispose . If the IDisposable interface is implemented by a parent class, you do not need to introduce the Implements statement a second time in the child class. (Chapter 2 will clarify the discussion on inheritance and implementing interfaces if it is new to you.)

The mechanics of implementing the IDisposable interface are demonstrated in the code fragment below.

 Public Class MyDisposableClass   Implements IDisposable   Public Sub Dispose() Implements IDisposable.Dispose   End Sub End Class 

The Implements statement indicates that we will be implementing the methods defined by the named interface, and the Implements clause after the Dispose method completes the contract between this class and the IDisposable interface.

Using Events

Whether you use the WithEvents statement or work with events using AddHandler and RemoveHandler , you are using delegates. Events are supported by a new class called Delegate in .NET. Delegates are completely new and introduce many powerful concepts. In this section I will briefly demonstrate how to add an event member to a class or structure. (Chapter 3 contains a complete discussion of the new Delegate idiom in VB .NET.)

To add an event member to a class or structure, use the Event keyword, followed by the name and the method signature that event handlers must have. ( Method signature refers to the type, number, and order of arguments.) For example, suppose we wanted to track the location of StickMan. We could implement a Moved event that consumers could handle and receive notification of StickMan's location. Listing 1.1 demonstrates the conventional technique for adding events to classes and structures.

Listing 1.1 Convention for Implementing Events in Classes and Structures
 1:  Public Class StickMan 2: 3:    ' Other code 4: 5:    Public Event Moved(ByVal X As Integer, ByVal Y As Integer) 6: 7:    Private Sub OnMoved(ByVal X As Integer, ByVal Y As Integer) 8:      RaiseEvent Moved(X, Y) 9:    End Sub 10: 11:   Private Sub Move(ByVal Y As Integer, ByVal X As Integer) 12:     ChangeRow(Y) 13:     ChangeColumn(X) 14:     OnMoved(X, Y) 15:     FOwner.Refresh() 16:  End Sub 17: 18: End Sub 

Line 5 introduces the event named Moved . Its signature is a subroutine that takes two Integer arguments. By convention we raise events in a procedure with an On prefix and the same root as the event. Thus a Moved event would be raised from an OnMoved subroutine. Wherever we want to raise the Moved event we would invoke OnMoved , as demonstrated in line 14.

Now let's look at the way we would handle events. For now we will use the VB6 WithEvents statement. (In Chapter 3 we will explore the revised way of handling events.)

Assuming a consumer wanted to handle Moved , we would need to tell the consumer about the event. We do this by introducing the reference in a WithEvents statement. The StickMan.sln sample application tracks the location of StickMan by handling Moved . The additional code appears next.

 WithEvents Man As ClassBasics.StickMan Private Sub Man_Moved(ByVal X As Integer, _   ByVal Y As Integer) Handles Man.Moved   Text = String.Format("StickMan Demo - X: {0}, Y: {1}", X, Y) End Sub 

The WithEvents statement introduces the variable Man and adds the list of events associated with the Man reference to the Method Name combobox in the code editor. You can code the handler manually or use the code editor to automatically generate the handler. (Refer to Figure 1.4 for a visual guide.) To generate the Moved handler using the code editor, select the Man reference from the Class Name combobox (shown near the top left of Figure 1.4), and pick the event name from the Method Name combobox (shown near the top right of Figure 1.4).

Figure 1.4. Use the Class Name and Method Name comboboxes to generate event handlers in VB .NET.

graphics/01fig04.gif

TIP

Events are a good way to enforce loose coupling. As you can infer from the StickMan.Moved event, StickMan has no knowledge of the objects receiving the Moved event. Using events in this way allows you to build classes without foreknowledge of consumer classes.

Whenever StickMan.OnMoved is invoked, the event handler will be called, and in our implementation, the location of the StickMan will be displayed in the Text property.

Using Access Modifiers

Access modifiers (also referred to as access specifiers ) are keywords that describe who has access to members of types. There are five access modifiers in VB .NET: Public , Protected , Friend , Protected Friend , and Private . Each modifier allows a specific level of access to the thing it modifies.

Public access is the least restrictive access. Like public restrooms, anybody has access. If you have ever visited most gas station restrooms, you know this is both a good thing and a bad thing. Public restrooms are good when you need to use one, but you certainly don't want a public restroom in your house. In the VB .NET context, public access means both consumers and producers can access the element adorned with the Public keyword.

Private access is the opposite of public access. Private access means only the producer can access elements adorned with the Private modifier. This means the author of a type is the only person who can access private elements in that type. Like first dates, most things are best kept private. Where types are concerned , making just a few public makes your types easier to use. Use too many public elements and your classes will seem schizophrenic.

The Protected modifier is used in inheritance relationships. Protected means private to consumers and public to generalizers. A generalizer is a consumer who subclasses, or creates child classes from, your class. Make members of types protected if you think a generalizer may want to extend the behavior of a member. Because we are talking about behaviors, the implication is that we are talking about methods. Generally this is true. Virtual methods should be protected. Keep fields private.

The Friend and Protected Friend modifiers are used less often. Elements modified by Friend are accessible only within the assembly in which they are defined. The Protected Friend modifier is a combination of Friend and Protected . Only generalizers within the same assembly can access Protected Friend elements.

Several general rules are inferred by specific modifiers. Here is an overview of some of the key implications.

  • Classes are generally public. Nested classes are more often protected or private.

  • Fields should almost always be private.

  • Properties are almost always public, but sometimes they are protected.

  • Methods are found using all modifiers. Keep the number of public methods to a minimum, perhaps less than a half- dozen per type.

  • Events are most often public.

  • The members of an interface are always public, and you cannot explicitly adorn method declarations in an interface with any modifier.

  • Do not rely on default access. Always adorn every element of a type with the access you want it to have rather than relying on default access.

If you find yourself providing many public members of classes and structures, determine whether there is a natural subdivision; if so, split the type into more than one type. Otherwise it is best to be explicit and intentional about the access modifier you apply to every member.



Visual Basic. NET Power Coding
Visual Basic(R) .NET Power Coding
ISBN: 0672324075
EAN: 2147483647
Year: 2005
Pages: 215
Authors: Paul Kimmel

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