This document discusses interfaces in C# and .NET. It defines interfaces as collections of related members that define behaviors that classes can choose to support. It describes how to define interfaces using the interface keyword, implement interfaces in classes using a comma separated list, and obtain interface references from objects using casting, the "as" and "is" keywords. It also covers explicit interface implementation to avoid name clashes, interfaces as parameters and return types, and building interface hierarchies.
“Oh GOSH! Reflecting on Hackteria's Collaborative Practices in a Global Do-It...
Dotnet unit 4
1. Chapter 8
Interfaces and Collections
8.1 INTRODUCTION
“An interface is nothing more than a named collection of semantically related abstract members.”
The exact number of members defined by a given interface always depends on the exact behavior
that a given class may support.
8.2 DEFINING INTERFACES USING C#
At a syntactic level, an interface is defined using the C# “interface” keyword. Unlike other .NET
types, interfaces never specify a base class (not even System.Object) and contain members that do
not take an access modifier (interface methods are implicitly public). To get the ball rolling, here is
a custom interface definition:
// This interface defines the behavior of ‘having points’.
public interface IPointy {
byte GetNumberOfPoints(); // Implicitly public and abstract.
}
.NET interfaces (C# or otherwise) are also able to define any number of properties. For example,
you could modify the IPointy interface to use a read/write property:
// The pointy behavior as a read / write property.
public interface IPointy { // Remove ‘get’ or ‘set’ to build read/
write only property.
byte Points{ get; set;}
}
2. Interfaces and Collections 205
8.3 IMPLEMENTING AN INTERFACE USING C#
When a C# class (or structure) chooses to extend its functionality by supporting a given interface,
it does so using a comma-delimited list in the type definition. The direct base class must be listed
first.
// This class derives from System.Object and implements a single
interface.
public class SomeClass : ISomeInterface
{...}
// This class derives from a specific base class and implements
a single interface.
public class AnotherClass : MyBaseClass, ISomeInterface
{...}
// This struct derives from System.ValueType and implements a
single interface.
public struct SomeStruct : ISomeInterface
{...}
The shapes hierarchy
Figure8.1: The shapes hierarchy
// This interface defines the behavior of ‘having points’.
public interface IPointy {
byte GetNumberOfPoints(); // Implicitly public and abstract.
}
___________________________________________________________________________
public abstract class Shape {
protected string petName;
public Shape() { petName = “NoName”; }
public Shape(string s) { petName = s;}
public virtual void Draw() { // Draw() is virtual and may be overridden.
Console.WriteLine(“Shape.Draw()”);
3. 206 C# .Netprogramming
}
public string PetName {
get { return petName;}set { petName = value;}
}
}
________________________________________________________________________________________
public class Circle : Shape {
public Circle() { }
public Circle(string name): base(name) { }
}
_______________________________________________________________________________________
// A given class may implement as many interfaces as necessary, but
may have exactly 1 base class.
public class Hexagon : Shape, IPointy {
public Hexagon(){ }
public Hexagon(string name) : base(name){ }
public override void Draw() { // Shape class defined the
PetName property.
Console.WriteLine(“Drawing {0} the Hexagon”, PetName);
}
// IPointy Implementation.
public byte GetNumberOfPoints() {
return 6;
}
}
________________________________________________________________________
public class Triangle : Shape, IPointy {
public Triangle() { }
public Triangle(string name) : base(name) { }
public override void Draw() {
Console.WriteLine(“Drawing {0} the Triangle”, PetName);
}
public byte GetNumberOfPoints() { // IPointy Implementation.
return 3;
}
}
_________________________________________________________________________
Each class now returns the number of points to the outside world when asked to
do so.
4. Interfaces and Collections 207
8.3.1 Invoking Interface Members at the object Level
The way to interact with functionality supplied by a given interface is to invoke the methods directly
from the object level. For example:
Hexagon hex = new Hexagon();
Console.WriteLine(“Points: {0}”, hex.GetNumberOfPoints());
This approach works fine in this case, given that you are well aware that the Hexagon type has
implemented the interface.
There is number of situation you will be required to obtain a valid interface reference directly
before you are able to activate said functionality.
8.3.2 Obtaining Interface References: Explicit Casting
Assume you have created an instance of the Hexagon class, and wish to dynamically see if it
supports the pointy behavior. One approach is to make use of an explicit cast in order to obtain an
interface reference:
Hexagon hex = new Hexagon(“Bill”);
IPointy itfPt = (IPointy)hex;
Console.WriteLine(itfPt.GetNumberOfPoints());
If you do not require a stand-alone interface reference, you could shorten the previous code into a
single step as follows:
Hexagon hex = new Hexagon(“Bill”);
Console.WriteLine( ( (IPointy)hex ).GetNumberOfPoints() );
In each of these cases, you are explicitly asking the Hexagon instance for access to the IPointy
interface. If the object does support this interface, you are then able to exercise the behavior
accordingly.
When you attempt to access an interface not supported by a given class using a direct cast, the
runtime throws an InvalidCastException. To safely recover from this possibility, simply catch the
exception:
Circle c = new Circle(“Lisa”);
IPointy itfPt;
try{
itfPt = (IPointy)c;
Console.WriteLine( itfPt.GetNumberOfPoints() );
}
catch(InvalidCastException e) {
Console.WriteLine(“OOPS! Not an Ipointy type ...”);
}
5. 208 C# .Netprogramming
8.3.3 Obtaining Interface References: The “as” Keyword
The second way you can test for interface support is to make use of the “as” keyword For example:
Hexagon hex2 = new Hexagon(“Peter”);
IPointy itfPt2;
itfPt2 = hex2 as IPointy;
if( itfPt2 != null )
Console.WriteLine(itfPt2.GetNumberOfPoints());
else
Console.WriteLine(“OOPS! Not pointy...”);
As you can see, the “as” syntax sets the interface variable to null if a given interface is not supported
by the object (notice that you check your IPointy reference for null before continuing) rather than
throwing an exception.
8.3.4 Obtaining Interface References: The “is” Keyword
Finally, you may also obtain an interface from an object using the “is” keyword. If the object in
question is
not IPointy compatible, the condition fails:
Triangle t = new Triangle();
if( t is IPointy )
Console.WriteLine( t. GetNumberOfPoints() );
else
Console.WriteLine(“OOPS! Not pointy...”);
Exercising the Shapes Hierarchy
Create an array of generic Shape references, each of which has been assigned to a given subclass?
You may make use of any of the previous techniques to discover at runtime which items in the array
support this behavior:
// Let’s discover which shapes are pointy at runtime...
Shape[] s = { new Hexagon(), new Circle(), new Triangle(“Joe”),
new Circle(“JoJo”)} ;
for(int i = 0; i < s.Length; i++) {
// Shape base class defines an abstract Draw() member, so all shapes know how
to draw themselves.
s[i].Draw();
// Who’s pointy?
if(s[i]is IPointy)
6. Interfaces and Collections 209
Console.WriteLine(“-> Points: {0} “, ((IPointy)s[i]).GetNumberOfPoints());
else
Console.WriteLine(“-> {0}’s not pointy!”, s[i].PetName);
}
The output follows in Figure.
Figure 8.2: output
8.4 INTERFACES AS PARAMETERS
Given that interfaces are strongly typed entities, you may construct methods that take interfaces as
parameters as well as method return values. To illustrate, assume you have defined another interface
named IDraw3D as follows:
The Updated Shape Hierarchy
Figure8.3: Updated shape hierarch
// The 3D drawing behavior.
public interface IDraw3D {
void Draw3D();
} Next, assume that two of your three shapes (Circle and Hexagon) have been
configured to support this new behavior:
7. 210 C# .Netprogramming
// Circle supports IDraw3D.
public class Circle : Shape, IDraw3D {
...
public void Draw3D()
{ Console.WriteLine(“Drawing Circle in 3D!”); }
}
// If your types support multiple interfaces, simply tack them to the end of
the class definition.
public class Hexagon : Shape, IPointy, IDraw3D {
...
public void Draw3D()
{ Console.WriteLine(“Drawing Hexagon in 3D!”); }
}
If you now define a method taking an IDraw3D interface as a parameter, you are able to effectively
send in any object supporting IDraw3D. Consider the following:
public class ShapesApp {
// I’ll draw anyone supporting IDraw3D!
public static void DrawThisShapeIn3D( IDraw3D itf3d ) {
Console.WriteLine(“-> Drawing IDraw3D compatible type”);
itf3d.Draw3D();
}
public static int Main(string[] args) {
Shape[] s = { new Hexagon(), new Circle(),
new Triangle(), new Circle(“JoJo”)} ;
for(int i = 0; i < s.Length; i++)
{
...
// Can I draw you in 3D?
if(s[i] is IDraw3D)
DrawThisShapeIn3D( (IDraw3D)s[i] );
}
return 0;
}
}
8.5 UNDERSTANDING EXPLICIT INTERFACE IMPLEMENTATION
In our previous definition of IDraw3D, you were forced to name your method Draw3D() in order to
avoid clashing with the abstract Draw() method defined in the Shapes base class:
8. Interfaces and Collections 211
// The 3D drawing behavior.
public interface IDraw3D {
void Draw3D();
}
While there is nothing wrong with this interface definition, a more natural method name would
simply be Draw():
// The 3D drawing behavior.
public interface IDraw3D {
void Draw ();
}
If you were to create a new class that derives from Shape and implements IDraw3D, you would be
in for some problematic behavior. Before seeing the problem firsthand, assume you have defined
the following new class named Line:
// Problems...
public class Line : Shape, IDraw3D // Both define a Draw() method!
{
public override void Draw() {
Console.WriteLine(“Drawing a line...”);
}
}
The Line class compiles without a hitch. But, consider the following object user code:
// Calls Line.Draw()
Line myLine = new Line();
myLine.Draw();
// Also calls Line.Draw().
IDraw3D itfDraw3d= (IDraw3D) myLine;
itfDraw3d.Draw();
Given what you already know about the Shapes base class and IDraw3D interface, it looks as if
you have acquired two abstract methods named Draw(). However, as the Line class offers a concrete
implementation, the compiler is happy to call the same implementation from an interface or object
reference.
The explicit interface implementation, you are able to ensure that the object user can only
access methods defined by a given interface using the correct interface reference, as well as
circumvent possible name clashes. To illustrate, here is the updated Line class:
public class Line : Shape, IDraw3D
{
// You can only call this method using an IDraw3D interface reference.
9. 212 C# .Netprogramming
void IDraw3D.Draw() {
Console.WriteLine(“Drawing a 3D line...”);
}
// You can only call this using a Line (or base class) reference.
public override void Draw() {
Console.WriteLine(“Drawing a line...”);
}
} There are a few odds and ends to be aware of when using explicit interface implementation.
First and foremost, you cannot make use of an access modifier when using this technique. For
example, the following is illegal syntax:
// Nope! Illegal.
public class Line : Shape, IDraw3D {
public void IDraw3D.Draw() { // <= Error!
Console.WriteLine(“Drawing a 3D line...”);
}
...
}
The whole reason to use explicit interface method implementation is to ensure that a given interface
method is bound at the interface level. If you were to add the “public” keyword, this would suggest
that the method is a member of the public sector of the class. Given this design, the caller is unable
to invoke IDraw3D.Draw() from an object level:
// This triggers the overridden Shape.Draw() method.
Line l = new Line();
l.Draw();
Explicit interface implementation can be very helpful whenever you are implementing a number of
interfaces that happen to contain identical methods. For example, assume you wish to create a class
that implements all the following interfaces:
// Three interfaces each defining identical methods.
public interface IDraw {
void Draw();
}
public interface IDraw3D {
void Draw();
}
public interface IDrawToPrinter {
void Draw();
}
10. Interfaces and Collections 213
If you wish to build a shape (using interface-based techniques) that supports basic rendering (IDraw),
3D rendering (IDraw3D), as well as printing services (IDrawToPrinter), the only way to provide
unique behaviors for each method is to use explicit interface implementation:
// Not deriving from Shape, but still injecting a name clash.
public class SuperImage : IDraw, IDrawToPrinter, IDraw3D
{
void IDraw.Draw() { // Basic drawing logic. }
void IDrawToPrinter.Draw() { // Printer logic. }
void IDraw3D.Draw() { // 3D support. }
}
8.6 INTERFACES AS POLYMORPHIC AGENTS
As you already know, an abstract base class (containing abstract members) allows us to define a
specific behavior that is common across all members in the same class hierarchy. To really understand
the usefulness of interfaces, assume that the GetNumberOfPoints() method was not defined by
IPointy, but rather as an abstract member of the Shape class. If this were the case, Hexagon and
Triangle would still be able to return the correct result. At the same time, Circle would also be
obligated to contend with the GetNumberOfPoints() method as well. In this case, however, it might
seem a bit inelegant to simply return 0.
The problem with defining GetNumberOfPoints() in the Shapes base class is that all derived
types must contend with this member, regardless of the semantics. Thus, the first key point about
interfaces is the fact that you can select which members in the hierarchy support custom behaviors.
Also understand that the same interface can be implemented by numerous types, even if they are
not within the same class hierarchy in the first place. This can yield some very powerful programming
constructs.
For example, assume that you have developed a brand new class hierarchy modeling kitchen
utensils and another modeling gardening equipment. Although each hierarchy of types is completely
unrelated from a classical inheritance point of view, you can link them together using the common
behavior supplied by the IPointy interface:
// This array can only contain types which implement the IPointy interface.
IPointy myPointyObjects[] = {new Hexagon(), new Knife(),new Triangle(), new
Fork(), new PitchFork()};
At this point, you are able to iterate through the array and treat each object as an IPointy-compatible
object, regardless of the overall diversity of the class hierarchies. In fact, the IDisposable interface
may be implemented by numerous .NET types, regardless of which assembly they reside in. At
runtime, you are able to determine if the object supports this behavior (using an explicit cast or the
“as”/”is” keywords) and call the Dispose() method.
11. 214 C# .Netprogramming
8.7 BUILDING INTERFACE HIERARCHIES:
Just as a class can serve as a base class to other classes (base classes to yet another class), it is
possible to build derived relationships among interfaces. As you might expect, the topmost interface
defines a general behavior, while the most derived interface defines more specific behaviors. To
illustrate, consider the following interface hierarchy:
// The base interface.
interface IDraw { void Draw();}
interface IDraw2 : IDraw { void DrawToPrinter(); }
interface IDraw3 : IDraw2 { void DrawToMetaFile(); }
Understand, of course, that you can name your derived interfaces anything you choose. Here I
demonstrate how to make use of a common COM-centric naming convention, which is to suffix a
numerical qualifier to the derived type. The relationships between these custom interfaces can be
seen in Figure .
Now, if a class wished to support each behavior expressed in this interface hierarchy, it would
derive from the nth-most interface (IDraw3 in this case). Any methods defined by the base interface(s)
are
automatically carried into the definition. For example:
Figure 8.4: interface inheritance
// This class supports IDraw, IDraw2 and IDraw3.
public class SuperImage : IDraw3
{
// in this case, as there is no name clash. However, by doing so, we will force
the user to obtain the interface first.
void IDraw.Draw() { // Basic drawing logic }
void IDraw2.DrawToPrinter() { // Draw to printer. }
void IDraw3.DrawToMetaFile() { // Draw to metafile. }
}
12. Interfaces and Collections 215
Here is some sample usage:
// Exercise the interfaces.
public class TheApp {
public static int Main(string[] args) {
SuperImage si = new SuperImage();
IDraw itfDraw = (IDraw)si;
itfDraw.Draw();
if(itfDraw is IDraw3) // Now get IDraw3.
{
IDraw3 itfDraw3 = (IDraw3)itfDraw;
itfDraw3.DrawToMetaFile();
itfDraw3.DrawToPrinter();
}
return 0;
}
}
8.8 INTERFACES WITH MULTIPLE BASE INTERFACES
As you build interface hierarchies, be aware that it is completely permissible to create an interface
that derives from multiple base interfaces. Recall, of course, that it is not permissible to build a
class that derives from multiple base classes. For example, assume you are building a new set of
interfaces that model automobile behaviors:
interface IBasicCar { void Drive(); }
interface IUnderwaterCar { void Dive(); }
// Here we have an interface with TWO base interfaces.
interface IJamesBondCar : IBasicCar, IUnderwaterCar{
void TurboBoost();
}
If you were to build a class that implements IJamesBondCar, you would now be responsible for
implementing TurboBoost(), Dive(), and Drive():
public class JBCar : IJamesBondCar
{
public JBCar(){ }
// Again, we are not required to use explicit interface implementation, as
we have no name clashes.
void IBasicCar.Drive(){ Console.WriteLine(“Speeding up...”);}
void IUnderwaterCar.Dive(){ Console.WriteLine(“Submerging...”);}
void IJamesBondCar.TurboBoost(){ Console.WriteLine(“Blast off!”);}
}
13. 216 C# .Netprogramming
This specialized automobile can now be manipulated as you would expect:
JBCar j = new JBCar();
if( j is IJamesBondCar )
{
( ( IJamesBondCar )j ).Drive();
( ( IJamesBondCar )j ).TurboBoost();
( ( IJamesBondCar )j ).Dive();
}
8.9 IMPLEMENTING INTERFACES USING VS .NET
Although interface-based programming is a very powerful programming technique, one drawback
is the very simple fact that you are required to do a lot of manual typing. Given that interfaces are
a named set of abstract members, you will be required to type in the stub code (and implementation)
for each interface method on each class that supports the behavior. As you would expect, VS .NET
does support an integrated tool that helps make this task easy. To illustrate, assume you have an
interface defining the following four methods:
public interface IAmAnInterface
{
void MethodA();
void MethodC();
void MethodD();
void MethodE();
}
And also assume you have a class that supports IAmAnInterface:
public class SomeClass : IAmAnInterface
{}
At this point, you are free to build stub code for each abstract method by hand. However, if you
wish to force VS .NET, switch to Class View and find a class that already specifies support for the
interface you wish to implement. Once you do, expand the Bases and Interfaces node, right-click
the interface icon, and select Add | Implement Interface Figure.
Figure 8.5 : interface implementation
14. Interfaces and Collections 217
namespace IFaceHierarchy
{
public class MiniVan : ICar
{
public MiniVan(){ }
#region ICar Members
public void Drive() {
new Exception(“not implemented.”);
}
#endregion
}
}
Once you do, you will see that VS .NET has built automatic stub code wrapped in a #region/
#endregion pair. when you insert a new class definition into your VS .NET projects using Class
View, you are able to specify the set of implemented interfaces at the time of creation. When you do
so, you will still need to run the Implement Interface Wizard as shown previously.
8.10 UNDERSTANDING THE ICONVERTIBLE INTERFACE:
The IConvertible type, which allows you to dynamically convert between data types using interface-
based programming techniques. Using this interface, you are able to cast between types on the fly
using language-agnostic terms.
Majority of the intrinsic data types of C# (bool, int, double, etc.) are simply aliases to true-blue
structures in the System namespace. Like any .NET type, each of these structures may define any
number of methods and may optionally implement any number of predefined interfaces. For example,
if you were to view the formal C# definition of System.Boolean (using wincv.exe), you would find
this type (as well as all intrinsic data type structures) implements the IComparable and IConvertible
interfaces:
public struct Boolean : IComparable , IConvertible
{
public static readonly string FalseString;
public static readonly string TrueString;
public virtual int CompareTo(object obj);
public virtual bool Equals(object obj);
public virtual int GetHashCode();
public Type GetType();
public virtual TypeCode GetTypeCode();
public static bool Parse(string value);
public virtual string ToString();
15. 218 C# .Netprogramming
public virtual string ToString(IFormatProvider provider);
}
public interface IConvertible
{
TypeCode GetTypeCode();
bool ToBoolean(IFormatProvider provider);
byte ToByte(IFormatProvider provider);
char ToChar(IFormatProvider provider);
DateTime ToDateTime(IFormatProvider provider);
Decimal ToDecimal(IFormatProvider provider);
double ToDouble(IFormatProvider provider);
short ToInt16(IFormatProvider provider);
int ToInt32(IFormatProvider provider);
long ToInt64(IFormatProvider provider);
SByte ToSByte(IFormatProvider provider);
float ToSingle(IFormatProvider provider);
string ToString(IFormatProvider provider);
object ToType(Type conversionType, IFormatProvider provider);
UInt16 ToUInt16(IFormatProvider provider);
UInt32 ToUInt32(IFormatProvider provider);
UInt64 ToUInt64(IFormatProvider provider);
}
bool myBool = true;
IConvertible itfConv = (IConvertible)myBool;
8.10.1 IConvertible.ToXXXX() Members
I Convertible interface defines a number of methods of the form ToXXXX(), which as you can
most likely tell, provide a way to convert from one type into another. As you may also be able to
tell, it may not always be possible to convert between data types. For example, although it is natural
to predict converting from an Int32 into a Double, it really makes no sense to convert from a
Boolean into a DateTime.
Given the fact that when a type implements an interface, it must contend with all methods (even
if they are not applicable), the system types will simply throw an InvalidCastException if the
conversion is semantically not well formed:
// Obtain the IConvertible interface.
bool myBool = true;
IConvertible itfConv = (IConvertible)myBool;
try{
itfConv.ToSingle(...);
16. Interfaces and Collections 219
}
catch(InvalidCastException e) {
Console.WriteLine(e);
}
8.10.2 A Brief Word Regarding IFormatProvider
Notice that all of the ToXXXX() methods take a parameter of type IFormatProvider. Objects that
implement this interface are able to format their contents based on culture-specific information (for
example, returning a floating point number that is formatted in various currencies). Here is the
formal definition:
public interface IFormatProvider
{
object GetFormat(Type formatType);
}
If you were to build a custom type that should be formatted using various locals, implementing
IFormatProvider would be a must. However, if you are simply attempting to call members of the
base class libraries that require an IFormatProvider-compatible object, feel free to leverage the
System.Globalization.CultureInfo type as follows:
IConvertible itfConvert = (IConvertible)theInt;
byte theByte = itfConvert.ToByte(CultureInfo.CurrentCulture);
Console.WriteLine(“Type code int converted to byte is: {0}”,theByte.GetTypeCode());
Console.WriteLine(“Value of converted int: {0}”, theByte);
8.10.3 IConvertible.GetTypeCode()
In addition to the ToXXXX() members, IConvertible defines a member named GetTypeCode().
This method, which is available to any class or structure implementing IConvertible, allows you to
programmatically discover a value that represents the type code of the type, which is represented
by the following enumeration:
public enum TypeCode
{
Boolean, Byte, Char, DateTime,DBNull, Decimal, Double, Empty,
Int16, Int32, Int64, Object,SByte, Single, String, UInt16,UInt32,
UInt64
}
To be sure, IConvertible.GetTypeCode() is not a method you will need to call all that often in your
day-today programming endeavors.
17. 220 C# .Netprogramming
8.11 BUILDING A CUSTOM ENUMERATOR
(IENUMERABLE AND ENUMERATOR)
To illustrate the process of implementing existing .NET interfaces, let’s first examine the role of
IEnumerable and IEnumerator. Assume you have developed a class named Garage that contains a
set of individual Car types stored within a System.Array:
// Garage contains a set of Car objects.
public class Garage {
private Car[] carArray;
// Fill with some Car objects upon startup.
public Garage() {
carArray = new Car[4];
carArray[0] = new Car(“Rusty”, 30);
carArray[1] = new Car(“Clunker”, 55);
carArray[2] = new Car(“Zippy”, 30);
carArray[3] = new Car(“Fred”, 30);
}
}
Ideally, it would be convenient to iterate over the Garage object’s subitems using the C# foreach
construct:
// This seems reasonable...
public class Program {
static void Main(string[] args) {
Garage carLot = new Garage();
// Hand over each car in the collection?
foreach (Car c in carLot) {
Console.WriteLine(“{0} is going {1} MPH”,c.PetName, c.CurrSpeed);
}
}
}
The compiler informs you that the Garage class does not implement a method named
GetEnumerator(). This method is formalized by the IEnumerable interface, which is found lurking
within the System.Collections namespace. Objects that support this behavior advertise that they
are able to expose contained subitems to the caller:
// This interface informs the caller that the object’s subitems can be
enumerated.
public interface IEnumerable {
IEnumerator GetEnumerator();
}
18. Interfaces and Collections 221
As you can see, the GetEnumerator() method returns a reference to yet another interface named
System.Collections.IEnumerator. This interface provides the infrastructure to allow the caller to
traverse the internal objects contained by the IEnumerable-compatible container:
// This interface allows the caller to obtain a container’s sub items.
public interface IEnumerator
{
bool MoveNext (); // Advance the internal position of the cursor.
object Current { get;} // Get the current item (read-only property).
void Reset (); // Reset the cursor before the first member.
}
If you wish to update the Garage type to support these interfaces, it will take time to implement
each method manually. As the System.Array type already implements IEnumerable and IEnumerator,
you can simply delegate the request to the System.Array as follows:
using System.Collections;
...
public class Garage : IEnumerable
{
// System.Array already implements IEnumerator!
private Car[] carArray;
public Garage()
{
carArray = new Car[4];
carArray[0] = new Car(“FeeFee”, 200, 0);
carArray[1] = new Car(“Clunker”, 90, 0);
carArray[2] = new Car(“Zippy”, 30, 0);
carArray[3] = new Car(“Fred”, 30, 0);
}
public IEnumerator GetEnumerator()
{
// Return the array object’s IEnumerator.
return carArray.GetEnumerator();
}
} Once you have updated your Garage type, you can now safely use the type within the C# foreach
construct. Furthermore, given that the GetEnumerator() method has been defined publicly, the
object user could also interact with the IEnumerator type:
// Manually work with IEnumerator.
IEnumerator i = carLot.GetEnumerator();
19. 222 C# .Netprogramming
i.MoveNext();
Car myCar = (Car)i.Current;
Console.WriteLine(“{0} is going {1} MPH”, myCar.PetName, myCar.CurrSpeed);
If you would prefer to hide the functionality of IEnumerable from the object level, simply make use
of explicit interface implementation:
public IEnumerator IEnumerable.GetEnumerator()
{
// Return the array object’s IEnumerator.
return carArray.GetEnumerator();
}
8.12 UNDERSTANDING C# ITERATOR METHODS
Under .NET 1.x, if you wished to have your custom collections (such as Garage) support foreach
like enumeration, implementing the IEnumerable interface (and possibly the IEnumerator interface)
was mandatory. However, offers an alternative way to build types that work with the foreach loop
via iterators.
Simply put, an iterator is a member that specifies how a container’s internal items should be
returned when processed by foreach. While the iterator method must still be named GetEnumerator(),
and the return value must still be of type IEnumerator, your custom class does not need to implement
any of the expected interfaces:
public class Garage // No longer implementing IEnumerable!
{
private Car[] carArray;
...
// Iterator method.
public IEnumerator GetEnumerator()
{
foreach (Car c in carArray)
{
yield return c;
}
}
}
Notice that this implementation of GetEnumerator() iterates over the subitems using internal foreach
logic and returns each Car to the caller using the new yield return syntax. The yield keyword is used
to specify the value (or values) to be returned to the caller’s foreach construct. When the yield
return statement is reached, the current location is stored, and execution is restarted from this
location the next time the iterator is called.
20. Interfaces and Collections 223
When the C# compiler encounters an iterator method, it will dynamically generate a nested
class within the scope of the defining type (Garage in this case). The autogenerated class implements
the GetEnumerator(), MoveNext() and Current members on your behalf (oddly, the Reset() method
is not, and you will receive a runtime exception if you attempt to call it). If you were to load the
current application into ildasm.exe, you would find that the Garage’s implementation of
GetEnumerator() is making use of this compiler-generated type (which happens to be named
<GetEnumerator>d__0 in this example) internally:
.method public hidebysig instance class
[mscorlib]System.Collections.IEnumerator
GetEnumerator() cil managed
{
...
newobj instance void
CustomEnumeratorWithYield.Garage/’<GetEnumerator>d__0'::.ctor(int32)
...
} // end of method Garage::GetEnumerator
However, if you are building a more exotic custom container (such as a binary tree) where you need
to manually implement the IEnumerator and IEnumerable interfaces, the C# iterator syntax can be
a massive time-saver. In any case, the caller’s code is identical when interacting with a type’s
iterator method via foreach:
static void Main(string[] args)
{
Console.WriteLine(“***** Fun with Iterator Methods *****n”);
Garage carLot = new Garage();
foreach (Car c in carLot)
{
Console.WriteLine(“{0} is going {1} MPH”, c.PetName, c.CurrSpeed);
}
Console.ReadLine();
}
8.13 BUILDING CLONEABLE OBJECTS ( ICLONEABLE)
System.Object defines a member named MemberwiseClone(). This method is used to obtain a
shallow copy of the current object. Object users do not call this method directly (as it is protected);
however, a given object may call this method itself during the cloning process. To illustrate,assume
you have a class named Point:
21. 224 C# .Netprogramming
// A class named Point.
public class Point
{
// Public for easy access.
public int x, y;
public Point(int x, int y) { this.x = x; this.y = y;}
public Point(){}
// Override Object.ToString().
public override string ToString() {
return string.Format(“X = {0}; Y = {1}”, x, y );
}
}
The following assignment operation results in two references to the same Point object on the heap;
modifications using either reference affect the same object on the heap:
static void Main(string[] args)
{
// Two references to same object!
Point p1 = new Point(50, 50);
Point p2 = p1;
p2.x = 0;
Console.WriteLine(p1);
Console.WriteLine(p2);
}
The ability to return an identical copy of itself to the caller, you may implement the standard
ICloneable interface. This type defines a single method named Clone():
public interface ICloneable
{
object Clone();
}
The basic functionality is Copy the values of your member variables into a new object instance, and
return it to the user. To illustrate, consider the following update to the Point class:
// The Point now supports “clone-ability.”
public class Point : ICloneable {
public int x, y;
public Point(){ }
public Point(int x, int y) { this.x = x; this.y = y;}
// Return a copy of the current object.
22. Interfaces and Collections 225
public object Clone()
{ return new Point(this.x, this.y); }
public override string ToString()
{ return string.Format(“X = {0}; Y = {1}”, x, y ); }
}
In this way, you can create exact stand-alone copies of the Point type, as illustrated by the following
code:
static void Main(string[] args)
{
// Notice Clone() returns a generic object type.
Point p3 = new Point(100, 100);
Point p4 = (Point)p3.Clone();
// Change p4.x (which will not change p3.x).
p4.x = 0;
// Print each object.
Console.WriteLine(p3);
Console.WriteLine(p4);
}
The Point type does not contain reference type variables, you could simplify the implementation of
the Clone() method as follows:
public object Clone() {
// Copy each field of the Point member by member.
return this.MemberwiseClone();
}
If the Point did contain any reference type member variables, MemberwiseClone() will copy the
references to those objects. If you wish to support a true deep copy, you will need to create a new
instance of any reference type variables during the cloning process. Let’s see an example.
A More Elaborate Cloning Example
Now assume the Point class contains a reference type member variable of type PointDescription.
This class maintains a point’s friendly name as well as an identification number expressed as a
System.Guid. Here is the implementation:
// This class describes a point.
public class PointDescription
{
// Exposed publicly for simplicity.
public string petName;
23. 226 C# .Netprogramming
public Guid pointID;
public PointDescription()
{
this.petName = “No-name”;
pointID = Guid.NewGuid();
}
}
public class Point : ICloneable
{
public int x, y;
public PointDescription desc = new PointDescription();
public Point(){}
public Point(int x, int y)
{
this.x = x; this.y = y;
}
public Point(int x, int y, string petname)
{
this.x = x;
this.y = y;
desc.petName = petname;
}
public object Clone() {
return this.MemberwiseClone();
}
public override string ToString() {
return string.Format(“X = {0}; Y = {1}; Name = {2};nID =
{3}n”,x, y, desc.petName, desc.pointID);
}
}
Notice that you did not yet update your Clone() method. Therefore, when the object user asks for a
clone using the current implementation, a shallow (member-by-member) copy is achieved. To
illustrate, assume you have updated Main() as follows:
static void Main(string[] args)
{
Console.WriteLine(“Cloned p3 and stored new Point in p4”);
Point p3 = new Point(100, 100, “Jane”);
Point p4 = (Point)p3.Clone();
Console.WriteLine(“Before modification:”);
24. Interfaces and Collections 227
Console.WriteLine(“p3: {0}”, p3);
Console.WriteLine(“p4: {0}”, p4);
p4.desc.petName = “Mr. X”;
p4.x = 9;
Console.WriteLine(“nChanged p4.desc.petName and p4.x”);
Console.WriteLine(“After modification:”);
Console.WriteLine(“p3: {0}”, p3);
Console.WriteLine(“p4: {0}”, p4);
}
Figure shows the output
Figure 8.6: output
In order for your Clone() method to make a complete deep copy of the internal reference types, you
need to configure the object returned by MemberwiseClone() to account for the current point’s
name. Here is one possible implementation:
// Now we need to adjust for the PointDescription member.
public object Clone() {
Point newPoint = (Point)this.MemberwiseClone();
PointDescription currentDesc = new PointDescription();
currentDesc.petName = this.desc.petName;
newPoint.desc = currentDesc;
return newPoint;
}
25. 228 C# .Netprogramming
If you rerun the application once again as shown in Figure , you see that the Point returned from
Clone() does copy its internal reference type member variables (note the pet name is now unique
for both p3 and p4).
8.14 BUILDING COMPARABLE OBJECTS (ICOMPARABLE)
The System.IComparable interface specifies a behavior that allows an object to be sorted based on
some specified key. Here is the formal definition:
// This interface allows an object to specify its relationship between other
like objects.
public interface IComparable
{
int CompareTo(object o);
}
Let’s assume you have updated the Car class to maintain an internal ID number (represented by a
simple integer named carID) that can be set via a constructor parameter and manipulated using a
new property named ID. Here are the relevant updates to the Car type:
public class Car
{
...
private int carID;
public int ID
{
get { return carID; }
set { carID = value; }
}
public Car(string name, int currSp, int id)
{
currSpeed = currSp;
petName = name;
carID = id;
}
...
}
Object users might create an array of Car types as follows:
static void Main(string[] args)
{
26. Interfaces and Collections 229
// Make an array of Car types.
Car[] myAutos = new Car[5];
myAutos[0] = new Car(“Rusty”, 80, 1);
myAutos[1] = new Car(“Mary”, 40, 234);
myAutos[2] = new Car(“Viper”, 40, 34);
myAutos[3] = new Car(“Mel”, 40, 4);
myAutos[4] = new Car(“Chucky”, 40, 5);
}
The System.Array class defines a static method named Sort(). When you invoke this method on an
array of intrinsic types (int, short, string, etc.), you are able to sort the items in the array in numerical/
alphabetic order as these intrinsic data types implement IComparable.
To send an array of Car types into the Sort() method as follows
Array.Sort(myAutos);
If you run this test, you would find that an ArgumentException exception is thrown by the runtime,
with the following message: “At least one object must implement IComparable.” When you build
custom types, you can implement IComparable to allow arrays of your types to be sorted.
// The iteration of the Car can be ordered based on the CarID.
public class Car : IComparable
{
...
// IComparable implementation.
int IComparable.CompareTo(object obj)
{
Car temp = (Car)obj;
if(this.carID > temp.carID)
return 1;
if(this.carID < temp.carID)
return -1;
else
return 0;
}
}
As you can see, the logic behind CompareTo() is to test the incoming type against the current
instance based on a specific point of data. The return value of CompareTo() is used to discover if
this type is less than, greater than, or equal to the object it is being compared with (see Table).
27. 230 C# .Netprogramming
Table 8.1
CompareTo() Return Value Meaning in Life
Any number less than zero This instance comes before the specified object in the sort order.
Zero This instance is equal to the specified object.
Any number greater than zero This instance comes after the specified object in the sort order.
Now that your Car type understands how to compare itself to like objects, you can write the
following user code:
static void Main(string[] args)
{
// Make an array of Car types.
...
// Dump current array.
Console.WriteLine(“Here is the unordered set of cars:”);
foreach(Car c in myAutos)
Console.WriteLine(“{0} {1}”, c.ID, c.PetName);
Array.Sort(myAutos);
Console.WriteLine(“Here is the ordered set of cars:”);
foreach(Car c in myAutos)
Console.WriteLine(“{0} {1}”, c.ID, c.PetName);
Console.ReadLine();
}
Figure8.7 : Illustrates a test run.
28. Interfaces and Collections 231
8.15 SPECIFYING MULTIPLE SORT ORDERS (ICOMPARER)
In this version of the Car type, you made use of the car’s ID to function as the baseline of the sort
order. if you wanted to build a Car that could be sorted by ID as well as by pet name then you need
to make friends with another standard interface named IComparer, defined within the
System.Collections namespace as follows:
// A generic way to compare two objects.
interface IComparer
{
int Compare(object o1, object o2);
} Unlike the IComparable interface, IComparer is typically not implemented on the type you are
trying to sort (i.e., the Car). Rather, you implement this interface on any number of helper classes,
one for each sort order (pet name, car ID, etc.).
// This helper class is used to sort an array of Cars by pet name.
using System.Collections;
public class PetNameComparer : IComparer
{
public PetNameComparer(){ }
// Test the pet name of each object.
int IComparer.Compare(object o1, object o2)
{
Car t1 = (Car)o1;
Car t2 = (Car)o2;
return String.Compare(t1.PetName, t2.PetName);
}
}
The object user code is able to make use of this helper class. System.Array has a number of overloaded
Sort() methods, one that just happens to take an object implementing IComparer:
static void Main(string[] args)
{
...
// Now sort by pet name.
Array.Sort(myAutos, new PetNameComparer());
// Dump sorted array.
Console.WriteLine(“Ordering by pet name:”);
foreach(Car c in myAutos)
Console.WriteLine(“{0} {1}”, c.ID, c.PetName);
...
}
29. 232 C# .Netprogramming
8.16 EXPLORING THE SYSTEM. COLLECTIONS NAMESPACE
First of all, System.Collections defines a number of standard interfaces. Most of the classes defined
within the System.Collections namespace implement these interfaces to provide access to their
contents. Table gives a breakdown of the core collectioncentric interfaces.
Table 8.2
System.Collections Interface Meaning in Life
ICollection Defines generic characteristics (e.g., count and thread safety) for a collection
type.
IList Provides behavior to add, remove, and index items in a list of objects.
Also, this interface defines members to determine whether the implementing
collection type is read-only and/or a fixed-size container.
IEqualityComparer Defines methods to support the comparison of objects for equality.
IDictionary Allows an object to represent its contents using name/value pairs.
IDictionaryEnumerator Enumerates the contents of a type supporting IDictionary.
IEnumerable Returns the IEnumerator interface for a given object.
IEnumerator Generally supports foreach-style iteration of subtypes.
IHashCodeProvider Returns the hash code for the implementing type using a customized hash
algorithm.
IKeyComparer (This interface is new to .NET 2.0.) Combines the functionality of
IComparer and IHashCodeProvider to allow objects to be compared in a
“hash-code-compatible manner”.
Many of these interfaces are related by an interface hierarchy, while others are stand-alone
entities. Figure illustrates the relationship between each type.
Figure 8.8: Interface hierarchy
30. Interfaces and Collections 233
8.16.1 Role of ICollection
The ICollection interface is the most primitive interface of the System.Collections namespace in
that it defines a behavior supported by a collection type. In a nutshell, this interface provides a
small set of properties that allow you to determine (a) the number of items in the container, (b) the
thread safety of the container, as well as (c) the ability to copy the contents into a System.Array
type. Formally, ICollection is defined as follows (note that ICollection extends IEnumerable):
public interface ICollection : IEnumerable
{ // IEnumerable member
int Count { get; }
bool IsSynchronized { get; }
object SyncRoot { get; }
void CopyTo(Array array, int index);
}
8.16.2 Role of IDictionary
As you may already be aware, a dictionary is simply a collection that maintains a set of name/value
pairs. For example, you could build a custom type that implements IDictionary such that you can
store Car types (the values) that may be retrieved by ID or pet name (e.g., names). Given this
functionality, you can see that the IDictionary interface defines a Keys and Values property as well
as Add(), Remove(), and Contains() methods. The individual items may be obtained by the type
indexer. Here is the formal definition:
public interface IDictionary :ICollection, IEnumerable
{
bool IsFixedSize { get; }
bool IsReadOnly { get; }
object this[ object key ] { get; set; }
ICollection Keys { get; }
ICollection Values { get; }
void Add(object key, object value);
void Clear();
bool Contains(object key);
IDictionaryEnumerator GetEnumerator();
void Remove(object key);
}
31. 234 C# .Netprogramming
8.16.3 Role of IDictionaryEnumerator
If you were paying attention, you may have noted that IDictionary.GetEnumerator() returns an
instance of the IDictionaryEnumerator type. IDictionaryEnumerator is simply a strongly typed
enumerator, given that it extends IEnumerator by adding the following functionality:
public interface IDictionaryEnumerator : IEnumerator
{
// IEnumerator methods...
DictionaryEntry Entry { get; }
object Key { get; }
object Value { get; }
}
Notice how IDictionaryEnumerator allows you to enumerate over items in the dictionary via the
generic Entry property, which returns a System.Collections.DictionaryEntry class type. In addition,
you are also able to traverse the name/value pairs using the Key/Value properties.
8.16.4 Role of IList
The final key interface of System.Collections is IList, which provides the ability to insert, remove,
and index items into (or out of) a container:
public interface IList :ICollection, IEnumerable
{
bool IsFixedSize { get; }
bool IsReadOnly { get; }
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);
}
8.17 CLASS TYPES OF SYSTEM.COLLECTIONS
As I hope you understand by this point in the chapter, interfaces by themselves are not very useful
until they are implemented by a given class or structure. Table provides a rundown of the core
classes in the System.Collections namespace and the key interfaces they support.
32. Interfaces and Collections 235
Table 8.3
System. Meaning in Life Key Implemented Interfaces
Collections Class
ArrayList Represents a dynamically sized IList, ICollection, IEnumerable, and ICloneable
array of objects
Hashtable Represents a collection of objects IDictionary, ICollection, IEnumerable, and
identified by a numerical key. ICloneable
Custom types stored in a Hashtable
shouldalways override System.
Object.GetHashCode().
Queue Represents a standard first-in, FIFO ICollection, ICloneable, and IEnumerable
queue
SortedList Like a dictionary; however, the IDictionary, ICollection, IEnumerable, and
elements can also be accessed by ICloneable
ordinal position
Stack A last-in, first-out (LIFO) queue ICollection, ICloneable, and IEnumerable
providing push and pop (and peek)
functionality.
8.17.1 Working with the ArrayList Type
The ArrayList type it allows you to dynamically resize the contents at your notion. To illustrate the
basics of this type, consider the following code, which shows the ArrayList to manipulate a set of
Car objects:
static void Main(string[] args)
{
// Create ArrayList and fill with some initial values.
ArrayList carArList = new ArrayList();
carArList.AddRange(
new Car[]{ new Car(“Fred”, 90, 10),
new Car(“Mary”, 100, 50),
new Car(“MB”, 190, 11)
}
);
Console.WriteLine(“Items in carArList: {0}”, carArList.Count);
foreach(Car c in carArList)
Console.WriteLine(“Car pet name: {0}”, c.PetName);
Console.WriteLine(“n->Inserting new Car.”);
carArList.Insert(2, new Car(“TheNewCar”, 0, 12));
Console.WriteLine(“Items in carArList: {0}”, carArList.Count);
object[] arrayOfCars = carArList.ToArray();
33. 236 C# .Netprogramming
for(int i = 0; i < arrayOfCars.Length; i++)
{
Console.WriteLine(“Car pet name: {0}”,
((Car)arrayOfCars[i]).PetName);
}
}
8.17.2 Working with the Queue Type
Queues are containers that ensure items are accessed using a first-in, first-out manner. When you
are modeling a scenario in which items are handled on a first-come, first-served basis,
System.Collections.Queue is your type of choice. In addition to the functionality provided by the
supported interfaces, Queue defines the key members shown in Table.
Table 8.4
Member of System. Meaning in Life
Collection.Queue
Dequeue() Removes and returns the object at the beginning of the Queue
Enqueue() Adds an object to the end of the Queue
Peek() Returns the object at the beginning of the Queue without removing it
To illustrate these methods, we will leverage our automobile theme once again and build a
Queue object that simulates a line of cars waiting to enter a car wash. First, assume the following
static helper method:
public static void WashCar(Car c)
{
Console.WriteLine(“Cleaning {0}”, c.PetName);
} Now, consider the following code:
static void Main(string[] args)
{
...
// Make a Q with three items.
Queue carWashQ = new Queue();
carWashQ.Enqueue(new Car(“FirstCar”, 0, 1));
carWashQ.Enqueue(new Car(“SecondCar”, 0, 2));
carWashQ.Enqueue(new Car(“ThirdCar”, 0, 3));
// Peek at first car in Q.
Console.WriteLine(“First in Q is {0}”,((Car)carWashQ.Peek()).PetName);
// Remove each item from Q.
WashCar((Car)carWashQ.Dequeue());
WashCar((Car)carWashQ.Dequeue());
34. Interfaces and Collections 237
WashCar((Car)carWashQ.Dequeue());
// Try to de-Q again?
try
{ WashCar((Car)carWashQ.Dequeue()); }
catch(Exception e)
{ Console.WriteLine(“Error!! {0}”, e.Message);}
}
Here, you insert three items into the Queue type via its Enqueue() method. The call to Peek() allows
you to view (but not remove) the first item currently in the Queue, which in this case is the car
named FirstCar. Finally, the call to Dequeue() removes the item from the line and sends it into the
WashCar() helper function for processing. Do note that if you attempt to remove items from an
empty queue, a runtime exception is thrown.
8.17.3 Working with the Stack Type
The System.Collections.Stack type represents a collection that maintains items using a last-in,
first-out manner. As you would expect, Stack defines a member named Push() and Pop() (to place
items onto or remove items from the stack). The following stack example makes use of the standard
System.String:
static void Main(string[] args)
{
...
Stack stringStack = new Stack();
stringStack.Push(“One”);
stringStack.Push(“Two”);
stringStack.Push(“Three”);
// Now look at the top item, pop it, and look again.
Console.WriteLine(“Top item is: {0}”, stringStack.Peek());
Console.WriteLine(“Popped off {0}”, stringStack.Pop());
Console.WriteLine(“Top item is: {0}”, stringStack.Peek());
Console.WriteLine(“Popped off {0}”, stringStack.Pop());
Console.WriteLine(“Top item is: {0}”, stringStack.Peek());
Console.WriteLine(“Popped off {0}”, stringStack.Pop());
try
{
Console.WriteLine(“Top item is: {0}”, stringStack.Peek());
Console.WriteLine(“Popped off {0}”, stringStack.Pop());
} catch(Exception e)
{ Console.WriteLine(“Error!! {0}”, e.Message);}
}
35. 238 C# .Netprogramming
Here, you build a stack that contains three string types (named according to their order of
insertion).
As you peek onto the stack, you will always see the item at the very top, and therefore the first
call to Peek() reveals the third string. After a series of Pop() and Peek() calls, the stack is eventually
empty, at which time additional Peek()/Pop() calls raise a system exception.
8.18 SYSTEM.COLLECTIONS.SPECIALIZED NAMESPACE
In addition to the types defined within the System.Collections namespace, you should also be
aware that the .NET base class libraries provide the System.Collections.Specialized namespace,
which defines another set of types that are more (pardon the redundancy) specialized. For example,
the StringDictionary and ListDictionary types each provide a stylized implementation of the
IDictionary interface. Table documents the key class types.
Table 8.5
Member of System.Collections.Specialized Meaning in Life
CollectionsUtil Creates collections that ignore the case in strings.
HybridDictionary Implements IDictionary by using a ListDictionary while the
collection is small, and then switching to a Hashtable when
the collection gets large.
ListDictionary Implements IDictionary using a singly linked list.
Recommended for collections that typically contain ten items or fewer.
NameValueCollection Represents a sorted collection of associated String keys and
String values that can be accessed either with the key or
with the index.
StringCollection Represents a collection of strings.
StringDictionary Implements a hashtable with the key strongly typed to be a
string rather than an object.
StringEnumerator Supports a simple iteration over a StringCollection.
SUMMARY
The .NET framework defines a collection namespace of system .which contains many classes and
interfaces, provides to define various collection of objects every programmer usage this class and
interfaces to develop their applications .user can define their own collection too.
QUESTIONS
1. What is an interface? with a program demonstrate the implicit and explicit access of interface.
2. Write the three methods of ICompare interface class.
36. Interfaces and Collections 239
3. Why interfaces are used in C# programming? With example explain any four interfaces of
System.Collection.
4. Write a C# program which contains following:
(a) an interface called dimension with the methods length() and width() which returns
length and width in centimeters.
(b) another interface called metric dimension with the method lengthinches() and
widthinches() , which returns length and width in inches.
(c) a class box that implements both the above said interfaces. This class has two data
member’s lengthinches and widthinches.
Define appropriate constructor for the class box write a main program of create an
instance of box and to display the box length and width in inches and centimeters by
ionvoking the appropriate methods of two interface.
5. What are the major difference and similarities between a class and an interface?
6. All the member of interface are implicitly abstract .what is the implication of this ?
7. We can combine two or more interface together. Discuss.
8. Describe various forms of implementing interfaces.
9. What is explicit interface implementation? When is it used?
10. When an interface inherit another interface, we call it an extending an interface .But when
a class inherits an interface, we call it as implementing an interface. Why?