5.4 Interfaces in .NET

only for RuBoard

The .NET Framework class library doesn't contain much in the way of interfaces, but every object designer should be familiar with a few of them. IDisposable is probably the most important because it plays an important role in garbage collection. Comparing and copying objects are also relatively common tasks ; therefore, you should look at IComparable and ICloneable . ISerializable (discussed in Chapter 8) converts an object to a stream to persist it to disk or transport it across a network. Undoubtedly, though, constructing collections is one of the most essential programming tasks used in building object hierarchies. In .NET, collections are built by using IEnumerable and IEnumerator . These two interfaces allow your objects to be exposed through a For...Each loop, among other things.

5.4.1 IDisposable

As you have seen, the lifespan of an object is nondeterministic; there is no way to know when an object will be freed. At some given point, the garbage collector (GC) decides that an object is no longer in use and reclaims its memory. If the object has a Finalize method, the GC calls it; it acts as a rough analog to a destructor.

This scenario creates two problems. First, calling Finalize is expensive. Garbage collection runs twice for these objects. On the first pass, the GC puts all objects that implement Finalize into the finalization queue and frees everything else. On the second pass, it must call Finalize for all objects in the queue, and only then is the memory released. Objects with finalizers take longer to be freed.

The second problem occurs in situations when a class wraps a limited resource, such as a file, a window, or, more commonly, a database connection. Best practice says that you should acquire a resource, use it, and free it as quickly as possible so you don't run out of resources. There are three varieties of resource wrappers :

  • A class that holds both unmanaged and managed resources

  • A class that holds only unmanaged resources

  • A class that holds only managed resources

Managed resources are contained within .NET. They include such things as database connections and sockets and are defined in the .NET class library. Unmanaged resources exist outside of the framework. COM objects are one example. Another is a handle obtained from Win32, a device context (HDC) perhaps.

If at all possible, avoid using a Finalize method. If you can, write your classes so that the resource is used and then released. This is not always possible, however. If a held resource is used in several places in an object, the cost of obtaining the resource could far outweigh the overhead incurred from a call to Finalize . The solution is simpleprovide your own cleanup method. Implementing this method, though, is not straightforward.

There is actually an established pattern in .NET for handling cleanup situations. A resource wrapper first needs to implement IDisposable , which is defined in the System namespace. As Example 5-4 shows, this interface contains one method, Dispose .

Example 5-4. IDisposable
 Imports System     'Defined in System 'Public Interface IDisposable '    Sub Dispose( ) 'End Interface         Public Class ResourceBase   Implements IDisposable     End Class 

The class shown in Example 5-5 holds two limited resources. The first is an instance of OleDbConnection , which is a database connection. The second is a file handle obtained from calling the Win32 API CreateFile function. Both are obtained from the constructor and are used in several places within the class. The interop code for making Win32 API calls was left out for brevity's sake.

Example 5-5. Implementing IDisposable, Part I
 Public Class ResourceBase   Implements IDisposable       Private conn As OleDbConnection   Private connString As String   Private hFile As IntPtr       Public Sub New(ByVal connString As String)     Me.connString = connString     InitResources( )   End Sub       Private Sub InitResources( )         Me.conn = New OleDbConnection(Me.connString)     Me.conn.Open( )         Me.hFile = CreateFile("data.txt", _                           GENERIC_WRITE, _                           0, _                           Nothing, _                           OPEN_ALWAYS, _                           FILE_ATTRIBUTE_NORMAL, _                           Nothing)   End Sub       'IDisposable implementation here     End Class 

Classes that implement IDisposable place the responsibility of cleanup in the hands of the user . Dispose must be called directly; it's not as elegant as a destructor, which is called automatically when an object goes out of scope, but that's the price you pay for managed memory. The good news is that all the classes in .NET do the same thing, so you should get used to it quickly.

If an object implements IDisposable , it should have a Finalize method, too. Finalize acts as a backup plan if Dispose is not called. Should this happen, any resources that might be lying around can be freed when the garbage collector fires up.

If Dispose is called, finalization should be canceled for the object. This cancelation prevents the cleanup code from being called twice and keeps well-behaved code from incurring the Finalize call's performance penalty.

Example 5-6 shows a common pattern for implementing Dispose . Notice how GC.SuppressFinalize is called to cancel finalization for the class when Dispose is called.

Example 5-6. Implementing IDisposable, Part II
 Public Class ResourceBase   Implements IDisposable       'Other methods       Private disposed As Boolean = False       Public Sub Dispose( ) Implements IDisposable.Dispose     Dispose(True)     GC.SuppressFinalize(Me)   End Sub       Protected Overridable Sub Dispose(ByVal disposing As Boolean)     If Not (Me.disposed) Then       Me.disposed = True       If (disposing) Then         'Free managed resources here         Me.conn.Close( )       End If       'Free unmanaged resources here       CloseHandle(Me.hFile)     End If   End Sub       Public Overrides Sub Finalize( )     Dispose(False)   End Sub     End Class 

For now, ignore the fact that Dispose is overloaded. Just understand that two things that can happen: a client can call Dispose like it should, or Finalize can do all the dirty work. In either case, the drudgery is delegated to the protected version of Dispose .

A private flag in the class determines whether the class is already disposed. If it isn't, the resources held by the class are released. Beyond this point, subtleties are all that matter. If disposing is True , which is the case when IDisposable.Dispose is called, the database connection is closed.

Finalize calls Dispose with a False argument. In this case, the database connection is not closed because conn is a managed object; it is an instance of OleDbConnection (defined in the System.Data.OleDb namespace). If a client fails to call ResourceBase.Dispose , two objects will end up in the finalization queue: ResourceBase and OleDbConnection .

When the garbage collector frees the memory associated with these objects, the order of their release is not guaranteed . If OleDbConnection is freed first, conn will no longer refer to a valid object when ResourceBase is finalized. Then when ResourceBase.Dispose is called, an exception will be thrown.

The disposing parameter determines whether Dispose is called or if the object is finalized. In the first case, you know the reference to conn is still valid, so you can call conn.Close (which essentially calls the OleDbConnection implementation of IDisposable ) and free it. In the latter instance, it is best to let the GC deal with both objects on its own time.

All methods in a resource wrapper should be aware of the disposed flag and handle the situation accordingly . If the object was disposed, you should throw an exception, as shown in the following code:

 Public Class ResourceBase         Private disposed As Boolean = False         Public Sub ResourceMethod( )  If Me.disposed Then   Throw New ObjectDisposedException(...)   End If  'Continue              End Sub     End Class 

In addition to implementing IDisposable , you can expose a Close method. In the framework itself, many classes expose this method. When a class has a Close method, it implies that it can be reused. The OleDbConnection class from the example has a Close method. Once Close is called, the connection can be reopened by calling Open . While Close is standard across the framework, though, the implementer determines how an object is reused; a corresponding Open method is not required.

Writing a Close method can be as simple as routing the call to IDisposable.Dispose :

 Public Sub Close( )     Dispose( ) End Sub 

However, consider the following when your object is reused in this type of situation. When Dispose is called, finalization is suppressed. What happens if you close an object, reopen it, and forget to call Dispose ? The object will not be finalized. If you reuse an object, you need to make sure that finalization will occur. You can do this by calling GC.ReRegisterForFinalize .

Example 5-5 shows that the constructor calls a private function known as InitResources . Usually, a constructor needs to share code that is used to reopen an object:

 Public Sub New(connString As String)     Me.connString = connString     InitResources( )     End Sub     Public Sub Open(connString As String)     Me.connString = connString     InitResources( )  GC.ReRegisterForFinalize(Me)  End Sub 

The protected version of Dispose exists for the benefit of derived classes, as shown in Example 5-7. In the derived class, Dispose is overridden and implemented in a manner similar to the base class. The only difference is that before the derived version exits, MyBase.Dispose is called. IDisposable is already implemented in the base class. Reimplementing the interface is unnecessary.

Example 5-7. Deriving from classes that implement IDisposable
 Public Class ResourceDerived : Inherits ResourceBase       Private disposed As Boolean = False       Public Sub New(ByVal connString As String)     MyBase.New(connString)   End Sub       Protected Overloads Overrides Sub Dispose(ByVal disposing As Boolean)         If Not (Me.disposed) Then       Me.disposed = True       If (disposing) Then         'Free managed resources here       End If           'Release unmanaged resources here           'Call Dispose in base class       MyBase.Dispose(disposing)     End If   End Sub     End Class 

You do not need to override Finalize . The base class implementation you inherited is suitable. Dispose is a virtual function. Even though Finalize is called from the base class, ResourceDerived.Dispose will be called. If ResourceDerived needs to encapsulate additional resources, it is free to do so, and the pattern maintains its integrity.

This pattern for implementing Dispose in this chapter is not thread-safe. A second object could call IDisposable.Dispose before the disposed flag was set to True . If you find that multiple threads call Dispose , you should consider a redesign.

By calling Dispose , you imply ownership of the object. If you don't own an object, you shouldn't call it. If you do have ownership, then you can call it, but make sure no one else does. There can be only one owner.

One of the drawbacks to the previous Dispose pattern is that the parameterized version of Dispose needs to be reimplemented for every derived class. The pattern works well and is recommended in the .NET documentation, but it is not the only available option. An alternative, lightweight pattern (one of many, no doubt) allows Dispose to be implemented once in the base class.

Start with the BaseResource class from the previous Dispose pattern and add the following two methods:

 Overridable Protected Sub DoDispose( )     'Do nothing here - this will be overriden in the subclass End Sub     Protected Sub CheckDispose( )     If (disposed) Then         'Throw exception here     End If End Sub 

Remove the Overrides modifier from the parameterized Dispose method; this method no longer needs to be virtual because it will be implemented only once in the base classbut it can still be called from derived classes because it remains protected.

Now, instead of freeing managed resources, call the new DoDispose method instead. Also, remove any code that frees unmanaged resources. The method should now look like this:

 Protected Sub Dispose(ByVal disposing As Boolean)     If Not disposed Then         disposed = True         'If this is a call to Dispose, dispose all managed resources         If (disposing) Then             DoDispose( )    'Should be overridden in subclass         End If     End If End Sub 

The entire listing for the alternative ResourceBase class is shown in Example 5-8.

Example 5-8. Alternative IDisposable pattern
 Imports System     Public Class ResourceBase   Implements IDisposable       Private disposed As Boolean = False       Protected Overridable Sub DoDispose( )     'Do nothing here - this will be overriden in the subclass   End Sub       Public Sub Dispose( ) Implements IDisposable.Dispose     Dispose(True)     GC.SuppressFinalize(Me)   End Sub       Protected Sub Dispose(ByVal disposing As Boolean)     If Not (Me.disposed) Then       Me.disposed = True       If (disposing) Then         DoDispose( )        'Should be overridden in subclass       End If     End If   End Sub       Protected Sub CheckDispose( )     If (disposed) Then       'Throw exception here     End If   End Sub       Protected Overrides Sub Finalize( )     Dispose(False)   End Sub     End Class 

As demonstrated in Example 5-9, the derived class no longer needs to implement a Dispose method. However, it overrides the DoDispose method that was added to the base class. This override frees resources allocated by the derived class, regardless of whether or not they are managed; no distinction is made.

Example 5-9. Alternative ResourceDerived class
 Public Class ResourceDerived : Inherits BaseResource       'Unmanaged resource   Private handle As IntPtr       'Managed resource   Private Components As Component       Public Sub New( )     'Constructor       End Sub       Protected Overrides Sub DoDispose( )     Components.Dispose( )     Release(handle)     handle = IntPtr.Zero   End Sub       Public Sub DoSomething( )         CheckDispose( )  'Make sure object is still alive         'Do something important       End Sub     End Class 

Every public method in the class begins with a call to CheckDispose (which also is implemented once in the base class), which determines whether or not the object is still alive. If it was disposed, an exception is thrown from the base class.

5.4.2 IComparable

Objects wishing to take advantage of Array. Sort or Array.BinarySearch ( System.Array is the base class for all arrays in the runtime) can implement the IComparable interface. The interface defines a single method named CompareTo and looks like this:

 Public Interface IComparable     Function CompareTo(ByVal obj As Object) As Integer  End Interface 

The return value of CompareTo indicates how obj relates to the object that actually implements the interface. Possible values are shown in Table 5-1.

Table 5-1. CompareTo return values

Return value

Relationship

< 0

x is less than y .

= 0

x equals y .

> 0

x is greater than y .

How this method is implemented is arbitrary; it all depends on the object's needs. Example 5-10 contains an unembellished listing for the MuscleCar class. You could implement IComparable to base sorting on the car's horsepower.

Example 5-10. The MuscleCar class
 Imports System     Public Class MuscleCar       Public ReadOnly year, make, model As String   Public ReadOnly horsePower As Short       Public Sub New(ByVal year As String, _                  ByVal make As String, _                  ByVal model As String, _                  ByVal horsePower As Short)     Me.year = year     Me.make = make     Me.model = model     Me.horsePower = horsePower   End Sub     End Class 

First, specify that the class will implement the IComparable interface:

 Imports System  Public Class MuscleCar  Implements IComparable 

Next, the CompareTo method must be added to the class. This implementation is interesting because the horsepower of the MuscleCar is stored as a Short , and the Short data type also implements IComparable . This means that the call can be forwarded to the Short implementation, saving you a few lines of code:

 Public Function CompareTo(ByVal obj As Object) As Integer _     Implements IComparable.CompareTo         'Cast obj to a MuscleCar     Dim otherCar As MuscleCar = CType(obj, MuscleCar)     Return Me.horsePower.CompareTo(otherCar.horsePower)          End Function 

Given two instances of a MuscleCar , you can now compare them:

 Dim car1 As MuscleCar = New MuscleCar("1966", "Shelby", "Cobra", 425) Dim car2 As MuscleCar = New MuscleCar("1969", "Pontiac", "GTO", 370)     Console.WriteLine(car1.CompareTo(car2)) 

The result is 55 , the difference of 425 and 370 , meaning car1 is "greater" than car2 (no flames, please ).

You can also sort the class by calling Array.Sort . First, though, you must override the ToString method so that you can print the sort results to the console in a meaningful way. Simply add the following method to MuscleCar :

 Overrides Public Function ToString( ) As String     Return String.Format( _         "MuscleCar - Year:{0} Make:{1} Model:{2} HP:{3}", _         Me.year, Me.make, Me.model, Me.horsePower) End Function 

You can now test the sorting by declaring a few different instances of MuscleCar and calling Array.Sort , which is a shared method:

 Friend Class Test     Public Shared Sub Main( )         Dim car As MuscleCar         Dim cars(3) As MuscleCar         cars(0) = New MuscleCar("1970", "Plymouth", "Hemi-Cuda", 425)         cars(1) = New MuscleCar("1966", "Shelby", "Cobra", 425)         cars(2) = New MuscleCar("1969", "Pontiac", "GTO", 370)         cars(3) = New MuscleCar("1969", "Chevy", "Chevelle SS", 375)  Array.Sort(cars)  For Each car In Cars             Console.WriteLine(car.ToString( ))         Next car     End Sub End Class 

After the array is sorted, you can write each element to the console window (thanks to the ToString override). The last two cars have the same horsepower. In this case, the order is determined by the position of the cars in the array:

 C:\>muscle MuscleCar - Year:1969 Make:Pontiac Model:GTO HP:370 MuscleCar - Year:1969 Make:Chevy Model:Chevelle SS HP:375 MuscleCar - Year:1970 Make:Plymouth Model:Hemi-Cuda HP:425 MuscleCar - Year:1966 Make:Shelby Model:Cobra HP:425 

5.4.3 ICloneable

Implementing ICloneable does not give your object the ability to take advantage of amazing .NET features. The interface just exists to provide a standard mechanism for making a copy of an object. ICloneable contains a single method, Clone , that returns a copy of the object:

 Public Interface ICloneable     Function Clone( ) As Object End Interface 

Clone can provide a deep or shallow copythe choice is yours. Deep copies are exact copies of the original object, including private member state. A shallow copy is just a reference to the original object; there is still only one instance after the call.

A deep copy is a little more difficult to implement if the object you copy is part of a containment relationship with other objects; all contained objects need to be copied , too. The section Section 8.3.2.1 in Chapter 8 covers this topic in more depth.

Example 5-11 shows a simple deep-copy implementation for the MuscleCar class. The class contains no additional objects or state variables beyond what is passed in to the constructor, so you only need to create a new object and return it.

Example 5-11. ICloneable implementation for MuscleCar
 Imports System     Public Class MuscleCar   Implements ICloneable       Public ReadOnly year, make, model As String   Public ReadOnly horsePower As Short       Public Sub New(ByVal year As String, _                  ByVal make As String, _                  ByVal model As String, _                  ByVal horsePower As Short)     Me.year = year     Me.make = make     Me.model = model     Me.horsePower = horsePower   End Sub       Public Function Clone( ) As Object Implements ICloneable.Clone     Return New MuscleCar(Me.year, Me.make, Me.model, Me.horsePower)   End Function     End Class 

You could implement Clone by using Return Me , but doing so is not in the spirit of the interface. Me simply returns a reference to the existing object. Instead, you should have two distinct objects when the call returns. In a shallow copy, though, the object and its clone can share object references for any contained items.

5.4.4 IEnumerable and IEnumerator

The System.Collections namespace contains classes and interfaces that facilitate most of the common, collection-oriented tasks such as stacks, lists, queues, and dictionaries. At its heart lies the IEnumerable interface, which provides iteration support for all collections. This support makes it possible to use the For...Each language construct to access members of a collection. Even common arrays support collection-like access as a result of this interface (all arrays are derived from System.Array , which implements IEnumerable ).

IEnumerable contains a single method:

 Public Interface IEnumerable   Function GetEnumerator( ) As IEnumerator End Interface 

GetEnumerator has one job: to return an object that provides access to the items in the collection. This object, usually called an enumerator, must implement the IEnumerator interface:

 Public Interface IEnumerator     Function MoveNext( ) As Boolean     ReadOnly Property Current( ) As Object     Sub Reset( ) End Interface 

Every call to GetEnumerator should return a new object that enumerates the collection in its current state. Calling GetEnumerator , adding items to the collection, and calling GetEnumerator a second time should result in two enumeratorseach with a different idea about what the collection contains. The first enumerator does not know about the additional items added to the collection.

The framework's collection classes contain a private member named _version (disassemble System.Collections.Stack to see it) that is incremented every time the collection is modified. The enumerator is returned from GetEnumerator and is passed this version number and a reference to (rather than a copy of) the items in the collection. Whenever the collection is accessed, the enumerator's version is compared to the current version of the collection. If they are different, then an InvalidOperationException is thrown.

For example, consider a primitive, three-dimensional object that contains a collection of points describing it, as shown in Example 5-12.

Example 5-12. Implementing IEnumerable
 Imports System Imports System.Collections     Public Structure ThreeDPoint   Public x, y, z As Single   Public Overrides Function ToString( ) As String     Return String.Format( _     "x={0},y={1},z={2}", x.ToString( ), y.ToString( ), z.ToString( ))   End Function End Structure     Public MustInherit Class ThreeDObject   Implements IEnumerable       Private myPoints( ) As ThreeDPoint       Public Function GetEnumerator( ) As IEnumerator _       Implements IEnumerable.GetEnumerator     Return New ThreeDEnumerator(myPoints)   End Function       Protected WriteOnly Property Points( ) As ThreeDPoint( )     Set(ByVal p( ) As ThreeDPoint)       myPoints = p     End Set   End Property     End Class 

Internally, the points that comprise the object are maintained in an array. These points will be initialized in a derived class because 3-D objects like cubes, spheres, and pyramids can contain any number of points. The protected Points property passes the initialized point from the subclass back to ThreeDObject . There is no need to reimplement an interface that was implemented in a base class:

 Public Class Pyramid : Inherits ThreeDObject        '5-sided pyramid - 3 walls and a bottom       Private myPoints(3) As ThreeDPoint        Public Sub New( )     'Initialize points to pyramid shape then pass to      'protected property in base class     Points = myPoints   End Sub     End Class 

Implementing GetEnumerator is straightforward. It merely creates a new instance of ThreeDEnumerator (shown in Example 5-13), passing a reference to the ThreeDPoint array. It is possible to initialize a new enumerator with a copy of the elements in the array instead of passing the array by reference, but there would be a performance hit: one from the copy and another for all duplicated items in memory. Making a copy of all the items in a collection for an enumerator is preferable only when the collection contains a small number of items that need to reflect the exact state of the collection at any given time.

Example 5-13. Implementing IEnumerator
 Public Class ThreeDEnumerator   Implements IEnumerator       Private myPoints( ) As ThreeDPoint   Private index As Integer       Public Sub New(ByVal p( ) As ThreeDPoint)     myPoints = p     index = -1   End Sub       Public ReadOnly Property Current( ) As Object _       Implements IEnumerator.Current     Get       If (index < 0) OrElse (index = myPoints.Length) Then         Throw New InvalidOperationException( _             "Collection index is out of bounds!")       End If       Return myPoints(index)     End Get   End Property       Public Function MoveNext( ) As Boolean _       Implements IEnumerator.MoveNext     If (index < myPoints.Length) Then       index += 1     End If     Return Not (index = myPoints.Length)   End Function       Public Sub Reset( ) Implements IEnumerator.Reset     index = -1   End Sub     End Class 

When the constructor for ThreeDEnumerator is called, a reference to the myPoints array is stored as private data and the current index of the array is set to -1 . This step is important. The initial position of the collection cursor should be before the first element, which is element (arrays are -based). This position allows the collection to be accessed easily from a While loop without having to first check to see if the collection contains any items:

 'First call to MoveNext moves cursor to 'the first item (if it exists)     While (myEnumerator.MoveNext( ))     'Get item from collection here End While 

After minimal validation, Current returns element index from the myPoints array. MoveNext checks to see if index is less than the number of points in the array and increments it if it is still within the bounds of the array. Reset sets the index back to -1 .

You can access the collection in several ways. The first is a strictly generic technique that uses only interfaces:

 Friend Class Test          Public Shared Sub Iterate(ByVal enumerable As IEnumerable)         Dim enumerator As IEnumerator = enumerable.GetEnumerator( )         While (enumerator.MoveNext( ))             Dim p As Object             p = enumerator.Current             Console.WriteLine(p.ToString( ))         End While     End Sub         Public Shared Sub Main( )         Dim myPyramid As New Pyramid( )         Iterate(myPyramid)     End Sub      End Class 

This technique is a great example of polymorphism at work. As you can see, the Iterate function can enumerate any collection, whether it is a ThreeDObject , an array, stack, or queue. The only requirement is that the object implement IEnumerable .

You can also declare a specific type that implements IEnumerable :

 Friend Class Test     Public Shared Sub Main( )         Dim pmid As New Pyramid( )         Dim myEnumerator As IEnumerator = pmid.GetEnumerator( )                  While (myEnumerator.MoveNext)             Dim p As Object             p = myEnumerator.Current             Console.WriteLine(p.ToString( ))         End While     End Sub End Class 

Of course, doing so is definitely more abrasive than just using For...Each :

 Friend Class Test     Public Shared Sub Main( )         Dim pmid As New Pyramid( )         Dim p As ThreeDPoint                          For Each p In pmid             Console.WriteLine(p.ToString( ))         Next p     End Sub End Class 
5.4.4.1 Performance issues

This implementation has two major performance problems. The collection contains value types ( ThreeDPoint structures), but enumerators are designed to work with reference types. For instance, before Current can return a ThreeDPoint , it must box it. This means that an object is allocated from the managed heap and the value of the ThreeDPoint is copied into this object. Not only will this object need to be garbage collected, but when the collection is iterated, the object will be unboxedthe values from the object will be copied back to a ThreeDPoint structure. Thus, for every element in the array, you end up copying memory twice and are left with an object on the managed heap waiting to be garbage collected.

There are a few things that can be done to make this collection (and others) work with value types without incurring performance penalties. Start with the ThreeDObject class and add the following method:

 'This is not apart of IEnumerator Public Function GetEnumerator( ) As ThreeDEnumerator     Return New ThreeDEnumerator(myPoints) End Function 

This example is a new version of GetEnumerator . Don't be confused by it. This method is not an implementation of IEnumerator.GetEnumerator ; it's just a method with the same name . The only difference between this method and IEnumerator.GetEnumerator is the fact that it returns an instance of ThreeDEnumerator instead of a reference to IEnumerator . Adding this method causes a naming conflict with IEnumerator.GetEnumerator (methods can't be overloaded on return type alone), so the interface implementation needs to be renamed . Any name will do:

 Public Function OldGetEnumerator( ) As IEnumerator _     Implements IEnumerable.GetEnumerator     Return GetEnumerator( ) End Function 

Don't worry about the method name being different from the interface name. The method still implements IEnumerable.GetEnumerator , so the following code still works:

 Dim enumerable As IEnumerable = New Pyramid( )     'This will call OldGetEnumerator  Dim enumerator As IEnumerator = enumerable.GetEnumerator( )  

If you call the method directly, you will have to refer to it by its new name. Example 5-14 contains the new listing for ThreeDObject .

Example 5-14. Implementing collections for value types
 Public MustInherit Class ThreeDObject   Implements IEnumerable       Private myPoints( ) As ThreeDPoint       Public Function OldGetEnumerator( ) As IEnumerator _       Implements IEnumerable.GetEnumerator     Return GetEnumerator( )   End Function       Public Function GetEnumerator( ) As ThreeDEnumerator     Return New ThreeDEnumerator(myPoints)   End Function       Protected WriteOnly Property Points( ) As ThreeDPoint( )     Set(ByVal p( ) As ThreeDPoint)       myPoints = p     End Set   End Property     End Class 

You need to make a couple of modifications to the ThreeDEnumerator class to finish off the optimization. First, change Current to return a ThreeDPoint instead of an Object , and then remove the Implements statement:

  Public ReadOnly Property Current( ) As ThreeDPoint  Get         If (index < 0) OrElse (index = myPoints.Length) Then             Throw New InvalidOperationException(_             "Collection index is out of bounds!")         End If         Return myPoints(index)     End Get End Property 

Add a new implementation of IEnumerator.Current that calls the type-specific version. As in ThreeDObject , this method must be renamed to avoid a conflict at compile time. Again, any name will be fine:

 Public ReadOnly Property  OldCurrent  ( ) As Object _     Implements IEnumerator.Current     Get        Return Me.Current  'Call non-interface version     End Get End Property 

If the collection is accessed with a For . . . Each loop, the GetEnumerator method for the class is called before the IEnumerable implementation. If you do not need to access your collection in a generic manner, all you need is a GetEnumerator method. You don't need to implement IEnumerable at all, but you are advised to do so. The same holds true for the enumerator object. As long as the enumerator has a Current method and a MoveNext method, the For...Each loop will compile successfully. However, these interfaces should be implemented regardless. Remember that the noninterface versions of GetEnumerator , Current , and MoveNext are called first in a For . . . Each construct. Example 5-15 contains the new listing for ThreeDEnumerator .

Example 5-15. Implementing an enumerator to handle value types
 Public Class ThreeDEnumerator   Implements IEnumerator       Private myPoints( ) As ThreeDPoint   Private index As Integer       Public Sub New(ByVal p( ) As ThreeDPoint)     myPoints = p     index = -1   End Sub       Public ReadOnly Property ICurrent( ) As Object _       Implements IEnumerator.Current     Get       Return Me.Current     End Get   End Property       Public ReadOnly Property Current( ) As ThreeDPoint     Get       If (index < 0) OrElse (index = myPoints.Length) Then         Throw New InvalidOperationException( _         "Collection index is out of bounds!")       End If       Return myPoints(index)     End Get   End Property       Public Function MoveNext( ) As Boolean _       Implements IEnumerator.MoveNext     If (index < myPoints.Length) Then       index += 1     End If     Return Not (index = myPoints.Length)   End Function       Public Sub Reset( ) Implements IEnumerator.Reset     index = -1   End Sub     End Class 

If the collection is accessed generically through interfaces only, the actual implementations of the interfaces are called. However, since those implementations delegate to the noninterface versions ( OldGetEnumerator and OldCurrent ), no boxing will take place.

only for RuBoard


Object-Oriented Programming with Visual Basic. Net
Object-Oriented Programming with Visual Basic .NET
ISBN: 0596001460
EAN: 2147483647
Year: 2001
Pages: 112
Authors: J.P. Hamilton

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