Generic Interfaces in .NET

for RuBoard

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 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 : IEnumerable, ICollection,                           IList, ICloneable 

The first three interfaces form a simple interface hierarchy, as shown in Figure 5-1. As you go down the hierarchy, additional methods are added, until IList specifies a fully featured list.

Figure 5-1. Interface hierarchy for lists.

graphics/05fig01.gif

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.cs  using System;  using System.Collections;  public class StringList  {     private static ArrayList list;     public static void Main()     {     // Initialize strings and show starting state     list = new ArrayList(4);        ShowCount();     AddString("Amy");     AddString("Bob");     AddString("Charlie");     ShowEnum(list);      // enumerator     ShowCount();     // Add two more strings and show state again     AddString("David");     AddString("Ellen");     ShowList(list);      // foreach     ShowCount();     // Remove two strings from list and show state     RemoveString("David");     RemoveAt(0);     ShowArray(list);    // index notation     ShowCount();     // Try to remove two strings not in list     RemoveString("Amy");     RemoveAt(3);  }  ... 

Here is the output:

 list.Count = 0  list.Capacity = 4  Amy  Bob  Charlie  list.Count = 3  list.Capacity = 4  array[0] = Amy  array[1] = Bob  array[2] = Charlie  array[3] = David  array[4] = Ellen  list.Count = 5  list.Capacity = 8  Bob  Charlie  Ellen  list.Count = 3  list.Capacity = 8  List does not contain Amy  No element at index 3 
Interface Documentation

Interfaces are documented in the online .NET Framework SDK Documentation. Figure 5-2 illustrates the documentation of the IEnumerable interface. The right-hand pane has a language filter button graphics/icon04.gif which we have used to show only C# 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. 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 methods for you.

Figure 5-2. NET Framework SDK documentation for IEnumerable interface.

graphics/05fig02.gif

IEnumerable And IEnumerator

The basic interface that must be supported by collection classes is IEnumerable , which has a single method, GetEnumerator .

 interface IEnumerable  {     IEnumerator GetEnumerator();  } 

GetEnumerator returns an interface reference to IEnumerator , which is the interface used for iterating through a collection. This interface has the property Current and the methods MoveNext and Reset .

 interface IEnumerator  {     object Current {get;}     bool MoveNext();     void Reset();  } 

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 static void ShowEnum(ArrayList array)  {  IEnumerator iter = array.GetEnumerator();   bool more = iter.MoveNext();  while (more)     {  string str = (string) iter.Current;  Console.WriteLine(str);  more = iter.MoveNext();  }  } 

This pattern of using an enumerator to iterate through a list is so common that C# provides a special kind of loop, foreach , that can be used for iterating through the elements of any collection. Here is the comparable code using foreach .

 private static void ShowList(ArrayList array)  {  foreach (string str in array)  {        Console.WriteLine(str);     }  } 
ICollection

The ICollection interface is derived from IEnumerable and adds a Count property and a CopyTo method.

 interface ICollection : IEnumerable  {     int Count {get;}     bool IsSynchronized {get;}     object SyncRoot {get;}     void CopyTo(Array array, int index);  } 

There are also synchronization properties that 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 to easily implement. We discuss the .NET mechanisms for thread synchronization in Chapter 8.

Our StringList program illustrates use of the Count property of ICollection .

 private static void ShowCount()  {     Console.WriteLine("list.Count = {0}",  list.Count  );     Console.WriteLine("list.Capacity = {0}",  list.Capacity);  } 
IList

The IList interface is derived from ICollection and provides methods for adding an item to a list, removing an item, and so on. An indexer is provided that enables array notation to be used. (We discussed indexers in Chapter 3.)

 interface IList : ICollection  {     object this[int index] {get; set;}     int Add(object value);     void Clear();     bool Contains(object value);     int IndexOf(object value);     void Insert(int index, object value);     void Remove(object value);     void RemoveAt(int index);  } 

Our StringList sample code illustrates using the indexer and the Add , Contains , Remove , and RemoveAt methods.

 private static void ShowArray(ArrayList array)  {     for (int i = 0; i < array.Count; i++)     {        Console.WriteLine("array[{0}] = {1}", i,  array[i]  );     }  }  private static void AddString(string str)  {  if (list.Contains(str))  throw new Exception("list contains " + str);  list.Add(str);  }  private static void RemoveString(string str)  {  if (list.Contains(str))   list.Remove(str);  else        Console.WriteLine("List does not contain {0}", str);  }  private static void RemoveAt(int index)  {     try     {  list.RemoveAt(index);  }     catch (ArgumentOutOfRangeException)     {        Console.WriteLine("No element at index {0}", index);     }  } 

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 copy semantics of C#. 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 make a deep copy.

Recall that C# 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 struct in C# 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 references to the same data, not two independent copies of the data. To actually copy the data itself and not merely the 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 C# 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.cs  using System;  using System.Collections;  public class Course : ICloneable  {     public string Title;     public ArrayList Roster;     public Course(string title)     {        Title = title;        Roster = new ArrayList();     }     public void AddStudent(string name)     {        Roster.Add(name);     }     public void Show(string caption)     {        Console.WriteLine("-----{0}-----", caption);        Console.WriteLine("Course : {0} with {1} students",           Title, Roster.Count);        foreach (string name in Roster)        {           Console.WriteLine(name);        }     }     public Course ShallowCopy()     {        return (Course) this.MemberwiseClone();     }     public object Clone()     {        Course course = new Course(Title);        course.Roster = (ArrayList) Roster.Clone();        return course;     }  } 

The test program constructs a Course instance c1 and then makes a copy c2 by various methods.

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.cs  using System;  using System.Collections;  public class CopyDemo  {     private static Course c1, c2;     public static void Main()     {        Console.WriteLine("Copy is done via c2 = c1");        InitializeCourse();        c1.Show("original");  c2 = c1;   c2.Title = ".NET Programming";   c2.AddStudent("Charlie");  c2.Show("copy with changed title and new student");        c1.Show("original");        ...     }     private static void InitializeCourse()     {        c1 = new Course("Intro to C#");        c1.AddStudent("John");        c1.AddStudent("Mary");     }  } 

We initialize with the title "Intro to C#" 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 both of these changes. Here is the output from this first part of the program:

 Copy is done via c2 = c1  -----original----- Course : Intro to C# with 2 students  John  Mary  -----copy----- Course : Intro to C# 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 our Course class. Instead, in Course we define a method, ShallowCopy , which is implemented using MemberwiseClone .

 // Course.cs  using System;  using System.Collections;  public class Course : ICloneable  {     ...  public Course ShallowCopy()   {   return (Course) this.MemberwiseClone();   }  ...  } 

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.cs  using System;  using System.Collections;  public class CopyDemo  {        ...        Console.WriteLine(           "\nCopy 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");        ... 

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 C# 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. Note that the Clone method returns an object , so we must cast to ArrayList before assigning to the Roster field.

 // Course.cs  using System;  using System.Collections;  public class Course : ICloneable  {     ...  public object Clone()   {   Course course = new Course(Title);   course.Roster = (ArrayList) Roster.Clone();   return course;   }  } 

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.cs  using System;  using System.Collections;  public class CopyDemo  {        ...        Console.WriteLine(           "\nCopy is done via c2 = c1.Clone()");        InitializeCourse();  c2 = (Course) c1.Clone();   c2.Title = ".NET Programming";   c2.AddStudent("Charlie");  c2.Show("copy with changed title and new student");        c1.Show("original");        ... 

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 C# 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.cs  ...  public class ArrayName  {     public static void Main(string[] args)     {        Name[] array = 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 (array[0] is IComparable)   Array.Sort(array);  else           Console.WriteLine(              "Name does not implement IComparable");        foreach (Name name in array)        {           if (name != null)              Console.WriteLine(name);        }     }  } 
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  {        int CompareTo(object object);  } 

Here is the implementation of the Name class, with its implementation of IComparable .

  public class Name : IComparable  {     private string text;     public Name(string text)     {        this.text = text;     }     public string Text     {        get        {           return text;        }     }  public int CompareTo(object obj)   {   string s1 = this.Text;   string s2 = ((Name) obj).Text;   return String.Compare(s1, s2);   }  } 

Understanding Frameworks

Our example offers some insight into the workings of frameworks. A framework is more than a library. In a typical library, you are concerned with your code calling library functions. In a framework, you call into the framework and the framework might call you . Your program can be viewed as the middle layer of a sandwich.

  • Your code calls the bottom layer.

  • The top layer calls your code.

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.

for RuBoard


Application Development Using C# and .NET
Application Development Using C# and .NET
ISBN: 013093383X
EAN: 2147483647
Year: 2001
Pages: 158

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