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.
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 .
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.
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.
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.
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.
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.
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.
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.
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.
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
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.
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.
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.
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.
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
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 .
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 .
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 |