Interfaces

   


Recall the Shape class from the drawing program case study presented earlier. It contained the abstract method DrawYourself, which obliged any class derived from Shape to implement a DrawYourself method with the same signature and return type. In other words any class derived from Shape had to fulfill a contract saying: "I the derived class of Shape hereby promise to implement any abstract method contained in Shape or I shall be abstract myself". This guarantee enabled us to call the different DrawYourself implementations in Circle, Triangle and Rectangle through dynamic binding. Notice that the Shape class didn't contain any data members or function member implementations and therefore didn't offer any code reuse benefits to its sub classes; the main reason for creating the Shape class was instead to call DrawYourself with dynamic binding.

The interface language construct can be viewed as the ultimate abstract class in that it only contains abstract functions. Even syntactically an interface resembles a purely abstract class that only contains abstract functions.

A class can implement an interface. The class then promises to implement the interface's abstract function members just like a derived class promises to implement the abstract functions of its base class. So an interface offers an alternative to a purely abstract base class for implementing functions that can be called through dynamic binding. We could for example, in our previous drawing program, have exchanged the Shape class with an equivalent Shape interface also containing an abstract DrawYourself method. The Circle, Triangle and Rectangle classes would then implement the Shape interface instead. This would not affect our ability to call the DrawYourself method via dynamic binding and would thus leave the functionality of the program unchanged. However this experiment fails to demonstrate the true power of interfaces and their proper use.

Even though interfaces and abstract classes are closely related syntactically and semantically they have one important difference: Interfaces can only contain abstract functions whereas abstract classes apart from abstract functions also may contain data members and fully implemented non-abstract functions. Consequently implementing multiple interfaces does not pose the same potential problems as multiple inheritance because clashing data members and implementations are nonexistent. As a result a class can have at most one base class but implement an unlimited number of interfaces.

An interface can itself implement one or more interfaces and thereby inherit the abstract functions of these interfaces. This allows interfaces to form interface hierarchies similar to those formed by classes.

Commonly classes form the sturdy taxonomical class hierarchies presented earlier where each pair of base class/derived class represents an is-a relationship. These hierarchies provide tremendous benefits as discussed earlier, however they also restrict our ability to benefit from polymorphism somewhat. Let's see why: To benefit from polymorphism in a class hierarchy we must have a group of classes with the same ancestor. This ancestor then specifies the function headers (in the form of abstract classes) and thereby the protocols for communicating with the individual implementations in the derived classes. But what if the classes we want to group together in this generic construct do not have the same ancestor? What if they are scattered throughout our program and even located in separate class hierarchies? Suppose for example we are writing a space invaders game containing the two class hierarchies A and B as shown in Figure 17.6.

Figure 17.6. IDrawable let three unrelated classes implement the same abstract method
graphics/17fig06.gif

Like Circle, Rectangle, and Triangle in the drawing case study, we want to draw objects of the three classes Planet, Galaxy and SpaceShip on the screen. Each class has its own distinct way of drawing itself on the screen so we decide to equip each class with a DrawYourself method and hope to group them together under a common ancestor that contains an abstract DrawYourself method. This poses a major problem because the three classes are located in separate parts of the program and do not have a common ancestor. We could attempt to solve the problem by inserting another class sitting on top of both hierarchies and let this class contain the abstract DrawYourself method. This could work but it would distort the hierarchy. Furthermore if we applied this same strategy to other groups of disparately located classes with the same polymorhic needs we would end up with a topmost class containing all kinds of non-related abstract methods.

A much more elegant solution would be to create an interface called, say, IDrawable (Conventionally an interface name commences with an uppercase I) containing the abstract method DrawYourself, and let the three classes implement this interface as illustrated in Figure 17.6. The IDrawable interface could be implemented as follows (more about interface syntax in the following section):

 public interface IDrawable {     void DrawYourself(); } 

NOTE

graphics/common.gif

It would not be possible to substitute the IDrawable interface with a class because Galaxy, SpaceShip and Planet would then each have two base classes, which is invalid.


IDrawable provides a common interface through which we can execute the different implementations of the three classes. So similar to Lines 38-44 in Listing 17.4 that accepted an array of type Shape we can now write a method called DrawSpaceScene which accepts an array with elements of type IDdrawable as shown in the following lines:

 public void DrawSpaceScene(IDrawable [] drawing) {     for(int i = 0; i < drawing.Length; i++)     {         drawing [i].DrawYourself();     } } 

This provides the same benefits in terms of generic programming as described in the drawing program case study.

Defining an interface

The syntax for defining an interface is shown in Syntax Box 17.2. It consists of an optional access modifier followed by the keyword interface, and the interface identifier. The optional <Base_interface_list> specifies >the list of interfaces that are implemented by the interface. As mentioned earlier this list can consist of an unlimited number of interfaces.

Syntax Box 17.2 The Interface

 Interface_definition::= [<Access_modifier>] interface <Interface_identifier> [: <Base_interface_list> graphics/ccc.gif] {     [<Abstract_methods>]     [<Abstract_properties>]     [<Abstract_indexers>]     [<Events>] } 

Where:

 <Abstract_method>::= <Return_type> <Method_identifier> ([<Parameter_list>]); <Abstract_property>::= <Property_type> <Property_identifier> {  [get;] [set;] } <Abstract_indexer>::= <Indexer_type> this [<Parameter_list>] {  [get;] [set;] } <Event>::= event <Event_type> <Event_identifier>; 

Notes:

Conventionally the interface identifier should commence with a capital I (for interface) and because an interface enables a class to perform additional actions by forcing it to implement its abstract members, the interface identifier often ends with able as in IComparable, ICloneable, Istorable and so forth. None of the interface members (abstract methods, properties, indexers and events) can include an access modifier. The members are implicitly declared public because they are meant to be accessible from outside the class that implements the members of interface. Properties and indexers defined in an interface can either have one abstract get accessor or one abstract set accessor or both. Accessors are implicitly declared abstract.

Recall the built-in string method called CompareTo presented in Chapter 7. It allows us to compare two strings lexicographically. For example, we can compare myString and yourString by writing the following:

 myString.CompareTo(yourString) 

If myString is greater than yourString, CompareTo returns a positive value; if the two strings are identical, CompareTo returns zero; and if myString is smaller than yourString, a negative value is returned.

As we realized in Chapter 7, the CompareTo method allowed us to sort a list of strings in alphabetical order. Thus, any class with an equivalent CompareTo method can have its objects sorted. This allows us to construct a sorting method, which can sort not only strings but any list of objects instantiated from a class, guaranteeing to contain a CompareTo implementation. How can a class guarantee this by implementing an interface that declares an abstract CompareTo method. We will continue our story about the generic sorting method in a moment; for now, let's assume that the previous discussion has convinced us that the following interface is needed to construct this generic sorting method.

 public interface IComparable {     int CompareTo(IComparable comp); } 

By implementing this interface, a class guarantees to provide an implementation for a public method called CompareTo with an int return type and one formal parameter of type IComparable. Furthermore, if the CompareTo method is implemented correctly and returns negative, zero, and positive values following the same scheme as that of the string class, we know its objects are comparable. The next section demonstrates how a class implements an interface in general, and demonstrates in particular how the TimeSpan class (originally introduced in Chapter 14, "Class Anatomy III: Writing Intuitive Code") can implement the IComparable interface to make objects of this class comparable.

Implementing an Interface

The syntax for specifying that a class implements an interface is, as shown in Syntax Box 17.3, similar to deriving it from a base class. A colon separates the class identifier from the optional base class and list of interfaces implemented by the class.

Syntax Box 17.3 Class Definition

 Class_defintion::= <Access_modif> class <Class_identifier> [: [<Base_class>][, <Interface_list>]] graphics/ccc.gif {     <Class_members> } 

where

 <Interface_list>::= <Interface_identifier_1> [, <Interface_identifier_2> ...] 

Notes:

  • A class can have zero or one base classes.

  • A class can implement an unlimited number of interfaces. Commas must separate all implemented interfaces.

Example:

The SportsCar class is derived from the Car class and implements the ITunable and IInsurable interfaces.

 public class SportsCar : Car, ITunable, IInsurable {     <Class_members> } 

The SportsCar class must implement all abstract members in Car, ITunable, and IInsurable; otherwise, SportsCar becomes abstract.

By letting the TimeSpan class implement the IComparable interface from the previous section, and by writing the method body for the CompareTo method in TimeSpan as required, you can make any two TimeSpan objects comparable, as demonstrated in Listing 17.11.

Listing 17.11 ComparableTimeSpans.cs
01: using System; 02: 03: public interface IComparable 04: { 05:     int CompareTo(IComparable comp); 06: } 07: 08: public class TimeSpan : IComparable 09: { 10:     private uint totalSeconds; 11: 12:     public TimeSpan() 13:     { 14:         totalSeconds = 0; 15:     } 16: 17:     public TimeSpan(uint initialSeconds) 18:     { 19:         totalSeconds = initialSeconds; 20:     } 21: 22:     public uint Seconds 23:     { 24:         get 25:         { 26:             return totalSeconds; 27:         } 28: 29:         set 30:         { 31:             totalSeconds = value; 32:         } 33:     } 34: 35:     public int CompareTo(IComparable comp) 36:     { 37:         TimeSpan compareTime = (TimeSpan) comp; 38: 39:         if(totalSeconds > compareTime.Seconds) 40:             return 1; 41:         else if(compareTime.Seconds == totalSeconds) 42:             return 0; 43:         else 44:             return -1; 45:     } 46: } 47: 48: class Tester 49: { 50:     public static void Main() 51:     { 52:         TimeSpan myTime = new TimeSpan(3450); 53:         TimeSpan worldRecord = new TimeSpan(1239); 54: 55:         if(myTime.CompareTo(worldRecord) < 0) 56:             Console.WriteLine("My time is below the world record"); 57:         else if(myTime.CompareTo(worldRecord) == 0) 58:             Console.WriteLine("My time is the same as the world record"); 59:         else 60:             Console.WriteLine("I spent more time than the world record  holder"); 61:     } 62: } I spent more time than the world record holder 

The IComparable interface is defined in lines 3 6. Notice that we can use IComparable to specify the type of CompareTo's formal parameter in line 5. Thus, any object of a class that implements the IComparable interface can be passed to this method as an argument. Had we declared the formal parameter to be of type TimeSpan instead, the interface would lose its general appeal and only be useful for the TimeSpan class, which defeats our original purpose of creating a generic sorting method.

The colon in line 8, followed by the word IComparable, specifies that TimeSpan is implementing the IComparable interface.

Lines 35 45 provide the required implementation of the CompareTo method. The method signature and the return type in line 35 are identical to that specified in the definition of the IComparable interface, as required, and the access modifier is public because CompareTo is implicitly specified to be so in the interface definition. The CompareTo method's task in TimeSpace's case is to compare the totalSeconds instance variable contained in the TimeSpan object, which is referenced by the comp parameter, with the totalSeconds instance variable of the current object.

Note

graphics/common.gif

A CompareTo method implemented by a Circle class might compare radiuses. An Account class might compare balances, while TimeSpan compares totalSeconds.


totalSeconds of the current object is readily available, but totalSeconds of the comp object must be accessed via the Seconds property. Even though comp references an object of type TimeSpan the Seconds property is inaccessible through the comp parameter. Only the CompareTo method can be called through comp, because this is the only method specified in the IComparable interface. To access the Seconds property, we must down cast comp to type TimeSpan as in line 37. We can then reach the Seconds property through compareTime in lines 39 and 41.

Just as it would be possible in the drawing program case study to call the DrawYourself method directly through an object of one of Shape's subclasses without the need for dynamic binding, as in

 Circle myCircle; myCircle.DrawYourself();     //Does not require dynamic binding 

it is also possible to call the CompareTo method directly through a TimeSpan object as demonstrated in lines 55 and 57. However, just like the myCircle.DrawYourself call doesn't expose polymorphism and its power, the calls to CompareTo in lines 55 and 57

 myTime.CompareTo(worldRecord)      //Does not require dynamic binding 

are merely useful to illustrate how the CompareTo method works. They could even have been implemented without applying the IComparable interface.

Notice that even though worldRecord is defined to be of type TimeSpan in line 53, it can still (in lines 55 and 57) be used as an argument to the CompareTo method (which accepts arguments of type IComparable) because TimeSpan implements the IComparable interface.

Listing 17.11 was meant to illustrate the syntax of defining and implementing an interface, but it didn't illustrate the true power of interfaces. We make up for this in the next section.

Generic Programming with Interfaces

Suppose we manually need to sort a list of numbers in ascending order. It takes us a while to work out a good sorting system, but eventually we get the hang of it. Soon after, we need to sort a list of students according to their grades. It turns out we can use exactly the same system we used to sort the numbers; the only difference is that instead of comparing pairs of numbers, we compare pairs of students. In fact, we can sort any list of objects with this method, as long as we can compare the pairs of the involved objects. When somebody asks us to describe the technique, we tell them the following (with large chunks cut out): "First you do the following…and if object x is greater than object y, …otherwise…later you…finally you…." We are able to explain the sorting technique in general terms without mentioning the kind of object we are sorting. We simply say "If object X is greater than object Y" instead. The person must work out how to compare pairs of the particular objects he or she is sorting.

We can use the same principle in computer programming to support code reuse. If the fundamental implementation of our sorting algorithm is independent of the object type we are sorting, we can write a generic implementation for this sorting routine an implementation that can be reused to sort any (comparable) object type.

This section demonstrates how we can apply interfaces to write a generic version of the BubbleSortAscending method that was first presented in Chapter 11, "Arrays Part II: Multi-Dimensional Arrays. Searching and Sorting Arrays," and shown in Listing 17.12 to refresh your memory. Notice that Listing 17.12 does not compile.

Listing 17.12 BubbleSortAscending.cs
01:  // Sort the elements of an array in ascending order 02: public static void BubbleSortAscending(int [] bubbles) 03: { 04:     bool swapped = true; 05: 06:     for (int i = 0; swapped; i++) 07:     { 08:         swapped = false; 09:         for (int j = 0; j < (bubbles.Length - (i + 1)); j++) 10:         { 11:             if (bubbles[j] > bubbles[j + 1]) 12:             { 13:                 Swap(j, j + 1, bubbles); 14:                 swapped = true; 15:             } 16:         } 17:     } 18: } 19: 20:  //Swap two elements of an array 21: public static void Swap(int first, int second, int [] arr) 22: { 23:     int temp; 24: 25:     temp = arr[first]; 26:     arr[first] = arr[second]; 27:     arr[second] = temp; 28: } 

At the moment, the BubbleSortAscending method can only be used to sort arrays with elements of type int (see line 2). If we needed to sort an array of doubles, we would need to write another bubble sort method meant for an array of element type double. Each class of objects with the need to be sorted requires its own dedicated bubble sort method. For example, 30 classes would require 30 sorting methods. This is clearly an inefficient avenue to follow. Instead, we set out to make the BubbleSortAscending implementation generic. The end result is shown in Listing 17.13, and the logic behind the code is discussed next.

The BubbleSortAscending method in Listing 17.12 is highly suited to become generic because only line 11 of this implementation cares about the changing array element type; the rest of the sorting logic remains unchanged. Line 11 asks the question, "Is element j greater than element j+1?" If we can somehow guarantee that the array element type of the array passed to the bubble sort method contains a method with the name CompareTo that only return a positive value if j is greater than j+1, we can compare two objects in the array with the following call:

 (objectA.CompareTo(objectB) > 0) 

and we can exchange line 11 of Listing 17.12 with the following line:

 if (bubbles[j].CompareTo(bubbles[j + 1]) > 0) 

How can we guarantee the existence of a CompareTo method? Any class that implements the IComparable interface defined in the previous section commits to implementing a CompareTo method. Consequently, any array element type that implements IComparable can be sorted by our sorting method. By declaring the formal parameter of the generic bubble sort method to be an array of type IComparable, as stated in its header in line 51 of Listing 17.13, we are assured that only arrays with comparable objects are passed to the method.

The only differences between our non-generic bubble sort in Listing 17.12 and the generic version in lines 51 57 of Listing 17.13 are found in the method header (line 51 of Listing 17.13) and the object comparison in line 60.

The TimeSpan class merely needs to implement the IComparable interface as specified in line 13 and provide an implementation for CompareTo (lines 35 45) to become "sortable." However, this privilege is not restricted to the TimeSpan class. We can make Account, RacingCar, Circle, Bacterium, and any other suitable classes "sortable" simply by letting the class implement the IComparable interface and by adding a few lines of code to implement the CompareTo method. Every "sortable" class reuses the same bubble sort code written just once.

Again, the dynamic binding mechanism is at work behind the scenes (in line 60 of Listing 17.13) to make this powerful scenario work. Even though the bubbles array contains IComparable elements, the call in line 60 to CompareTo automatically, via dynamic binding, calls the implementation for CompareTo that matches the object type stored in the element when the call takes place.

The Main method demonstrates our sorting method's ability to sort an array of four TimeSpan objects. Notice that even though the raceTime array is of type TimeSpan, it is still accepted by the sorting method because TimeSpan implements the IComparable interface.

Listing 17.13 GenericBubbleSort.cs
01: using System; 02: 03: public interface IComparable 04: { 05:     int CompareTo(IComparable comp); 06: } 07: 08: public class TimeSpan : IComparable 09: { 10:     private uint totalSeconds; 11: 12:     public TimeSpan() 13:     { 14:         totalSeconds = 0; 15:     } 16: 17:     public TimeSpan(uint initialSeconds) 18:     { 19:         totalSeconds = initialSeconds; 20:     } 21: 22:     public uint Seconds 23:     { 24:         get 25:         { 26:             return totalSeconds; 27:         } 28: 29:         set 30:         { 31:             totalSeconds = value; 32:         } 33:     } 34: 35:     public virtual int CompareTo(IComparable comp) 36:     { 37:         TimeSpan compareTime = (TimeSpan) comp; 38: 39:         if(totalSeconds > compareTime.Seconds) 40:             return 1; 41:         else if(compareTime.Seconds == totalSeconds) 42:             return 0; 43:         else 44:             return -1; 45:     } 46: } 47: 48: class Sorter 49: { 50:      // Sort the comparable elements of an array in ascending order 51:     public static void BubbleSortAscending(IComparable [] bubbles) 52:     { 53:         bool swapped = true; 54: 55:         for (int i = 0; swapped; i++) 56:         { 57:             swapped = false; 58:             for (int j = 0; j < (bubbles.Length - (i + 1)); j++) 59:             { 60:                 if (bubbles[j].CompareTo(bubbles[j + 1]) > 0) 61:                 { 62:                     Swap(j, j + 1, bubbles); 63:                     swapped = true; 64:                 } 65:             } 66:         } 67:     } 68: 69:      //Swap two elements of an array 70:     public static void Swap(int first, int second, IComparable [] arr) 71:     { 72:         IComparable temp; 73: 74:         temp = arr[first]; 75:         arr[first] = arr[second]; 76:         arr[second] = temp; 77:     } 78: } 79: 80: class Tester 81: { 82:     public static void Main() 83:     { 84:         TimeSpan [] raceTimes = new TimeSpan[4]; 85: 86:         raceTimes[0] = new TimeSpan(153); 87:         raceTimes[1] = new TimeSpan(165); 88:         raceTimes[2] = new TimeSpan(108); 89:         raceTimes[3] = new TimeSpan(142); 90: 91:         Sorter.BubbleSortAscending(raceTimes); 92: 93:         Console.WriteLine("List of sorted time spans:"); 94:         foreach (TimeSpan time in raceTimes) 95:         { 96:             Console.WriteLine(time.Seconds); 97:         } 98:     } 99: } List of sorted time spans: 108 142 153 165 

Interfaces Can Only Be Instantiated Indirectly

graphics/common.gif

Just like abstract classes, it doesn't make sense to instantiate an interface as in the following:

 IComparable icComp = new IComparable();       //Invalid 

You can only instantiate classes that provide implementations for all the abstract classes specified by ancestor classes and by implemented interfaces.


Building Interface Hierarchies

It is possible to extend an existing interface A by letting an interface B implement interface A. Interface B then contains the members from interface A, plus the members specified inside its own definition. A class can then either implement interface A or B according to its needs. If a class implements interface A, it must implement its abstract functions; if a class implements interface B, it must implement not only the abstract functions specified by B but also the abstract functions it inherits from A.

For example, we can extend the IComparable interface from the previous section by a new interface called IComparableAdvanced, as shown in the following lines:

 interface IComparableAdvanced : IComparable {     bool GreaterThan(IComparableAdvanced comp);     bool LessThan(IComparableAdvanced comp); } 

Any class implementing IComparableAdvanced must implement not only GreaterThan and LessThan but also CompareTo. Several interfaces can extend each other to form interface hierarchies.

Interface Conversions

If an object of class C implements an interface I, you can assign an instance of class C to a variable of interface I without using any explicit casts. For example, you can assign TimeSpan to icTime in the following line:

 IComparable icTime = new TimeSpan(392); 

because TimeSpan implements the IComparable interface.

If we need to go in the opposite direction (as in line 37 of Listing 17.13) and convert icTime to a variable of type TimeSpan, we need to perform the equivalent of a down cast, described earlier. (The down cast will allow us to access all the members of the TimeSpan object, rather than just those specified for the IComparable interface.) This requires a cast

 TimeSpan myTime = (TimeSpan) icTime; 

If icTime does not contain a TimeSpan object (but an object of another type that also implements the IComparable interface), the system will generate an exception. If you don't know in advance whether icTime contains a TimeSpan object, you can use the is and as operators described previously to ensure that icTime is of type TimeSpan before any conversions are attempted:

In the following, we use the is operator:

 TimeSpan myTime; if (icTime is TimeSpan)     myTime = (TimeSpan) icTime; else     Console.WriteLine("Could not assign icTime to myTime"); 

and next the as operator

 TimeSpan myTime; myTime = icTime as TimeSpan; if (myTime == null)     Console.WriteLine("Could not assign icTime to myTime"); 

In this context, the as operator is also more efficient than the is operator.

Note

graphics/common.gif

Sometimes, you might just want to check the object type in the variable of an interface type (like icTime) without performing a cast. The is operator is better suited for this scenario than the as operator, because the is operator does not automatically perform a cast.


Overriding Virtual Interface Implementations

Any function that a class implements from an interface can optionally be declared virtual. For example, the CompareTo method of TimeSpan was declared virtual in line 35 of Listing 17.13. A class derived from TimeSpan can override this virtual method in standard fashion or provide a new implementation.

For example, we could design a TimeSpanAdvanced class that overrides the CompareTo method of the TimeSpan class to provide more detailed feedback (see Listing 17.14). In this case, the new CompareTo returns 2 (line 10) if the TimeSpan object A is more than 50 seconds greater than object B. Conversely, if object A is more than 50 seconds below object B, minus 2 is returned.

Note

graphics/common.gif

The code in Listing 17.14 does not compile independently. You can insert the TimeSpanAdvanced class beside the TimeSpan class in Listing 17.13 and make a couple of calls to its CompareTo method to give it a test run.


Listing 17.14 TimeSpanAdvance.cs
01: public class TimeSpanAdvanced : TimeSpan 02: { 03:     public override int CompareTo(IComparable comp) 04:     { 05:         TimeSpan compareTime = (TimeSpan) comp; 06: 07:         if(base.Seconds > compareTime.Seconds) 08:         { 09:             if(base.Seconds > (compareTime.Seconds + 50)) 10:                 return 2; 11:             else 12:                 return 1; 13:         } 14:         else if(base.Seconds < compareTime.Seconds) 15:         { 16:             if(base.Seconds < (compareTime.Seconds - 50)) 17:                 return -2; 18:             else 19:                 return -1; 20:         } 21:         else 22:             return 0; 23:     } 24: } 

Implementing Interface Functions Explicitly

When we implemented the CompareTo method in the TimeSpan class, it was implicitly understood by the compiler that we were implementing the CompareTo method specified by the IComparable interface. Generally, we simply need to include a function header in our implementing class with the same signature, return type, and access modifier (public) as that of the abstract function in the interface we are implementing; we don't need to explicitly state which interface we are implementing.

However, if a class implements two interfaces each containing abstract functions with the same signature, the compiler is not able to determine which of these two interfaces we are implementing simply by looking at the method header in our implementing class.

For example, if our space invader game presented at the introduction to this interface section contained not only an IDrawable interface with the abstract method DrawYourself meant to allow a game piece (like the Planet, SpaceShip or other mentioned previously) to draw itself onscreen

 interface IDrawable {     void DrawYourself(); } 

but also an interface called IPrintable with an abstract method called DrawYourself meant to allow an object to print itself on a printer

 interface IPrintable {     void DrawYourself(); } 

then it would be impossible for the compiler to determine which of the interfaces for which the following DrawYourself implementations are implemented.


graphics/17infig03.gif

To resolve this problem, we can use explicit implementation for one or both of the implementations by inserting the name of the interface in front of the function name, as in the following code fragment.


graphics/17infig04.gif

In this case, we only applied explicit implementation to the IDrawable implementation. The compiler is then able to implicitly determine that the other DrawYourself method is implementing IPrintable's DrawYourself method. Alternatively, we could have applied explicit implementation on both method implementations.

Explicit implementations are implicitly public, so this access modifier cannot be used on explicit implementations. Furthermore, explicit implementations cannot be declared abstract, virtual, or new.

An explicitly implemented function cannot be accessed through its object as follows:

 SpaceShip mySpaceShip = new SpaceShip(); ... mySpaceShip.DrawYourself()    // Calls the implicitly implemented DrawYourself                               // not the explicitly implemented DrawYourself 

Instead, we can call the DrawYourself implementation for IDrawable through a variable of type IDrawable (using dynamic binding) as in the following:

 IDrawable idShip = mySpaceShip; IdShip.DrawYourself() 

Explicit implementation can be useful, even when we are not trying to resolve method signature clashes from different interfaces. Sometimes, we might want to implement an interface only with the intent of calling some or all of those methods through dynamic binding and not from the object itself. For example, if we only wanted the TimeSpace implementation of the CompareTo method to be called via dynamic binding and not be part of the TimeSpan object's directly accessible functions, we could define it as follows:

 public class TimeSpace {     ...     int IComparable.CompareTo(IComparable comp)     {         ...     } } 

The third line in the following code fragment is now invalid:

 TimeSpan myTime = new TimeSpan(); TimeSpan yourTime = new TimeSpan(); myTime.CompareTo(yourTime)    //Invalid 

whereas the following is valid:

 IComparable icTime = myTime; icTime.CompareTo(yourTime) 

Note

graphics/common.gif

As mentioned earlier, it is impossible to declare an explicit interface implementation to be virtual, so a derived class cannot override this function but must re-implement it. For example, the TimeSpanAdvanced class defined in Listing 17.14 could not use the keyword override in line 3 if CompareTo was explicitly implemented in TimeSpan.



   


C# Primer Plus
C Primer Plus (5th Edition)
ISBN: 0672326965
EAN: 2147483647
Year: 2000
Pages: 286
Authors: Stephen Prata

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