The .NET Framework exposes much standard functionality through generic interfaces, which are implemented in various combinations by classes in the Framework itself, and which can also be implemented by your own classes in order to tap into standard functionality defined by the Framework. In this section we will look at several categories of operations that are supported by these standard, generic interfaces. -
Collections -
Copying objects -
Comparing objects Our survey of generic interfaces is by no means exhaustive, but our sampling should give you a good understanding of how generic interfaces work in the .NET Framework. Collection Interfaces Now that we understand the concept of interfaces, we are equipped to take a closer look at collections, and in particular at the ArrayList class that we have used so heavily in the case study. If we look at the definition of ArrayList , we see that it implements four standard interfaces. Public Class ArrayList Implements IList, ICollection, IEnumerable, ICloneable The first three interfaces form a simple interface hierarchy, as shown in Figure 6-1. As you go down the hierarchy, additional methods are added until IList specifies a fully featured list. Figure 6-1. Interface hierarchy for lists. The fourth interface, ICloneable , is independent and is used to support deep copying. As a simple illustration of the collection interfaces, we provide the program StringList . Here is the Main method. We'll look at the individual helper methods as we examine the various collection interfaces. ' StringList.vb Imports System Imports System.Collections Module StringList Private m_list As ArrayList Sub Main() ' Initialize strings and show starting state m_list = New ArrayList(4) ShowCount() AddString("Amy") AddString("Bob") AddString("Charlie") ShowEnum(m_list) ' enumerator ShowCount() ' Add two more string and show state again AddString("David") AddString("Ellen") ShowList(m_list) ' for each ShowCount() ' Remove two strings from list and show state RemoveString("David") RemoveAt(0) ShowArray(m_list) ' index notation ShowCount() ' Try to remove two strings not in list RemoveString("Amy") RemoveAt(3) End Sub ... Here is the output: list.Count = 0 list.Capacity = 4 Amy Bob Charlie list.Count = 3 list.Capacity = 4 Amy Bob Charlie David Ellen list.Count = 5 list.Capacity = 8 array[0] = Bob array[1] = Charlie array[2] = Ellen list.Count = 3 list.Capacity = 8 List does not contain Amy No element at index 3 Interface Documentation Predefined interfaces are documented in the online .NET Framework SDK documentation. Figure 6-2 illustrates the documentation of the IEnumerable interface. The right-hand pane has a language filter button , which we have used to show only VB.NET versions. If you are using the interface in one of the .NET Framework classes that implement the interface, you do not need to implement any of the methods yourself, since they are implemented for you. If you are creating your own class that supports an interface, you must provide implementations of all the methods of the interface. In either case, the documentation describes the interface methods for you. Figure 6-2. .NET Framework SDK documentation for IEnumerable interface. Ienumerable and Ienumerator The basic interface that must be supported by collection classes is IEnumerable , which has a single method, GetEnumerator . Public Interface IEnumerable Function GetEnumerator() As IEnumerator End Interface GetEnumerator returns an interface reference to IEnumerator , which is the interface used for iterating through a collection. This interface has the read-only property Current and the methods MoveNext and Reset . Public Interface IEnumerator ReadOnly Property Current As Object Function MoveNext() As Boolean Sub Reset() End Interface The enumerator is initially positioned before the first element in the collection, and it must be advanced before it is used. The ShowEnum method (in the StringList example) illustrates using an enumerator to iterate through a list. Private Sub ShowEnum(ByVal array As ArrayList) Dim iter As IEnumerator = Array.GetEnumerator() Dim more As Boolean = iter.MoveNext() While (more) Dim str As String = iter.Current Console.WriteLine(str) more = iter.MoveNext() End While End Sub This pattern of using an enumerator to iterate through a list is so common that VB.NET provides a special kind of loop, known as For Each , that can be used for iterating through the elements of any collection. Here is the comparable code using For Each . Private Sub ShowList(ByVal array As ArrayList) Dim str As String For Each str In array Console.WriteLine(str) Next End Sub Icollection The ICollection interface is derived from IEnumerable and adds a Count property and a CopyTo method as well as IsSynchronized and SyncRoot properties. Public Interface ICollection Inherits IEnumerable ReadOnly Property Count As Integer ReadOnly Property IsSynchronized As Boolean ReadOnly Property SyncRoot As Object Sub CopyTo(ByVal array As Array, ByVal index As Integer) End Interface The synchronization properties IsSynchronized and SyncRoot can help you deal with thread safety issues. "Is it thread safe?" is a question frequently asked about library code. The short answer to this question for the .NET Framework class library is no. This does not mean that the designers of the Framework did not think about thread safety issues. On the contrary, there are many mechanisms to help you write thread-safe code when you need to. The reason that collections are not automatically thread safe is that your code should not have to pay the performance penalty to enforce synchronization when it is not running in a multithreading scenario. If you do need thread safety, you may use the thread-safety properties as required. We discuss the .NET mechanisms for thread synchronization in Chapter 10. Our StringList program illustrates use of the Count property of ICollection . The Capacity property is not defined in any interface, but is rather exposed by the ArrayList class itself. Private Sub ShowCount() Console.WriteLine("list.Count = {0}", m_list.Count ) Console.WriteLine(_ "list.Capacity = {0}", m_list.Capacity) End Sub IList The IList interface is derived from ICollection and provides methods for adding an item to a list, removing an item, and so on. A default parameterized property ( Item ) is provided that enables array notation to be used. (We discussed parameterized properties in Chapter 4.) Public Interface IList Inherits ICollection, IEnumerable ReadOnly Property IsFixedSize As Boolean ReadOnly Property IsReadOnly As Boolean Default Property Item(ByVal index As Integer) As Object Function Add(ByVal value As Object) As Integer Sub Clear() Function Contains(ByVal value As Object) As Boolean Function IndexOf(ByVal value As Object) As Integer Sub Insert(ByVal index As Integer, ByVal value _ As Object) Sub Remove(ByVal value As Object) Sub RemoveAt(ByVal index As Integer) End Interface Our StringList sample code illustrates using the indexer and the Add , Contains , Remove , and RemoveAt methods. Private Sub ShowArray(ByVal array As ArrayList) Dim i As Integer = 0 While i < array.Count Console.WriteLine("array[{0}] = {1}", i, array(i) ) i += 1 End While End Sub ... Private Sub AddString(ByVal str As String) If m_list.Contains(str) Then Throw New Exception("list contains " & str) End If m_list.Add(str) End Sub Private Sub RemoveString(ByVal str As String) If m_list.Contains(str) Then m_list.Remove(str) Else Console.WriteLine("List does not contain {0}", str) End If End Sub Private Sub RemoveAt(ByVal index As Integer) Try m_list.RemoveAt(index) Catch e As ArgumentOutOfRangeException Console.WriteLine("No element at index {0}", index) End Try End Sub Copy Semantics and ICloneable Sometimes you have to make a copy of an object. When you copy objects that contain objects and object references, you have to be aware of the nuances of copy semantics. We will compare reference copy, shallow memberwise copy, and deep copy. We will see that by implementing the ICloneable interface in your class, you can perform a deep copy. Recall that VB.NET has value types and reference types. A value type contains all its own data, while a reference type refers to data stored somewhere else. If a reference variable gets copied to another reference variable, both will refer to the same object. If the object referenced by the second variable is changed, the first variable will also reflect the new value. Sometimes you want this behavior, but sometimes you do not. Shallow Copy and Deep Copy A structure in VB.NET automatically implements a memberwise copy, sometimes known as a shallow copy . The Object root class has a protected method, MemberwiseClone , which will perform a memberwise copy of members of a class. If one or more members of a class are of a reference type, this memberwise copy may not be good enough. The result will be two data member references to the same data, not two independent copies of the data. To actually copy the data itself and not merely the data member references, you will need to perform a deep copy . Deep copy can be provided at either the language level or the library level. In C++ deep copy is provided at the language level through a copy constructor . In VB.NET deep copy is provided by the .NET Framework through a special interface, ICloneable , which you can implement in your classes in order to enable them to perform deep copy. Example Program We will illustrate all these ideas in the program CopyDemo . This program makes a copy of a Course instance. The Course class consists of a title and a collection of students. ' Course.vb Imports System Imports System.Collections Public Class Course Implements ICloneable Public Title As String Public Roster As ArrayList Public Sub New(ByVal titleText As String) Title = titleText Roster = New ArrayList() End Sub Public Sub AddStudent(ByVal name As String) Roster.Add(name) End Sub Public Sub Show(ByVal caption As String) Console.WriteLine("-----{0}-----", caption) Console.WriteLine(_ "Course : {0} with {1} students", _ Title, Roster.Count) Dim name As String For Each name In Roster Console.WriteLine(name) Next End Sub Public Function ShallowCopy () As Course Return Me. MemberwiseClone () End Function Public Function Clone () As Object _ Implements ICloneable.Clone Dim course As Course = New Course(Title) course.Roster = Roster.Clone() Return course End Function End Class The test program constructs a Course instance c1 and then makes a copy c2 using reference copy, shallow copy, and deep copy, as described in the next three sections. Reference Copy by Assignment The first way the copy is performed is by the straight assignment c2 = c1 . Now we get two references to the same object, and if we make any change through the first reference, we will see the same change through the second reference. The first part of the test program illustrates such an assignment. ' CopyDemo.vb Imports System Imports System.Collections Module CopyDemo Private c1 As Course Private c2 As Course Sub Main() Console.WriteLine("Copy is done via c2 = c1") InitializeCourse() c1.Show("original") c2 = c1 c2.Show("copy") c2.Title = ".NET Programming" c2.AddStudent("Charlie") c2.Show("copy with changed title and new student") c1.Show("original") ... End Sub Private Sub InitializeCourse() c1 = New Course("Intro to VB") c1.AddStudent("John") c1.AddStudent("Mary") End Sub End Module We initialize with the title "Intro to VB.NET" and two students. We make the assignment c2 = c1 and then change the title and add another student for c2 . We then show both c1 and c2 , and we see that both reflect the changes, meaning that the reference c1 has been copied to c2 , and c2 no longer references the object that it originally did. Here is the output from this first part of the program: Copy is done via c2 = c1 -----original----- Course : Intro to VB.NET with 2 students John Mary -----copy----- Course : Intro to VB.NET with 2 students John Mary -----copy with changed title and new student----- Course : .NET Programming with 3 students John Mary Charlie -----original----- Course : .NET Programming with 3 students John Mary Charlie Memberwise Clone Next we will illustrate doing a memberwise copy, which can be accomplished using the MemberwiseClone method of Object . Since this method is Protected, we cannot call it directly from outside the Course class. Instead, in Course we define a method, ShallowCopy , which is implemented using MemberwiseClone . ' Course.vb Imports System Imports System.Collections Public Class Course Implements ICloneable ... Public Function ShallowCopy() As Course Return Me.MemberwiseClone() End Function ... End Class Here is the second part of the test program, which calls the ShallowCopy method. Again, we change the title and a student in the second copy. ' CopyDemo.vb Imports System Imports System.Collections Module CopyDemo ... Sub Main() ... Console.WriteLine() Console.WriteLine(_ "Copy is done via c2 = c1.ShallowCopy()") InitializeCourse() c2 = c1.ShallowCopy() c2.Title = ".NET Programming" c2.AddStudent("Charlie") c2.Show("copy with changed title and new student") c1.Show("original") ... End Sub EndModule Here is the output of this second part of the program. Now the Title field has its own independent copy, but the Roster collection is just copied by reference, so each copy refers to the same collection of students. Copy is done via c2 = c1.ShallowCopy() -----copy with changed title and new student----- Course : .NET Programming with 3 students John Mary Charlie -----original----- Course : Intro to VB.NET with 3 students John Mary Charlie Using ICloneable The final version of copy relies on the fact that our Course class supports the ICloneable interface and implements the Clone method. To clone the Roster collection, we use the fact that ArrayList also implements the ICloneable interface, as discussed earlier in the chapter. ' Course.vb Imports System Imports System.Collections Public Class Course Implements ICloneable ... Public Function Clone() As Object _ Implements ICloneable.Clone Dim course As Course = New Course(Title) course.Roster = Roster.Clone() Return course End Function ... End Class Here is the third part of the test program, which calls the Clone method. Again, we change the title and a student in the second copy. ' CopyDemo.vb Imports System Imports System.Collections Module CopyDemo ... Sub Main() ... Console.WriteLine() Console.WriteLine(_ " Copy is done via c2 = c1.Clone()") InitializeCourse() c2 = c1.Clone() c2.Title = ".NET Programming" c2.AddStudent("Charlie") c2.Show("copy with changed title and new student") c1.Show("original") ... End Sub EndModule Here is the output from the third part of the program. Now we have completely independent instances of Course . Each has its own title and set of students. Copy is done via c2 = c1.Clone() -----copy with changed title and new student----- Course : .NET Programming with 3 students John Mary Charlie -----original----- Course : Intro to VB.NET with 2 students John Mary Comparing Objects We have quite exhaustively studied copying objects. We now examine comparing objects. To compare objects, the .NET Framework uses the interface IComparable . In this section we use the interface IComparable to sort an array. Sorting an Array The System.Array class provides a static method, Sort , that can be used for sorting an array. The program ArrayName illustrates applying this Sort method to an array of Name objects, where the Name class simply encapsulates a String through a read-only property Text . Here is the main program. ' ArrayName.vb ... Module ArrayName Sub Main() Dim array() As Name = New Name(10) {} array(0) = New Name("Michael") array(1) = New Name("Charlie") array(2) = New Name("Peter") array(3) = New Name("Dana") array(4) = New Name("Bob") If TypeOf array(0) Is IComparable Then array.Sort(array) Else Console.WriteLine(_ "Name does not implement IComparable") End If Dim name As Name For Each name In array If Not name Is Nothing Then Console.WriteLine(name) End If Next End Sub End Module Implementing IComparable In order for the Sort method to function, there must be a way of comparing the objects that are being sorted. This comparison is achieved through the CompareTo method of the interface IComparable . Thus, to sort an array of a type you define, you must implement IComparable for your type. Public Interface IComparable Function CompareTo(ByVal obj As Object) As Integer End Interface Here is the implementation of the Name class, with its implementation of IComparable . Public Class Name Implements IComparable Private m_text As String Public Sub New(ByVal text As String) Me.m_text = text End Sub Public ReadOnly Property Text() As String Get Return m_text End Get End Property Public Function CompareTo(ByVal obj As Object) _ As Integer Implements IComparable.CompareTo Dim s1 As String = Me.m_text Dim s2 As String = obj.Text Return String.Compare(s1, s2) End Function Public Overrides Function ToString() As String Return Text End Function End Class Understanding Frameworks Our example offers some insight into the workings of frameworks. A framework is more than just a library. In a typical library, you are only concerned with your code calling library functions. In a framework, you call into the framework and the framework may call back into your class methods . Your program can be viewed as the middle layer of a sandwich. The .NET Framework is an excellent example of such an architecture. There is rich functionality that you can call directly. There are many interfaces, which you can optionally implement to make your program behave appropriately when called by the framework, often on behalf of other objects. |